Git Product home page Git Product logo

react_good_page's People

Stargazers

 avatar

Watchers

 avatar  avatar

react_good_page's Issues

context and childContext

Using context and childContext in React
在 React 使用 context 和 childContext

When work­ing with React in a deeply nested hier­ar­chy, often times you will find your­self in a sit­u­a­tion where you need to pass down props from the par­ent as is to the chil­dren and the grand­chil­dren. Although pass­ing down prop­er­ties is great, but writ­ing code in such a man­ner encour­ages rely­ing on mem­ory to pass down the nec­es­sary props, and that’s not really a good use of your brain.Fortunately, React pro­vides a way to make this task easier.

在操作 React 中深层嵌套的层次结构时,当你需要从父组件获取 props 传递到子组件时,通常你需要找到自己所在的位置。尽管传递 properties 是好的做法,但在写代码时却要依靠记忆传递所需要的 props,这对大脑来说不怎么友好。幸运的是,React 提供了一种方式使得这些工作简单完成。

React has some­thing called as Child contexts that allows a par­ent ele­ment to spec­ify a con­text — aka a set of prop­er­ties — which can be accessed from all of its chil­dren and grand­chil­dren via the this.context object.

React 有一些叫做 Child context 的东西,允许父元素指定一个 context - 等于设置一个属性 - 它的所有 children 和 grandchildren 都可以通过 this.context 对象访问它的属性。

There are 2 aspects to how you would go about imple­ment­ing this in your code.

在代码实现需要处理两方面。

1.In the par­ent, the getChild­Con­text method is used to return an object that is passed own as the child con­text. The prop types of all the keys in this object must also be defined in the childContextTypes prop­erty of the parent.
2.Any child that needs to con­sume the prop­er­ties passed down from its par­ents must whitelist the prop­er­ties it wants to access via the ‘con­text­Types’ property.
Here’s an exam­ple of how this works

1.在父组件,getChildContext 方法返回一个对象,这个对象把自己传给 child context。这个对象中所有的 key 的 prop types 也必须在父组件的 childContextType 属性中定义。
2.任何 child 想访问从 parent 传递过来的 properties,都需要通过将 properties 添加到 contextTypes 属性进行访问。

这里是一段它们如何工作的代码:
var Grandparent = React.createClass({

childContextTypes: {
     foo: React.PropTypes.string.isRequired
},

getChildContext: function() {
     return { foo: "I m the grandparent" };
},

render: function() {
     return <Parent />;
}

});

var Parent = React.createClass({

childContextTypes: {
     bar: React.PropTypes.string.isRequired
},

getChildContext: function() {
     return { bar: "I am the parent" };
},

render: function() {
     return <Child />;
}

});

var Child = React.createClass({

contextTypes: {
    foo: React.PropTypes.string.isRequired,
    bar: React.PropTypes.string.isRequired
},

render: function() {
    return (
      <div>
        <div>My name is: {this.context.foo}</div>
        <div>My name is: {this.context.bar}</div>
      </div>
    )
}

});

// Finally you render the grandparent
React.render(, document.body);
One may argue that this makes the code a bit more ver­bose, because now you end up cre­at­ing an addi­tional key contextTypes on every child com­po­nent. How­ever, the real­ity is that you can spec­ify the ‘con­text­Types’ prop­erty on a mixin. That way you can avoid rewrit­ing the whitelist­ing boil­er­plate code by sim­ply using the mixin on all the child com­po­nents that need to access those prop­er­ties from its context.

有人可能会说这使得代码更复杂,因为现在要在每个子组件额外的添加 contextTypes 属性。但是,其实你可以把 contextTypes 属性的代码放到 mixin。通过简单的在所有子组件使用 mixin 可以避免重写白名单的代码,这样所有子组件都可以通过 context 访问父组件的属性了。

学习redux

redux
redux 是一个 Flux 架构的实现,react-europe 上的演讲可以看到很酷的功能(视频需翻墙)

react 项目所有的代码都是可以 hot-reload,并且不丢失 state,这对于开发复杂的单页应用非常有帮助,基于大量 es6+ 特性开发,看源码也算是学习es6+的方法。

hot reload
redux demo

redux 是如何实现 hot-reload 的?
webpack 提供的Hot Module Replacemen
redux 作者开发的React Hot Loader
redux 代码整理
Provider
当你使用redux时,你应该只有一个store,react-redux提供了一个叫做Provider的组件来帮助你维护这个store

Provider主要逻辑是

接收store,并将store绑定到childContext上
当store发生变化时,更新store(这里的变化不是指应用数据的变化,而是主要指reducer的变化,为了实现不改变应用state,而hot-reload)
import { createStore, combineReducers } from 'redux';
/**
* redux 1.0后,与组件相关的代码被迁移到 react-redux
* Provider 是一个redux 提供的组件,用来接收 store
*/
import { Provider } from 'react-redux';
//构造 reducer&store
const reducer = combineReducers(reducers);
const store = createStore(reducer);

export default class App extends Component {
  render() {
    /** Provider 接收一个 props 为 store
     * Provider 的 children 必须为一个 function,
     * 因为 Provider 内部会执行 this.props.children()
     */
    return (    
      <Provider store={store}>
        {() => <TodoApp /> }
      </Provider>
    );
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
redux 为了方便开发,使用了decorator,来简化开发,上面的代码可以简化成

import { createStore, combineReducers } from 'redux';
import { provide } from 'react-redux';
const reducer = combineReducers(reducers);
const store = createStore(reducer);

@provide(store);
export default class App extends Component {
  render() {
    return (    
        {() => <TodoApp /> }
    );
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
Reducer
reducers 可以理解为一堆 newState = fn(oldState, action)的函数,例如

//传入 state 和 action,返回新的 state
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [{
id: (state.length === 0) ? 0 : state[0].id + 1,
marked: false,
text: action.text
}, ...state];
break;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
那么 reducer 怎么被使用呢?

// 把多个 reducers 合并成一个 reducer
const reducer = combineReducers(reducers);
const store = createStore(reducer);

1
2
3
combineReducers的源码如下(简化版)

export default function combineReducers(reducers) {
// 容错代码 pick 主要是把非 function 的 reducer 删除
var finalReducers = pick(reducers, (val) => typeof val === 'function');
// combineReducers最后返回的是一个函数,接收 steate 和 action
// 看明白了吧,这个函数就是为了把多个 reducers 合并成一个 reducer
return function combination(state = {}, action) {
return mapValues(finalReducers, (reducer, key) => {
//state 会根据 key 来区分
var newState = reducer(state[key], action);
return newState;
});
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Store
store 会存储当前的 state、另外可以调用 action 和监听变化

Connector
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { Connector } from 'react-redux';
import Header from '../components/Header';
import MainSection from '../components/MainSection';
import * as TodoActions from '../actions/TodoActions';

export default class TodoApp extends Component {
render() {
return (
<Connector select={state => ({ todos: state.todos })}>
{this.renderChild}

);
}
renderChild({ todos, dispatch }) {
const actions = bindActionCreators(TodoActions, dispatch);
return (





);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
connector同样是react-redux提供的组件,主要用来将 store 的一部分数据,和组件进行绑定,方便当数据改变时,对组件进行更新(通过改变这个connector组件的state)

数据的检测都使用类似PureRenderMixin的判断,action中修改数据时应该注意。

connector 需要传入一个 select 的 props,select 是个函数,参数为 state

state 是当前应用的 state,当前应用的 state 是通过Provider的 this.context.state 传下来的

同样 connector 也有 Decorator 的简化版

import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Header from '../components/Header';
import MainSection from '../components/MainSection';
import * as TodoActions from '../actions/TodoActions';
@connect({state => ({ todos: state.todos })})
export default class TodoApp extends Component {
render({ todos, dispatch }) {
// actions
const actions = bindActionCreators(TodoActions, dispatch);
return (





);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
action
action最简单可以返回一个包含 action type 的对象,redux 会对 action 进行封装

同样借助 middleware,你也可以返回 thunk、promise等数据类型

export function addTodo(text) {
return {
type: types.ADD_TODO,
text
};
}

// Can also be async if you return a function
// by middleware redux-thunk
export function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with dispatch
dispatch(increment());
}, 1000);
};
}

// Could also read state of a store in the callback form
export function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();

if (counter % 2 === 0) {
  return;
}

dispatch(increment());

};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
由于redux所有数据(可以理解为state)的变化,都做了类似PureRenderMixin的判断,action中修改数据时应该注意,否则会出现数据不变的情况。

为了让数据改变生效,你应该类似这么写

function (state, action) {
return {
...state,
isAuthenticated: true,
email: action.email
};
}
1
2
3
4
5
6
7
middleware
redux 的middleware是为了支持更多种的 action

// Instead of this
dispatch(action)
// do this
middleware(dispatch)(action)

// simple middleware
function promiseMiddleware(next) {
  return action =>
    action && typeof action.then === 'function'
      ? action.then(next)
      : next(action);
}   

React 状态管理库: Mobx

React 是一个专注于视图层的库。React 维护了状态到视图的映射关系,开发者只需关心状态即可,由 React 来操控视图。

在小型应用中,单独使用 React 是没什么问题的。但在复杂应用中,容易碰到一些状态管理方面的问题,如:

React 只提供了在内部组件修改状态的接口 setState。导致数据、业务逻辑和视图层耦合在组件内部,不利于扩展和维护。

React 应用即一颗组件树。兄弟节点,或者不在同一树杈的节点之间的状态同步是非常麻烦。

关心性能的情况下,需要手动设置 shouldComponentUpdate

这时就需要引入状态管理库。现在常用的状态管理库有 Mobx 和 Redux,本文会重点介绍 Mobx,然后会将 Mobx 和 Redux 进行对比,最后展望下未来的 React 状态管理方面趋势。

Mobx 简介
Mobx 的理念非常简单,可以用一个 demo 就把其核心原理说清楚。Mobx/MobxReact 中有三个核心概念,observable、observer、action。为了简单起见,本文没有提及 computed 等概念。

observable: 通过 observable(state) 定义组件的状态,包装后的状态是一个可观察数据(Observable Data)。

observer: 通过 observer(ReactComponent) 定义组件。

action: 通过 action 来修改状态。

简化图如下:
bvqtuq

只讲概念还比较模糊,下面给大家举个例子。

// 通过 observable 定义组件的状态
const user = mobx.observable({
    name: "Jay",
     age: 22
})

// 通过 action 定义如何修改组件的状态
const changeName = mobx.action(name => user.name = name)
const changeAge = mobx.action(age => user.age = age)

// 通过 observer 定义 ReactComponent 组件。
const Hello = mobxReact.observer(class Hello extends React.Component {
        componentDidMount(){
            // 视图层通过事件触发 action
        changeName('Wang') // render Wang
    }

    render() {
                // 渲染
            console.log('render',user.name);
        return <div>Hello,{user.name}!</div>
    }
})

ReactDOM.render(<Hello />, document.getElementById('mount'));

// 非视图层事件触发,外部直接触发 action
changeName('Wang2')// render Wang2
// 重点:没有触发重新渲染
// 原因:Hello 组件并没有用到 `user.age` 这个可观察数据
changeAge('18')  // no console

例子看完了,是不是非常简单。

使用 Mobx,组件状态可以在外部定义(也可以在组件内部),因此,数据、业务逻辑可以轻易地和视图层分离,提高应用的可扩展性和可维护性。另外,由于组件状态可以在外部定义,兄弟节点之间的状态同步也非常容易。最后一点, Mobx 知道什么时候应该渲染页面,因此基本不需要手动设置 shouldComponentUpdate 来提高应用性能。

接下来给大家介绍下 Mobx 中 observable observer action 的用法,并会简单介绍一下其原理。

observable
Mobx 如此简单的原因之一,就是使用了可观察数据(Observable Data)。简单说,可观察数据就是可以观察到数据的读取、写入,并进行拦截。

Mobx 提供了 observable 接口来定义可观察数据。定义的可观察数据,通常也是组件的状态。该方法接收一个参数,参数可以是原始数据类型、普通 Object、Array、或者 ES6 中的 Map 类型,返回一个 observable 类型的参数。

Array.isArray(mobx.observable([1,2,3])) === false // true
mobx.isObservable(mobx.observable([1,2,3])) === true // true

注意,数组经过 observable 包装后,就不是 Array 类型了,而是 Mobx 定义的一个特殊类型 ———— observable 类型。observable 类型,可以通过 mobx.isObservable 来检查。

虽然数据类型不一样,但是使用方式基本和原来一致(原始数据类型除外)。

const observableArr =  mobx.observable([1,2,3]);
const observableObj =  mobx.observable({name: 'Jay'});
const observableMap =  mobx.observable(new Map([['name','Wang']]));

console.log(observableArr[0])  // 1
console.log(observableObj.name)  // Jay
console.log(observableMap.get('name'))  // Wang

可观察数据类型的原理是,在读取数据时,通过 getter 来拦截,在写入数据时,通过setter 来拦截。

Object.defineProperty(o, key, {
  get : function(){
        // 收集依赖的组件
    return value;
  },
  set : function(newValue){
        // 通知依赖的组件更新
        value = newValue
  },
});

在可观察数据被组件读取时,Mobx 会进行拦截,并记录该组件和可观察数据的依赖关系。在可观察数据被写入时,Mobx 也会进行拦截,并通知依赖它的组件重新渲染。

observer
observer 接收一个 React 组件作为参数,并将其转变成响应式(Reactive)组件。

// 普通组件
const Hello = mobxReact.observer(class Hello extends React.Component {
    render() {
        return <div>Hello,{user.name}!</div>
    }
})

// 函数组件
const Hello = mobxReact.observer( () => (
    <div>Hello,{user.name}!</div>
))

响应式组件,即当且仅当组件依赖的可观察数据发生改变时,组件才会自动响应,并重新渲染。

在本文最开始的例子中,响应式组件依赖了 user.name,但是没有依赖 user.age。所以当user.name 发现变化时,组件更新。而 user.age 发生变化时,组件没有更新。

这里再详细分析本文中的第一个例子:

user.name = 'Wang2'// render Wang2
// 重点:没有触发重新渲染
// 原因:Hello 组件并没有用到 `user.age` 这个可观察数据
user.age = '18'  // no console

当可观察数据变化时,Mobx 会调用 forceUpdate 直接更新组件。

而在传统 React 应用中,当状态、属性变化后会先调用 shouldComponentUpdate,该方法会深层对比前后状态和属性是否发生改变,再确定是否更新组件。

shouldComponentUpdate 是很消耗性能的。Mobx 通过可观察数据,精确地知道组件是否需要更新,减少了调用 shouldComponentUpdate 这一步。这是 Mobx 性能好的原因之一。

另外需要注意的是 observer 并不是 mobx 的方法,而是 mobx-react 的方法。mobx 和 mobx-react 关系如同 react 与 react-dom。

action
在 Mobx 中是可以直接修改可观察数据,来进行更新组件的,但不建议这样做。如果在任何地方都修改可观察数据,将导致页面状态难以管理。

所有对可观察数据地修改,都应该在 action 中进行。

const changeName = mobx.action(name => user.name = name)

使用 Mobx 可以将组件状态定义在组件外部,这样,组件逻辑和组件视图便很容易分离,兄弟组件之间的状态也很容易同步。另外,也不再需要手动使用 shouldComponentUpdate 进行性能优化了。

Mobx 与 Redux 对比
Mobx 的优势来源于可变数据(Mutable Data)和可观察数据 (Observable Data) 。

Redux 的优势来源于不可变数据(Immutable data)。

可观察数据的优势,在前文已经介绍过了。现在再来聊聊可变数据和不可变数据。

顾名思义,可变数据和不可变数据的区别在于,可变数据创建后可以修改,不可变数据创建后不可以修改。

可变数据,可以直接修改,所以操作起来非常简单。这使得使用 mobx 改变状态,变得十分简单。

不可变数据并不一定要用到 Immutable 库。它完全可以是一种约定,只要创建后不修改即可。比如说,Redux 中的 state。每次修改都会重新生成一个 newState ,而不会对原来的值进行改变。所以说 Redux 中的 state 就是不可变数据。

reducer(state, action) => newState.

不可变数据的优势在于,它可预测,可回溯。示例代码如下:

function foo(bar) {
  let data = { key: 'value' };
  bar(data);
  console.log(data.key); // 猜猜会打印什么?
}

如果是可变数据,data.key 的值可能会在 bar 函数中被改变,所以不能确定会打印什么值。但是如果是不可变数据,那么就可以肯定打印值是什么。这就是不可变数据的优势 ———— 可预测。不可变数据不会随着时间的变化(程序的运行)而发生改变。在需要回溯的时候,直接获取保存的值即可。

Mobx 与 Redux 技术选型的本质,是在可变数据与不可变数据之间选择。具体业务场景的技术选型,还需要根据实际情况进行分析,脱离业务场景讨论技术选型是没有意义的。但我个人在状态管理的技术选型上,还是倾向于 Mobx 的。原因是前端与副作用打交道非常频繁,有 Http 请求的副作用,Dom 操作的副作用等等。使用不可变数据,还必须得使用中间件对副作用封装;在 Redux 中修改一次状态,需要经过 Action、Dispatch、Reducer 三个步骤,代码写起来太啰嗦;而前端的程序以中小型程序为主,纯函数带来的可预测性的收益,远不及其带的代码复杂度所需要付出的成本。而 Mobx 使用起来更加简单,更适合现在以业务驱动、快速迭代的开发节奏。

展望:Mobx 与不可变数据的融合
不可变数据和可变数据,都是对状态的一种描述。那么有没有一种方案,能将一种状态,同时用可变数据和不可变数据来描述呢?这样就可以同时享有二者的优势了。(注意:当我们说可变数据时,通常它还是可观察数据,后文统一只说可变数据。)

答案是肯定的,它就是 MST(mobx-state-tree) https://github.com/mobxjs/mob...。

MST 是一个状态容器:一种状态,同时包含了可变数据、不可变数据两种不同的形式。

为了让状态可以在可变数据和不可变数据两种形式之间能够高效地相互转化,必须遵循 MST 定义状态的方法。

在 MST 中,定义状态必须先定义它的结构。状态的结构是一颗树(tree),树是由多层模型(model)组成,model 是由多个节点组成。

在下面的代码中,树只有一层 model,该 model 也只有一个节点:title。title 的类型是事先定好的,在这里是 types.string。树的结构定义好后,通过 create 方法传入数据,就生成树

import {types} from "mobx-state-tree"

// declaring the shape of a node with the type `Todo`
const Todo = types.model({
    title: types.string
})

// creating a tree based on the "Todo" type, with initial data:
const coffeeTodo = Todo.create({
    title: "Get coffee"
})

在一些稍微复杂的例子中,树的 model 可以有多层,每层可以有多个节点,有些节点定义的是数据类型(types.xxx),有些节点直接定义的是数据。下面的示例中,就是定义了一个多层多节点的树。除此之外,注意 types.model 函数的第一个参数定义的是 model 的名字,第二参数定义的是 model 的所有属性,第三个参数定义的是 action。

import { types, onSnapshot } from "mobx-state-tree"

const Todo = types.model("Todo", {
    title: types.string,
    done: false
}, {
    toggle() {
        this.done = !this.done
    }
})

const Store = types.model("Store", {
    todos: types.array(Todo)
})

// create an instance from a snapshot
const store = Store.create({ todos: [{
    title: "Get coffee"
}]})

最关键的来了,请看下面的代码。

// listen to new snapshots
onSnapshot(store, (snapshot) => {
    console.dir(snapshot)
})

// invoke action that modifies the tree
store.todos[0].toggle()
// prints: `{ todos: [{ title: "Get coffee", done: true }]}`

// prints: { todos: [{ title: "Get coffee", done: true }]}
在上述代码的第一部分,使用 onSnapshot 监听状态的改变。第二部分,调用 store.todos[0].toggle() ,在这个 action 中通过使用可变数据的方式,直接修改了当前的状态。同时在 onSnapshot 生成了一个状态快照。这个状态快照就是状态的不可变数据的表现形式。

MST 这么神奇,那么具体怎么用呢?MST 只是一个状态容器,同时包含了可变数据和不可变数据。你可以用 MST 直接搭配 React 使用。可以 MST + Mobx + React 配合着用,还可以 MST + Redux + React 混搭着用。

MST 比较新,业内的实践非常少,如果不是急需,现在还可以先观望一下。

虚拟dom浅析

通过学习React的Virtual DOM的知识,去加速你们的应用吧。对框架内部实现的介绍,比较全面且适合初学者,我们会让JSX更加简单易懂,给你展示React是如何判断要不要重新render,解释如何找到应用的性能瓶颈,以及给大家一些小贴士,如何避免常见错误。

React在前端圈内保持领先的原因之一,因为它的学习曲线非常平易近人:把你的模板包在JSX,了解一下props和state的概念之后,你就可以轻松写出React代码了。

如果你已经熟悉React的工作方式,可以直接跳至“优化我的代码”篇。

但要真正掌握React,你需要像React一样思考(think in React)。本文也会试图在这个方面帮助你。

下面看看我们其中一个项目中的React table:

eBay上的一个复杂的React表格(用于业务)
这个表里有数百个动态(表格内容变化)和可过滤的选项,理解这个框架更精细的点,对于保证顺畅的用户体验至关重要。

当事情出错时,你一定能感觉到。输入字段变得迟缓,复选框需要检查一秒钟,弹窗一个世纪后才出现,等等。

为了能够解决这些问题,我们需要完成一个React组件的整个生命旅程,从一开始的声明定义到在页面上渲染(再然后可能会更新)。系好安全带,我们要发车了!

JSX的背后
这个过程一般在前端会称为“转译”,但其实“汇编”将是一个更精确的术语。

React开发人员敦促你在编写组件时使用一种称为JSX的语法,混合了HTML和JavaScript。但浏览器对JSX及其语法毫无头绪,浏览器只能理解纯碎的JavaScript,所以JSX必须转换成JavaScript。这里是一个div的JSX代码,它有一个class name和一些内容:

Content!
以上的代码,被转换成“正经”的JavaScript代码,其实是一个带有一些参数的函数调用:

React.createElement('div',{className:'cn'},'Content!');
让我们仔细看看这些参数。

第一个是元素的type。对于HTML标签,它将是一个带有标签名称的字符串。

第二个参数是一个包含所有元素属性(attributes)的对象。如果没有,它也可以是空的对象。

剩下的参数都可以认为是元素的子元素(children)。元素中的文本也算作一个child,是个字符串’Content!’ 作为函数调用的第三个参数放置。

你应该可以想象,当我们有更多的children时会发生什么:

Content 1!
Content 2!
React.createElement( 'div', { className: 'cn' }, 'Content 1!', // 1st child React.createElement('br'), // 2nd child 'Content 2!' // 3rd child ) 我们的函数现在有五个参数:

一个元素的类型

一个属性对象

三个子元素。

因为其中一个child是一个React已知的HTML标签(
),所以它也会被描述为一个函数调用(React.createElement('br'))。

到目前为止,我们已经涵盖了两种类型的children:

简单的String

另一种会调用React.createElement。

然而,还有其他值可以作为参数:

基本类型 false, null, undefined, true

数组

React Components

可以使用数组是因为可以将children分组并作为一个参数传递:

React.createElement(
'div',
{ className: 'cn' },
['Content 1!', React.createElement('br'), 'Content 2!']
)
当然了,React的厉害之处,不仅仅因为我们可以把HTML标签直接放在JSX中使用,而是我们可以自定义自己的组件,例如:

function Table({ rows }) {
return (


{rows.map(row => (



))}
{row.title}

);
}
组件可以让我们把模板分解为多个可重用的块。在上面的“函数式”(functional)组件的例子里,我们接收一个包含表格行数据的对象数组,最后返回一个调用React.createElement方法的元素,rows则作为children传进table。

无论什么时候,我们这样去声明一个组件时:

从浏览器的角度来看,我们是这么写的:

React.createElement(Table,{rows:rows});
注意,这次我们的第一个参数不是String描述的HTML标签,而是一个引用,指向我们编写组件时编写的函数。组件的attributes现在是接收的props参数了。

把组件(components)组合成页面(page)
所以,我们已经将所有JSX组件转换为纯JavaScript,现在我们有一大堆函数调用,它的参数会被其他函数调用的,或者还有更多的其他函数调用这些参数……这些带参数的函数调用,是怎么转化成组成这个页面的实体DOM的呢?

为此,我们有一个ReactDOM库及其它的render方法:

function Table({ rows }) { /* ... */ } // defining a component
// rendering a component
ReactDOM.render(
React.createElement(Table, { rows: rows }), // "creating" a component
document.getElementById('#root') // inserting it on a page
);
当ReactDOM.render被调用时,React.createElement最终也会被调用,返回以下对象:

// There are more fields, but these are most important to us
{
type: Table,
props: {
rows: rows
},
// ...
}
这些对象,在React的角度上,构成了虚拟DOM。

他们将在所有进一步的渲染中相互比较,并最终转化为 真正的DOM(virtual VS real, 虚拟DOM VS 真实DOM)。

下面是另一个例子:这次div有一个class属性和几个children:

React.createElement(
'div',
{ className: 'cn' },
'Content 1!',
'Content 2!',
);
变成:

{
type: 'div',
props: {
className: 'cn',
children: [
'Content 1!',
'Content 2!'
]
}
}
需要注意的是,那些除了type和attribute以外的属性,原本是单独传进来的,转换之后,会作为在props.children以一个数组的形式打包存在。也就是说,无论children是作为数组还是参数列表传递都没关系 —— 在生成的虚拟DOM对象的时候,它们最后都会被打包在一起的。

进一步说,我们可以直接在组件中把children作为一项属性传进去,结果还是一样的:

在构建虚拟DOM对象完成之后,ReactDOM.render将会按下面的原则,尝试将其转换为浏览器可以识别和展示的DOM节点:

如果type包含一个带有String类型的标签名称(tag name)—— 创建一个标签,附带上props下所有attributes。

如果type是一个函数(function)或者类(class),调用它,并对结果递归地重复这个过程。

如果props下有children属性 —— 在父节点下,针对每个child重复以上过程。

最后,得到以下HTML(对于我们的表格示例):

...
Title
重新构建DOM(Rebuilding the DOM) 在实际应用场景,render通常在根节点调用一次,后续的更新会有state来控制和触发调用。

请注意,标题中的“重新”!当我们想更新一个页面而不是全部替换时,React中的魔法就开始了。我们有一些实现它的方式。我们先从最简单的开始 —— 在同一个node节点再次执行ReactDOM.render。

// Second call
ReactDOM.render(
React.createElement(Table, { rows: rows }),
document.getElementById('#root')
);
这一次,上面的代码的表现,跟我们已经看到的有所不同。React将从头开始创建所有DOM节点并将其放在页面上,而不是从头开始创建所有DOM节点,React将启动其diff算法,来确定节点树的哪些部分必须更新,哪些可以保持不变。

那么,它是怎样工作的呢?其实只有少数几个简单的场景,理解它们将对我们的优化帮助很大。请记住,现在我们在看的,是在React Virtual DOM里面用来代表节点的对象。

场景1:type是一个字符串,type在通话中保持不变,props也没有改变。
// before update
{ type: 'div', props: { className: 'cn' } }
// after update
{ type: 'div', props: { className: 'cn' } }
这是最简单的情况:DOM保持不变。

场景2:type仍然是相同的字符串,props是不同的。
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'div', props: { className: 'cnn' } }
type仍然代表HTML元素,React知道如何通过标准DOM API调用来更改元素的属性,而无需从DOM树中删除一个节点。

场景3:type已更改为不同的String或从String组件。
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'span', props: { className: 'cn' } }
React看到的type是不同的,它甚至不会尝试更新我们的节点:old元素将和它的所有子节点一起被删除(unmounted卸载)。因此,将元素替换为完全不同于DOM树的东西代价会非常昂贵。幸运的是,这在现实世界中很少发生。

划重点,记住React使用===(triple equals)来比较type的值,所以这两个值需要是相同类或相同函数的相同实例。

下一个场景更加有趣,通常我们会这么使用React。

场景4:type是一个component。
// before update:
{ type: Table, props: { rows: rows } }
// after update:
{ type: Table, props: { rows: rows } }
你可能会说,“咦,但没有任何变化啊!”,但是你错了。

如果type是对函数或类的引用(即常规的React组件),并且我们启动了tree diff的过程,则React会持续地去检查组件的内部逻辑,以确保render返回的值不会改变(类似对副作用的预防措施)。对树中的每个组件进行遍历和扫描 —— 是的,在复杂的渲染场景下,成本可能会非常昂贵!

值得注意的是,一个component的render(只有类组件在声明时有这个函数)跟ReactDom.render不是同一个函数。

关注子组件(children)的情况
除了上述四种常见场景之外,当一个元素有多个子元素时,我们还需要考虑React的行为。现在假设我们有这么一个元素:

// ...
props: {
children: [
{ type: 'div' },
{ type: 'span' },
{ type: 'br' }
]
},
// ...
我们想要交换一下这些children的顺序:

// ...
props: {
children: [
{ type: 'span' },
{ type: 'div' },
{ type: 'br' }
]
},
// ...
之后会发生什么呢?

当diffing的时候,如果React在检查props.children下的数组时,按顺序去对比数组内元素的话:index 0将与index 0进行比较,index 1和index 1,等等。对于每一次对比,React会使用之前提过的diff规则。在我们的例子里,它认为div成为一个span,那么就会运用到情景3。这样不是很有效率的:想象一下,我们已经从1000行中删除了第一行。React将不得不“更新”剩余的999个子项,因为按index去对比的话,内容从第一条开始就不相同了。

幸运的是,React有一个内置的方法(built-in)来解决这个问题。如果一个元素有一个key属性,那么元素将按key而不是index来比较。只要key是唯一的,React就会移动元素,而不是将它们从DOM树中移除然后再将它们放回(这个过程在React里叫mounting和unmounting)。

// ...
props: {
children: [ // Now React will look on key, not index
{ type: 'div', key: 'div' },
{ type: 'span', key: 'span' },
{ type: 'br', key: 'bt' }
]
},
// ...
当state发生了改变
到目前为止,我们只聊了下React哲学里面的props部分,却忽视了另外很重要的一部分state。下面是一个简单的stateful组件:

class App extends Component {
state = { counter: 0 }

increment = () => this.setState({
counter: this.state.counter + 1,
})

render = () => (
{'Counter: ' + this.state.counter}
)
}
在state对象里,我们有一个keycounter。点击按钮时,这个值会增加,然后按钮的文本也会发生相应的改变。但是,当我们这样做时,DOM中发生了什么?哪部分将被重新计算和更新?

调用this.setState会导致re-render(重新渲染),但不会影响到整个页面,而只会影响组件本身及其children组件。父母和兄弟姐妹都不会受到影响。当我们有一个层级很深的组件链时,这会让状态更新变得非常方便,因为我们只需要重绘(redraw)它的一部分。

把问题说清楚
我们准备了一个小demo,以便你可以在看到在“野蛮生长”的React编码方式下最常见的问题,后续我也告诉大家怎么去解决这些问题。你可以在这里看看它的源代码。你还需要React Developer Tools,请确保浏览器安装了它们。

我们首先要看看的是,哪些元素以及什么时候导致Virtual DOM的更新。在浏览器的开发工具中,打开React面板并选择“Highlight Updates”复选框:

在Chrome中使用“突出显示更新”复选框选中DevTools
现在尝试在表格中添加一行。如你所见,页面上的每个元素周围都会显示一个边框。这意味着每次添加一行时,React都在计算和比较整个虚拟DOM树。现在尝试点击一行内的counter按钮。你将看到state更新后虚拟DOM如何更新 —— 只有引用了state key的元素及其children受到影响。

React DevTools会提示问题出在哪里,但不会告诉我们有关细节的信息:特别是所涉及的更新,是由diffing元素引起的?还是被挂载(mounting)或者被卸载(unmounting)了?要了解更多信息,我们需要使用React的内置分析器(注意它不适用于生产模式)。

添加?react_perf到应用的URL,然后转到Chrome DevTools中的“Performance”标签。点击“录制”(Record)并在表格上点击。添加一些row,更改一下counter,然后点击“停止”(Stop)。

React DevTools的“Performance”选项卡
在输出的结果中,我们关注“User timing”这项指标。放大时间轴直到看到“React Tree Reconciliation”这个组及其子项。这些就是我们组件的名称,它们旁边都写着[update]或[mount]。

我们的大部分性能问题都属于这两类问题之一。

无论是组件(还是从它分支的其他组件)出于某种原因都会在每次更新时re-mounted(慢),又或者我们在大型应用上执行对每个分支做diff,尽管这些组件并没有发生改变,我们不希望这些情况的发生。

优化我们的代码:Mounting / Unmounting
现在,我们已经了解到当需要update Virtual Dom时,React是依据哪些规则去判断要不要更新,以及也知道了我们可以通过什么方式去追踪这些diff场景的背后发生了什么,我们终于准备好优化我们的代码了!首先,我们来看看mounts/unmounts。

如果你能够注意到当一个元素包含的多个children,他们是由array组成的话,你可以实现十分显著的速度优化。

我们来看看这个case:

在我们的Virtual DOM里这么表示:

// ...
props: {
children: [
{ type: Message },
{ type: Table },
{ type: Footer }
]
}
// ...
这里有一个简单的Message例子,就是一个div写着一些简单的文本,和以及一个巨大的Table,比方说,超过1000行。它们(Message和Table)都是顶级div的子组件,所以它们被放置在父节点的props.children下,并且它们key都不会有。React甚至不会通过控制台警告我们要给每个child分配key,因为children正在React.createElement作为参数列表传递给父元素,而不是直接遍历一个数组。

现在我们的用户已读了一个通知,Message(譬如新通知按钮)从DOM上移除。Table和Footer是剩下的全部。

// ...
props: {
children: [
{ type: Table },
{ type: Footer }
]
}
// ...
React会怎么处理呢?它会看作是一个array类型的children,现在少了第一项,从前第一项是Message现在是Table了,也没有key作为索引,比较type的时候又发现它们俩不是同一个function或者class的同一个实例,于是会把整个Tableunmount,然后在mount回去,渲染它的1000+行子数据。

因此,你可以给每个component添加唯一的key(但在目特殊的case下,使用key并不是最佳选择),或者采用更聪明的小技巧:使用短路求值(又名“最小化求值”),这是JavaScript和许多其他现代语言的特性。看:

// Using a boolean trick

{isShown && }
虽然Message会离开屏幕,父元素div的props.children仍然会拥有三个元素,children[0]具有一个值false(一个布尔值)。请记住true, false, null, undefined是虚拟DOM对象type属性的允许值,我们最终得到了类似的结果:

// ...
props: {
children: [
false, // isShown && evaluates to false
{ type: Table },
{ type: Footer }
]
}
// ...
因此,有没有Message组件,我们的索引值都不会改变,Table当然仍然会跟Table比较(当type是一个函数或类的引用时,diff比较的成本还是会有的),但仅仅比较虚拟DOM的成本,通常比“删除DOM节点”并“从0开始创建”它们要来得快。

现在我们来看看更多的东西。大家都挺喜欢用HOC的,高阶组件是一个将组件作为参数,执行某些操作,最后返回另外一个不同功能的组件:

function withName(SomeComponent) {
// Computing name, possibly expensive...
return function(props) {
return <SomeComponent {...props} name={name} />;
}
}
这是一种常见的模式,但你需要小心。如果我们这么写:

class App extends React.Component() {
render() {
// Creates a new instance on each render
const ComponentWithName = withName(SomeComponent);
return ;
}
}
我们在父节点的render方法内部创建一个HOC。当我们重新渲染(re-render)树时,虚拟DOM是这样子的:

// On first render:
{
type: ComponentWithName,
props: {},
}
// On second render:
{
type: ComponentWithName, // Same name, but different instance
props: {},
}
现在,React会对ComponentWithName这个实例做diff,但由于此时同名引用了不同的实例,因此全等比较(triple equal)失败,一个完整的re-mount会发生(整个节点换掉),而不是调整属性值或顺序。注意它也会导致状态丢失,如此处所述。幸运的是,这很容易解决,你需要始终在render外面创建一个HOC:

// Creates a new instance just once
const ComponentWithName = withName(Component);
class App extends React.Component() {
render() {
return ;
}
}
优化我的代码:Updating
现在我们可以确保在非必要的时候,不做re-mount的事情了。然而,对位于DOM树根部附近(层级越上面的元素)的组件所做的任何更改都会导致其所有children的diffing和调整(reconciliation)。在层级很多、结构复杂的应用里,这些成本很昂贵,但经常是可以避免的。

如果有一种方法可以告诉React你不用来检查这个分支了,因为我们可以肯定那个分支不会有更新,那就太棒了!

这种方式是真的有的哈,它涉及一个built-in方法叫shouldComponentUpdate,它也是组件生命周期的一部分。这个方法的调用时机:组件的render和组件接收到state或props的值的更新时。然后我们可以自由地将它们与我们当前的值进行比较,并决定是否更新我们的组件(返回true或false)。如果我们返回false,React将不会重新渲染组件,也不会检查它的所有子组件。

通常来说,比较两个集合(set)props和state一个简单的浅层比较(shallow comparison)就足够了:如果顶层的值不同,我们不必接着比较了。浅比较不是JavaScript的一个特性,但有很多小而美的库(utilities)可以让我们用上那么棒的功能。

现在可以像这样编写我们的代码:

class TableRow extends React.Component {

// will return true if new props/state are different from old ones
shouldComponentUpdate(nextProps, nextState) {
const { props, state } = this;
return !shallowequal(props, nextProps)
&& !shallowequal(state, nextState);
}

render() { /* ... */ }
}
但是你甚至都不需要自己写代码,因为React把这个特性内置在一个类React.PureComponent里面。它类似于 React.Component,只是shouldComponentUpdate已经为你实施了一个浅的props/state比较。

这听起来很“不动脑”,在声明class继承(extends)的时候,把Component换成PureComponent就可以享受高效率。事实上,并不是这么“傻瓜”,看看这些例子:

{ /* ... */ }} /> 上面的代码片段演示了三种最常见的反模式。尽量避免它们!

如果你能注意点,在render定义之外创建所有对象、数组和函数,并确保它们在各种调用间,不发生更改 —— 你是安全的。

你在updated demo,所有table的rows都被“净化”(purified)过,你可以看到PureComponent的表现了。如果你在React DevTools中打开“Highlight Updates”,你会注意到只有表格本身和新行在插入时会触发render,其他的行保持不变。

[译者说:为了便于大家理解purified,译者在下面插入了原文demo的一段代码]

class TableRow extends React.PureComponent {
render() {
return React.createElement('tr', { className: 'row' },
React.createElement('td', { className: 'cell' }, this.props.title),
React.createElement('td', { className: 'cell' }, React.createElement(Button)),
);
}
};
不过,如果你迫不及待地all in PureComponent,在应用里到处都用的话 —— 控制住你自己!

shallow比较两组props和state不是免费的,对于大多数基本组件来说,甚至都不值得:shallowCompare比diffing算法需要耗费更多的时间。

使用这个经验法则:pure component适用于复杂的表单和表格,但它们通常会减慢简单元素(按钮、图标)的效率。

感谢你的阅读!现在你已准备好将这些见解应用到你的应用程序中。可以使用我们的小demo(用了或没有用PureComponent)的仓库作为你的实验的起点。此外,请继续关注本系列的下一部分,我们计划涵盖Redux并优化你的数据,目标是提高整个应用的总体性能。

redux middleware 详细介绍

https://zhuanlan.zhihu.com/p/20597452

redux middleware 详解
前言

It provides a third-party extension point between dispatching an
action, and the moment it reaches the reducer.

这是 redux 作者 Dan 对 middleware 的描述,middleware 提供了一个分类处理 action 的机会,在 middleware 中你可以检阅每一个流过的 action,挑选出特定类型的 action 进行相应操作,给你一次改变 action 的机会。

为什么 dispatch 需要 middleware

上图表达的是 redux 中一个简单的同步数据流动的场景,点击 button 后,在回调中 dispatch 一个 action,reducer 收到 action 后,更新 state 并通知 view 重新渲染。单向数据流,看着没什么问题。但是,如果需要打印每一个 action 信息用来调试,就得去改 dispatch 或者 reducer 代码,使其具有打印日志的功能;又比如点击 button 后,需要先去服务器请求数据,只有等拿到数据后,才能重新渲染 view,此时我们又希望 dispatch 或者 reducer 拥有异步请求的功能;再比如需要异步请求完数据后,打印一条日志,再请求数据,再打印日志,再渲染...

面对多种多样的业务需求,单纯的修改 dispatch 或 reducer 的代码显然不具有普世性,我们需要的是可以组合的,自由插拔的插件机制,这一点 redux 借鉴了 koa 里中间件的**,koa 是用于构建 web 应用的 NodeJS 框架。另外 reducer 更关心的是数据的转化逻辑,所以 redux 的 middleware 是为了增强 dispatch 而出现的。

上面这张图展示了应用 middleware 后 redux 处理事件的逻辑,每一个 middleware 处理一个相对独立的业务需求,通过串联不同的 middleware,实现变化多样的的功能。那么问题来了:

middleware 怎么写?

redux 是如何让 middlewares 串联并跑起来的?

四步理解 middleware 机制

redux 提供了 applyMiddleware 这个 api 来加载 middleware,为了方便理解,下图将两者的源码放在一起进行分析。

图下边是 logger,打印 action 的 middleware,图上边则是 applyMiddleware 的源码,applyMiddleware 代码虽然只有二十多行,却非常精炼,接下来我们就分四步来深入解析这张图。

redux 的代码都是用 ES6/7 写的,所以不熟悉诸如 store => next => action => 或 ...state 的童鞋,可以先学习下箭头函数,展开运算符。

Step. 1 函数式编程**设计 middleware

middleware 的设计有点特殊,是一个层层包裹的匿名函数,这其实是函数式编程中的柯里化 curry,一种使用匿名单参数函数来实现多参数函数的方法。applyMiddleware 会对 logger 这个 middleware 进行层层调用,动态地对 store 和 next 参数赋值。

柯里化的 middleware 结构好处在于:

易串联,柯里化函数具有延迟执行的特性,通过不断柯里化形成的 middleware 可以累积参数,配合组合( compose,函数式编程的概念,Step. 2 中会介绍)的方式,很容易形成 pipeline 来处理数据流。

共享store,在 applyMiddleware 执行过程中,store 还是旧的,但是因为闭包的存在,applyMiddleware 完成后,所有的 middlewares 内部拿到的 store 是最新且相同的。

另外,我们可以发现 applyMiddleware 的结构也是一个多层柯里化的函数,借助 compose , applyMiddleware 可以用来和其他插件一起加强 createStore 函数。

import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers';
import DevTools from '../containers/DevTools';

const finalCreateStore = compose(
// Middleware you want to use in development:
applyMiddleware(d1, d2, d3),
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument()
)(createStore);
Step. 2 给 middleware 分发 store

创建一个普通的 store 通过如下方式:

let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
上面代码执行完后,applyMiddleware 函数陆续获得了三个参数,第一个是 middlewares 数组,[mid1, mid2, mid3, ...],第二个 next 是 Redux 原生的 createStore,最后一个是 reducer。我们从对比图中可以看到,applyMiddleware 利用 createStore 和 reducer 创建了一个 store,然后 store 的 getState 方法和 dispatch 方法又分别被直接和间接地赋值给 middlewareAPI 变量,middlewareAPI 就是对比图中红色箭头所指向的函数的入参 store。

var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
map 方法让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍,即执行红色箭头指向的函数。执行完后,获得 chain 数组,[f1, f2, ... , fx, ...,fn],它保存的对象是图中绿色箭头指向的匿名函数,因为闭包,每个匿名函数都可以访问相同的 store,即 middlewareAPI。

备注: middlewareAPI 中的 dispatch 为什么要用匿名函数包裹呢?

我们用 applyMiddleware 是为了改造 dispatch 的,所以 applyMiddleware 执行完后,dispatch 是变化了的,而 middlewareAPI 是 applyMiddleware 执行中分发到各个 middleware,所以必须用匿名函数包裹 dispatch, 这样只要 dispatch 更新了, middlewareAPI 中的 dispatch 应用也会发生变化。

Step. 3 组合串联 middlewares

dispatch = compose(...chain)(store.dispatch);
这一层只有一行代码,但却是 applyMiddleware 精华所在。compose 是函数式编程中的组合,compose 将 chain 中的所有匿名函数,[f1, f2, ... , fx, ..., fn],组装成一个新的函数,即新的 dispatch,当新 dispatch 执行时,[f1, f2, ... , fx, ..., fn],从左到右依次执行( 所以顺序很重要)。Redux 中 compose 的实现是下面这样的,当然实现方式不唯一。

function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
compose(...chain) 返回的是一个匿名函数,函数里的 funcs 就是 chain 数组,当调用 reduceRight 时,依次从 funcs 数组的右端取一个函数 fx 拿来执行,fx 的参数 composed 就是前一次 fx+1 执行的结果,而第一次执行的fn(n代表chain的长度)的参数 arg 就是 store.dispatch。所以当 compose 执行完后,我们得到的 dispatch 是这样的,假设 n = 3。

dispatch = f1(f2(f3(store.dispatch))))
这个时候调用新 dispatch,每个 middleware 的代码不就依次执行了嘛。

Step. 4 在 middleware 中调用 dispatch 会发生什么

经过 compose,所有的 middleware 算是串联起来了,可是还有一个问题,我们有必要挖一挖。在 step 2 时,提到过每个 middleware 都可以访问 store,即 middlewareAPI 这个变量,所以就可以拿到 store 的 dispatch 方法,那么在 middleware 中调用 store.dispatch()会发生什么,和调用 next() 有区别吗?比如下图:

在 step 2 的时候我们解释过,通过匿名函数的方式,middleware 中 拿到的 dispatch 和最终 compose 结束后的新 dispatch 是保持一致的,所以在middleware 中调用 store.dispatch() 和在其他任何地方调用效果是一样的,而在 middleware 中调用 next(),效果是进入下一个 middleware。下面这张图说明一切。

正常情况下,如图左,当我们 dispatch 一个 action 时,middleware 通过 next(action) 一层一层处理和传递 action 直到 redux 原生的 dispatch。如果某个 middleware 使用 store.dispatch(action) 来分发 action,就发生了右图的情况,相当于从外层重新来一遍,假如这个 middleware 一直简单粗暴地调用 store.dispatch(action),就会形成无限循环了。那么 store.dispatch(action) 的勇武之地在哪里?正确的使用姿势应该是怎么样的?

举个例子,需要发送一个异步请求到服务器获取数据,成功后弹出一个自定义的 Message。这里我门用到了 redux-thunk 这个作者写的 middleware。

const thunk = store => next => action =>
typeof action === 'function' ?
action(store.dispatch, store.getState) :
next(action)
redux-thunk 做的事情就是判断 action 类型是否是函数,若是,则执行 action,若不是,则继续传递 action 到下个 middleware。

针对上面的需求,我们设计了下面的 action:

const getThenShow = (dispatch, getState) => {
const url = 'http://xxx.json';

fetch(url)
.then(response => {
dispatch({
type: 'SHOW_MESSAGE_FOR_ME',
message: response.json(),
});
}, e => {
dispatch({
type: 'FETCH_DATA_FAIL',
message: e,
});
});
};
这个时候只要在业务代码里面调用 store.dispatch(getThenShow),redux-thunk 就会拦截并执行 getThenShow 这个 action,getThenShow 会先请求数据,如果成功,dispatch 一个显示 Message 的 action,否则 dispatch 一个请求失败的 action。这里的 dispatch 就是通过 redux-thunk middleware 传递进来的。

在 middleware 中使用 dispatch 的场景一般是:
接受到一个定向 action,这个 action 并不希望到达原生的 dsipatch,存在的目的是为了触发其他新的 action,往往用在异步请求的需求里。

总结

applyMiddleware 机制的核心在于组合 compose,将不同的 middlewares 一层一层包裹到原生的 dispatch 之上,而为了方便进行 compose,需对 middleware 的设计采用柯里化 curry 的方式,达到动态产生 next 方法以及保持 store 的一致性。由于在 middleware 中,可以像在外部一样轻松访问到 store, 因此可以利用当前 store 的 state 来进行条件判断,用 dispatch 方法拦截老的 action 或发送新的 action。

参考

Understanding Redux Middleware

Redux Middleware: Behind the Scene

mobx简介

MobX 是一个简单、方便扩展、久经考验的状态管理解决方案。这个教程旨在十分钟内向你介绍 MobX 的一些重要概念。MobX 是一个独立的苦,不过大多数人都把它和 React 一起使用,所以本教程也就着眼于这个组合展开。

核心概念
State 是每一个应用程序的核心部分,而使用一个不合规范的 State 则是让你的应用充满 bug 和失控的不二法门,或者就是局部变量环绕,让你的 state 失去了同步。有很多框架试图解决这个问题,比如使用不可变的 state,但是这样以来又带来了新的问题,比如数据必须规格化,完整性约束失效等等。

MobX 让整个事情又变简单了:它不允许产生失控的 state。它的理念也很简单:所有可以从 state 中派生的事物,都会自动的派生。

把 MobX 想象成 Excel 表格。

首先,有一个 state,它可以是一个object,array,primitives等等任何组成你程序的部分。你可以把这个想象成你应用程序的“单元格”。
然后就是 derivations,一般它是指可以从 state 中直接计算的来的结果。比如未完成的任务的数量,这个比较简单,也可以稍复杂一些比如渲染你的任务显示的html。它类似于你的应用程序中的“公式和图表”。
Reactions 和 derivations 很像,主要的区别在于 reactions 并不产生数据结果,而是自动完成一些任务,一般是和 I/O 相关的。他们保证了 DOM 和 网络请求会自动适时地出发。
最后是 actions。Actions 指的是所有会改变 state 的事情,MobX 保证所有 actions 都会有对应的 derivations 和 reactions 相伴,保证同步。
一个简单的 todo 的 state
理论说的够多的了,看一个例子也许会更明白一些。我们从一个简单的 todo 程序开始。

下面是一个简单直接的 TodoStore,没有鱼丸,没有粗面,没有 MobX ……

class TodoStore {
	todos = [];

	get completedTodosCount() {
    	        return this.todos.filter(
			todo => todo.completed === true
		).length;
       }

	report() {
		if (this.todos.length === 0)
			return "<none>";
		return `Next todo: "${this.todos[0].task}". ` + 
			`Progress: ${this.completedTodosCount}/${this.todos.length}`; 
	}

        addTodo(task) {
		this.todos.push({ 
			task: task,
			completed: false,
                        assignee: null
		});
	}
}

const todoStore = new TodoStore();

我们创建了一个 todoStore,它拥有一个 todos 集合。现在我们往这个 todoStore 里添加一些东西,为了明显起见,我们每修改一个地方,就调用todoStore.report。

todoStore.addTodo("read MobX tutorial");
console.log(todoStore.report());todoStore.addTodo("try MobX");
console.log(todoStore.report());todoStore.todos[0].completed = true;
console.log(todoStore.report());todoStore.todos[1].task = "try MobX in own project";
console.log(todoStore.report());todoStore.todos[0].task = "grok MobX tutorial";
console.log(todoStore.report());

到现在为止,没有什么特别的。不过如果我们可以不再手动调用 report 方法,事情会不会更美好一些?我们只需要在想要的地方修改这个 state,所有的汇报都自动来做。

太巧了,这就是 MobX 能为你做的事情。自动执行只在 state 改变的时候触发,就好像 Excel 中的图表只在单元格数据改变时更新一样。为了达到这个目标,TodoStore 必须成为可观测的(observable)才行,让我们来改一些代码。

同时,completedTodosCount 属性应该被自动派生,使用 @observable@computed 装饰器来做这些事情:

class ObservableTodoStore {
    @observable todos = [];
    @observable pendingRequests = 0;
 
    constructor() {
        mobx.autorun(() => console.log(this.report));
    }
 
    @computed get completedTodosCount() {
    	return this.todos.filter(
			todo => todo.completed === true
		).length;
    }
 
    @computed get report() {
        if (this.todos.length === 0)
            return "<none>";
	return `Next todo: "${this.todos[0].task}". ` + 
	    `Progress: ${this.completedTodosCount}/${this.todos.length}`; 
	}
 
    addTodo(task) {
	this.todos.push({ 
	    task: task,
	    completed: false,
	    assignee: null
	});
    }
}
 
const observableTodoStore = new ObservableTodoStore();

运行它,太棒了,我们每次赋值都能获得输出结果了。

有个 pendingRequests 暂时没用到,我们后面会用。另外这个教程都用了 ES6 的写法,不过 MobX 也支持 ES5 的写法。

在这个构造器中,我们使用autorun包裹了一个打出report的小函数。Autorun里的东西首先会运行一次,然后当其中的函数有observable的数据发生变化时,会再次运行。 这里我们使用了todos属性,每次todos变化了我们就打印出新的东西。

observableTodoStore.addTodo("read MobX tutorial");
observableTodoStore.addTodo("try MobX");
observableTodoStore.todos[0].completed = true;
observableTodoStore.todos[1].task = "try MobX in own project";
observableTodoStore.todos[0].task = "grok MobX tutorial";

(可以自己试试结果)

非常有趣是吧,report确实自己执行了,而且同步又精准。如果你仔细查看运行结果的话,你挥发性我们的第四句语句没有产生输出,因为我们修改了todos[1]的数据,而我们在report中指明的数据,并没有todos[1]的变化而发生变化。而第五句话修改了todos[0]的数据则输出了。这个例子很好的说明了,autorun不是简单的监视了todos,而是精确到了具体的一项。

让React更美好
好了,到目前未知,我们使report自动化了,是实话把react拉出来遛遛了。为了是的react 的组件可以识别mobx,我们需要使用mobx-react包来完成,使用autorun,自动的让组件和state同步,这个简直就和上面的让report自动输出一样简单。

下面是一个react 组件,唯一MobX出场的地方就是一个@observer修饰符,这已经足够了,你再也不用使用setState了,你也不需要指明这个组件需要关注state的哪个部分,也不许手动写什么高阶组件。一般来说,所有的部件都变成人工智能了,即使他被定义成一个木偶(纯展示)组件。

@observer
class TodoList extends React.Component {
  render() {
    const store = this.props.store;
    return (
      <div>
        { store.report }
        <ul>
          { store.todos.map(
            (todo, idx) => <TodoView todo={ todo } key={ idx } />
          ) }
        </ul>
        { store.pendingRequests > 0 ? <marquee>Loading...</marquee> : null }
        <button onClick={ this.onNewTodo }>New Todo</button>
        <small> (double-click a todo to edit)</small>
      <RenderCounter />
    </div>
    );
  }onNewTodo = () => {
    this.props.store.addTodo(prompt('Enter a new todo:','coffee plz'));
  }
}
​
@observer
class TodoView extends React.Component {
  render() {
    const todo = this.props.todo;
    return (
      <li onDoubleClick={ this.onRename }>
        <input
          type='checkbox'
          checked={ todo.completed }
          onChange={ this.onToggleCompleted }
        />
        { todo.task }
        { todo.assignee
          ? <small>{ todo.assignee.name }</small>
          : null
        }
      <RenderCounter />
    </li>
    );
  }onToggleCompleted = () => {
    const todo = this.props.todo;
    todo.completed = !todo.completed;
  }onRename = () => {
    const todo = this.props.todo;
    todo.task = prompt('Task name', todo.task) || todo.task;
  }
}ReactDOM.render(
  <TodoList store={ observableTodoStore } />,
  document.getElementById('reactjs-app')
);

执行下面的语句,我们会发现MobX帮我们把数据的更改反应到界面上去了。

store.todos[0].completed = !store.todos[0].completed;
store.todos[1].task = "Random todo " + Math.random();
store.todos.push({ task: "Find a fine cheese", completed: true });
// etc etc.. add your own statements here...

使用引用(References)

到现在位置,我们已经使用 observable 创建了个数据类型了。也许你会想,MobX 能不能应付引用呢?在之前的例子里,你可能主意到了又一个 assignee 的属性,我们就在这里放另外的一个 store,然后把它赋值给 tasks。

var peopleStore = mobx.observable([
  { name: "Michel" },
  { name: "Me" }
]);
observableTodoStore.todos[0].assignee = peopleStore[0];
observableTodoStore.todos[1].assignee = peopleStore[1];
peopleStore[0].name = "Michel Weststrate";

毫无疑问的,MobX 把着一切打理的井井有条。使用 MobX ,不需要规格话数据,不需要指明控件,事实上你的数据在哪里都无所谓。只要 observale 了,什么都好了。

总结
好了,仅仅依靠一些简单的修饰器,我们就让 react 程序如此生动有趣。最后总结一些:

@observale 修饰器或者 observable 函数让对象可以被追踪;
@computed 修饰器创造了自动运算的表达式;
autorun 函数让依靠 observable 的函数自动执行,这个用来写 log,发请求很不错;
@observer 修饰器让 React 组建自动起来,它会自动更新,即便是在一个很大的程序里也会工作的很好;

最后,MobX 不是一个状态容器

很多人把 MobX 当作另外一个 Redux,但是它仅仅是一个库,不是一个什么架构。上面的例子还是需要程序员自己去组织逻辑和store或者控制器什么的,正如有人在 HackerNews 上所说的:

React和Redux的连接react-redux

http://leozdgao.me/reacthe-reduxde-qiao-jie-react-redux/

之前一直在探索React相关的东西,手上有个SPA项目,于是准备上Redux试试水。Redux本身和React并没有之间的关联,它是一个通用Javscript App模块,用做App State的管理。要在React的项目中使用Redux,比较好的方式是借助react-redux这个库来做连接,这里的意思是,并不是没有react-redux,这两个库就不弄一起用了,而是说react-redux提供了一些封装,一种更科学的代码组织方式,让我们更舒服地在React的代码中使用Redux。

之前仅通过Redux文档来了解react-redux,在一段时间的实践后准备翻一翻源代码,顺便做些相关的总结。我看的代码的npm版本为v4.0.0,也就是说使用的React版本是0.14.x。

react-redux提供两个关键模块:Provider和connect。

Provider
Provider这个模块是作为整个App的容器,在你原有的App Container的基础上再包上一层,它的工作很简单,就是接受Redux的store作为props,并将其声明为context的属性之一,子组件可以在声明了contextTypes之后可以方便的通过this.context.store访问到store。不过我们的组件通常不需要这么做,将store放在context里,是为了给下面的connect用的。

这个是Provider的使用示例:

// config app root
const history = createHistory()
const root = (



)

// render
ReactDOM.render(
root,
document.getElementById('root')
)
connect
这个模块是算是真正意义上连接了Redux和React,正好它的名字也叫connect。

先考虑Redux是怎么运作的:首先store中维护了一个state,我们dispatch一个action,接下来reducer根据这个action更新state。

映射到我们的React应用中,store中维护的state就是我们的app state,一个React组件作为View层,做两件事:render和响应用户操作。于是connect就是将store中的必要数据作为props传递给React组件来render,并包装action creator用于在响应用户操作时dispatch一个action。

好了,详细看看connect这个模块做了什么。先从它的使用来说,它的API如下:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps是一个函数,返回值表示的是需要merge进props的state。默认值为() => ({}),即什么都不传。

(state, props) => ({ }) // 通常会省略第二个参数
mapDispatchToProps是可以是一个函数,返回值表示的是需要merge仅props的actionCreators,这里的actionCreator应该是已经被包装了dispatch了的,推荐使用redux的bindActionCreators函数。

(dispatch, props) => ({ // 通常会省略第二个参数
...bindActionCreators({
...ResourceActions
}, dispatch)
})
更方便的是可以直接接受一个对象,此时connect函数内部会将其转变为函数,这个函数和上面那个例子是一模一样的。

mergeProps用于自定义merge流程,下面这个是默认流程,parentProps值的就是组件自身的props,可以发现如果组件的props上出现同名,会被覆盖。

(stateProps, dispatchProps, parentProps) => ({
...parentProps,
...stateProps,
...dispatchProps
})
options共有两个开关:pure代表是否打开优化,详细内容下面会提,默认为true,withRef用来给包装在里面的组件一个ref,可以通过getWrappedInstance方法来获取这个ref,默认为false。

connect返回一个函数,它接受一个React组件的构造函数作为连接对象,最终返回连接好的组件构造函数。

然后几个问题:

React组件如何响应store的变化?
为什么connect选择性的merge一些props,而不是直接将整个state传入?
pure优化的是什么?
我们把connect返回的函数叫做Connector,它返回的是内部的一个叫Connect的组件,它在包装原有组件的基础上,还在内部监听了Redux的store的变化,为了让被它包装的组件可以响应store的变化:

trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(::this.handleChange)
this.handleChange()
}
}

handleChange () {
this.setState({
storeState: this.store.getState()
})
}
但是通常,我们connect的是某个Container组件,它并不承载所有App state,然而我们的handler是响应所有state变化的,于是我们需要优化的是:当storeState变化的时候,仅在我们真正依赖那部分state变化时,才重新render相应的React组件,那么什么是我们真正依赖的部分?就是通过mapStateToProps和mapDispatchToProps得到的。

具体优化的方式就是在shouldComponentUpdate中做检查,如果只有在组件自身的props改变,或者mapStateToProps的结果改变,或者是mapDispatchToProps的结果改变时shouldComponentUpdate才会返回true,检查的方式是进行shallowEqual的比较。

所以对于某个reducer来说:

export default (state = {}, action) => {
return { ...state } // 返回的是一个新的对象,可能会使组件reRender
// return state // 可能不会使得组件reRender
}
另外在connect的时候,要谨慎map真正需要的state或者actionCreators到props中,以避免不必要的性能损失。

最后,根据connect的API我们发现可以使用ES7 decorator功能来配合React ES6的写法:

@connect(
state => ({
user: state.user,
resource: state.resource
}),
dispatch => ({
...bindActionCreators({
loadResource: ResourceActions.load
}, dispatch)
})
)
export default class Main extends Component {

}
OK,结束了。

文/leozdgao(简书作者)
原文链接:http://www.jianshu.com/p/94c988cf11f3
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

redux项目架构,以及简单项目的实现(好文)!!

转载 原文地址 matthew-sun/blog#18
React是最好的前端库,因为其发源于世界上最好的后端语言框架。 ---信仰

4.0 will likely be the last major release. Use Redux instead. It's really great. —Flummox框架作者 acdliteAndrew Clark
为什么使用React还需要使用别的框架来搭配?

React的核心是使用组件定义界面的表现,是一个View层的前端库,那么在使用React的时候我们通常还需要一套机制去管理组件与组件之间,组件与数据模型之间的通信。

为什么使用Redux?

Facebook官方提出了FLUX**管理数据流,同时也给出了自己的实现来管理React应用。可是当我打开FLUX的文档时候,繁琐的实现,又臭又长的文档,实在难以让我有使用它的欲望。幸好,社区中和我有类似想法的不在少数,github上也涌现了一批关于实现FLUX的框架,比较出名的有Redux,Reflux,Flummox。

其中Redux的简单和有趣的编程体验是最吸引我的地方。

简单。和其它的FLUX实现不一样,Redux只有唯一的state树,不管项目变的有多复杂,我也仅仅只需要管理一个State树。可能你会有疑问,一个state树就够用了?这个state树该有多大?别着急,Redux中的Reducer机制可以解决这个问题。

有趣。忙于迭代项目的你,体会编程带来的趣味是有多久没有体会到了?瞧下面这张图,右边那个调试工具是啥?整个应用的action和state都这么被轻松的管理了?行为还能被保存,删除,回滚,重置?修改了代码,页面不刷新也能产生变化?别开玩笑了,不行,世界那么大,让我去试试!

Redux DevTools

注:Redux开发调试工具:redux-devtools
React应用无刷新保存工具:react-transform

不明真相的群众,可能这里需要我来安利一下Flux数据流的**,看图:

╔═════════╗ ╔════════╗ ╔═════════════════╗
║ Actions ║──────>║ Stores ║──────>║ View Components ║
╚═════════╝ ╚════════╝ ╚═════════════════╝
^ │
└──────────────────────────────────────┘

注意:图片仅仅是FLUX**,而不是Facebook的实现。
大致的过程是这样的,View层不能直接对state进行操作,而需要依赖Actions派发指令来告知Store修改状态,Store接收Actions指令后发生相应的改变,View层同时跟着Store的变化而变化。

举个例子:A组件要使B组件发生变化。首先,A组件需要执行一个Action,告知绑定B组件的Store发生变化,Store接收到派发的指令后改变,那相应的B组件的视图也就发生了改变。假如C,D,E,F组件绑定了和B组件相同的Store,那么C,D,E,F也会跟着变化。

使用React和Redux开发一个小程序

为了更好的描述怎么样使用Redux管理React应用,我做了一个Manage Items的小例子。你可以在这里找到全部的源代码:https://github.com/matthew-sun/redux-example

Manage Items

快速查看

1.git clone [email protected]:matthew-sun/redux-example.git

2.npm install && npm start

3.open localhost:3000

目录结构

.
+-- app
| +-- actions
| +-- index.js
| +-- components
| +-- content.js
| +-- footer.js
| +-- searchBar.js
| +-- constants
| +-- ActionTypes.js
| +-- containers
| +-- App.js
| +-- reducers
| +-- index.js
| +-- items.js
| +-- filter.js
| +-- utils
| +-- configureStore.js
| +-- index.js
+-- scss
| +-- pure.scss
+-- index.html
Index.js

在入口文件中,我们需要把App和redux建立起联系。Provider是react-redux提供的组件,它的作用是把store和视图绑定在了一起,这里的Store就是那个唯一的State树。当Store发生改变的时候,整个App就可以作出对应的变化。这里的会传进Provider的props.children里。

/* app/index.js */

import React from 'react';
import { Provider } from 'react-redux';
import App from './containers/App';
import configureStore from './configureStore';

const store = configureStore();

React.render(

    <div>
      <Provider store={store}>
        <App />
      </Provider>,
    </div>
```,
    document.getElementById('app'));
Constants

keyMirror这个方法非常的有用,它可以帮助我们轻松创建与键值key相等的常量。

/* app/constants/actionTypes.js */

import keyMirror from 'fbjs/lib/keyMirror';

export default keyMirror({
    ADD_ITEM: null,
    DELETE_ITEM: null,
    DELETE_ALL: null,
    FILTER_ITEM: null
});

// 等于
// export const ADD_ITEM = 'ADD_ITEM';
// export const DELETE_ITEM = 'DELETE_ITEM';
// export const DELETE_ALL = 'DELETE_ALL';
// export const FILTER_ITEM = 'FILTER_ITEM';
Actions

Action向store派发指令,action 函数会返回一个带有 type 属性的 Javascript Plain Object,store将会根据不同的action.type来执行相应的方法。addItem函数的异步操作我使用了一点小技巧,使用redux-thunk中间件去改变dispatch,dispatch是在View层中用bindActionCreators绑定的。使用这个改变的dispatch我们可以向store发送异步的指令。比如说,可以在action中放入向服务端的请求(ajax),也强烈推荐这样去做。

/* app/actions/index.js */

import { ADD_ITEM, DELETE_ITEM, DELETE_ALL, FILTER_ITEM } from '../constants/actionTypes';

export function addItem(item) {
    return dispatch => {
       setTimeout(() => dispatch({type: ADD_ITEM}), 1000)
    }
}
export function deleteItem(item, e) {
    return {
       type: DELETE_ITEM,
       item
    }
}
export function deleteAll() {
    return {
       type: DELETE_ALL
    }
}
export function filterItem(e) {
    let filterItem = e.target.value;
    return {
       type: FILTER_ITEM,
       filterItem
    }
}
Reducers

Redux有且只有一个State状态树,为了避免这个状态树变得越来越复杂,Redux通过 Reducers来负责管理整个应用的State树,而Reducers可以被分成一个个Reducer。

Reduce在javascript Array的方法中出现过,只是不太常用。简单快速的用代码样例来回顾一下:

  /* Array.prototype.reduce */

var arr = [1,2,3,4];
var initialValue = 5;
var result = arr.reduce(function(previousValue, currentValue) {
    return previousValue + currentValue
}, initialValue)
console.log(result)
// 15
// 该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。
// 整个函数执行的过程大致是这样 ((((5+1)+2)+3)+4)
回到Redux中来看,整个的状态就相当于从[初始状态]merge一个[action.state]从而得到一个新的状态,随着action的不断传入,不断的得到新的状态的过程。(previousState, action) => newState,注意:任何情况下都不要改变previousState,因为这样View层在比较State的改变时只需要简单比较即可,而避免了深度循环比较。Reducer的数据结构我们可以用immutable-js,这样我们在View层只需要react-immutable-render-mixin插件就可以轻松的跳过更新那些state没有发生改变的组件子树。

/* app/reducers/items.js */

import Immutable from 'immutable';
import { ADD_ITEM, DELETE_ITEM, DELETE_ALL } from '../constants/actionTypes';

const initialItems = Immutable.List([1,2,3]);

export default function items(state = initialItems, action) {
    switch(action.type) {
        case ADD_ITEM:
            return state.push( state.size !=0 ? state.get(-1)+1 : 1 );
        case DELETE_ITEM: 
            return state.delete( state.indexOf(action.item) );
        case DELETE_ALL:
            return state.clear();
        default:
            return state;
    }
}
连接reducers

Redux提供的combineReducers函数可以帮助我们把reducer组合在一起,这样我们就可以把Reducers拆分成一个个小的Reducer来管理Store了。

/* app/reducers/index.js */

import { combineReducers } from 'redux';
import items from './items';
import filter from './filter';

const rootReducer = combineReducers({
  items,
  filter
});

export default rootReducer;
Middleware

在Redux中,Middleware 主要是负责改变Store中的dispatch方法,从而能处理不同类型的 action 输入,得到最终的 Javascript Plain Object 形式的 action 对象。

以redux-thunk为例子:

/* redux-thunk */  
export default function thunkMiddleware({ dispatch, getState }) {
  return next => 
     action => 
       typeof action === ‘function’ ? 
         action(dispatch, getState) : 
         next(action);
}
当ThunkMiddleware 判断action传入的是一个函数,就会为该thunk函数补齐dispatch和getState参数,否则,就调用next(action),给后续的Middleware(Middleware 插件可以被绑定多个)得到使用dispatch的机会。

 /* app/configureStore.js */

import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

var buildStore = compose(applyMiddleware(thunk))(createStore);
export default function configureStore(initialState) {
  const store = buildStore(rootReducer, initialState);
  return store;
}
UI

智能组件和木偶组件,因为本文主要是介绍Redux,对这个感兴趣的同学可以看一下这篇文章Smart and Dumb Components。本项目中在结构上会把智能组件放在containers中,木偶组件放于components中。

containers

智能组件,会通过react-redux函数提供的connect函数把state和actions转换为旗下木偶组件所需要的props。

/* app/containers/App.js */

import React from 'react';
import SearchBar from '../components/searchBar';
import Content from '../components/content';
import Footer from '../components/footer';
import { connect } from 'react-redux';
import ImmutableRenderMixin from 'react-immutable-render-mixin';
import * as ItemsActions from '../actions';
import { bindActionCreators } from 'redux';

let App = React.createClass({
     mixins: [ImmutableRenderMixin],
     propTypes: {
         items: React.PropTypes.object,
         filter: React.PropTypes.string
     },
     render() {
         let styles = {
             width: '200px',
             margin: '30px auto 0'
         }
         const actions = this.props.actions;
         return (
             <div style={styles}>
                 <h2>Manage Items</h2>
                 <SearchBar filterItem={actions.filterItem}/>
                 <Content items={this.props.items} filter={this.props.filter} deleteItem={actions.deleteItem}/>
                 <Footer addItem={actions.addItem} deleteAll={actions.deleteAll}/>
             </div>
         )
     }
 })

export default connect(state => ({
     items: state.items,
     filter: state.filter
}), dispatch => ({
     actions: bindActionCreators(ItemsActions, dispatch)
}))(App);
components

木偶组件,各司其职,没有什么关于actions和stores的依赖,拿出项目中也可独立使用,甚至可以和别的actions,stores进行绑定。

SearchBar:查找Item。
Content:控制Items的显示,删除一个Item。
Footer:新增Item,删除全部Item。
调试工具

使用redux-devtools调试,为你在开发过程中带来乐趣。

/* app/index.js */

function renderDevTools(store) {
  if (__DEBUG__) {
    let {DevTools, DebugPanel, LogMonitor} = require('redux-devtools/lib/react');
    return (
      <DebugPanel top right bottom>
        <DevTools store={store} monitor={LogMonitor} />
      </DebugPanel>
    );
  }else {
    return null;
  }
}

React.render(
    <div>
        <Provider store={store}>
            <App />
        </Provider>
        {renderDevTools(store)}
    </div>,
  document.getElementById('app'));
/* app/configureStore.js */

import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

var buildStore;

if(__DEBUG__) {
    buildStore = compose(
      applyMiddleware(thunk),
      require('redux-devtools').devTools(),
      require('redux-devtools').persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
    )(createStore)
}else {
    buildStore = compose(applyMiddleware(thunk))(createStore)
}

export default function configureStore(initialState) {
  const store = buildStore(rootReducer, initialState);

  if(module.hot) {
    module.hot.accept('./reducers', () => {
      store.replaceReducer(require('./reducers'));
    });
  }

  return store;
}
在你的代码中加上上面的两段代码,运行npm run debug命令,就可以用调试工具来管理你的项目了。

延伸阅读

Redux Document
Awesome-redux

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.