设计思想
核心概念
- 所有的状态存放在
Store
。组件每次重新渲染,都必须由状态变化引起。 - 用户在 UI 上发出
action
。 reducer
函数接收action
,然后根据当前的state
,计算出新的state
。
动机
- 随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的
state
(状态)。通过限制更新发生的时间和方式,Redux 试图让state
的变化变得可预测。
生命周期
Redux 应用中数据的生命周期遵循下面 4 个步骤:
调用 store.dispatch(action)。
- Action 就是一个描述“发生了什么”的普通对象。比如:
{ type: 'LIKE_ARTICLE', articleId: 42 };
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } };
{ type: 'ADD_TODO', text: 'Read the Redux docs.'};
- 可以把
action
理解成新闻的摘要。如 “玛丽喜欢42号文章。” 或者 “任务列表里添加了'学习 Redux 文档'”。
你可以在任何地方调用store.dispatch(action)
,包括组件中、XHR 回调中、甚至定时器中。
Redux store 调用传入的 reducer 函数。
- Store 会把两个参数传入
reducer
: 当前的state
树和action
。例如,在这个todo
应用中,根reducer
可能接收这样的数据:
// 当前应用的 state(todos 列表和选中的过滤器)
let previousState = {
visibleTodoFilter: 'SHOW_ALL',
todos: [
{
text: 'Read the docs.',
complete: false
}
]
}
// 将要执行的 action(添加一个 todo)
let action = {
type: 'ADD_TODO',
text: 'Understand the flow.'
}
// render 返回处理后的应用状态
let nextState = todoApp(previousState, action);
- 注意 reducer 是纯函数。它仅仅用于计算下一个 state。它应该是完全可预测的:多次传入相同的输入必须产生相同的输出。它不应做有副作用的操作,如 API 调用或路由跳转。这些应该在
dispatch(action)
前发生。
根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
- 根
reducer
的结构完全由你决定。Redux 原生提供combineReducers()
辅助函数,来把根reducer
拆分成多个函数,用于分别处理state
树的一个分支。
-下面演示 combineReducers() 如何使用。假如你有两个 reducer:一个是 todo 列表,另一个是当前选择的过滤器设置:
function todos(state = [], action) {
// 省略处理逻辑...
return nextState;
}
function visibleTodoFilter(state = 'SHOW_ALL', action) {
// 省略处理逻辑...
return nextState;
}
let todoApp = combineReducers({
todos,
visibleTodoFilter
})
- 当你触发
action
后,combineReducers
返回的todoApp
会负责调用两个 reducer:
let nextTodos = todos(state.todos, action);
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action);
- 然后会把两个结果集合并成一个 state 树:
return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter
};
- 虽然
combineReducers()
是一个很方便的辅助工具,你也可以选择不用;你可以自行实现自己的根reducer
!
Redux store 保存了根 reducer 返回的完整 state 树。
这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。
现在,可以应用新的
state
来更新 UI。如果你使用了 React Redux 这类的绑定库,这时就应该调用component.setState(newState)
来更新。
三大原则
单一数据源
- 整个应用的
state
被储存在一棵object tree
中,并且这个object tree
只存在于唯一一个store
中。
State 是只读的
- 惟一改变
state
的方法就是触发action
,action
是一个用于描述已发生事件的普通对象。
使用纯函数来执行修改
- 为了描述
action
如何改变state tree
,你需要编写reducers
。
Redux 核心API
- Redux的核心是一个
store
,这个store
有Redux提供的createStore(reducers,[initialState])
方法生成。从函数签名看出,想生成store
,必须传入reducers
,同时也可以传入第二个可选参数初始化状态initialState
。
createStore
使用方法
const store = createStore(reducer);
reducer
- 在Redux里,负责响应
action
并修改数据的角色就是reducer
。reducer
本质上是一个纯函数,其函数签名为reducer(previousState, action) => newState
。reducer
在处理action
时,需传入一个previousState
参数。reducer
的职责就是根据previousState
和action
来计算出新的newState
。 使用方法将
reducer
即下面的todo
作为参数传入createStore(todo)中//以下为reducer的格式 const todo = (state = initialState, action) => { switch(action.type) { case 'XXX': return //具体的业务逻辑; case 'XXX': return //具体的业务逻辑; default: return state; } }
- 在Redux里,负责响应
getState()
- 使用方法
getState()
- 获取
store
中的状态。
- 使用方法
dispatch(action)
- 使用方法
store.dispatch(action)
- 分发一个
action
,并返回这个action
,这是唯一能改变store
中数据的方式。store.dispatch接受一个Action对象作为参数,将它发送出去。
- 使用方法
subscribe(listener)
- 使用方法
store.subscribe(listenter)
- 注册一个监听者,它在
store
发生变化时被调用,一旦State发生了变化,就会自动执行这个函数。通过subscribe绑定了一个监听函数之后,只要dispatch了一个action,所有监听函数都会自动执行一遍。
- 使用方法
replaceReducer(nextReducer)
- 更新当前
store
里的reducer
,一般只会在开发者模式中调用该方法。
- 更新当前
createStore的实现
包含
getState()
,dispatch(action)
,subscribe(listener)
;本函数近似源码,可简单实现功能与帮助理解createStore
的原理const createStore = (reducer) => { let state; //声明一个变量承接状态 let list = [];//声明一个数组用于储存监听函数 const getState = () => { return state;//直接返回state; } const dispatch = (action) =>{ state = reducer(state, action);//更新状态,且循环list数组,并执行里面的事件 list.forEach((fn) => { fn(); }) } const subscribe = (fn) => { list.push(fn);//将函数传入list中 return () => { list = list.filter(cd => cd != fn) } } return { getState, subscribe, dispatch } }
combineReducers
- 随着应用变得复杂,需要对
reducer
函数 进行拆分,拆分后的每一块独立负责管理state
的一部分。combineReducers
辅助函数的作用是,把一个由多个不同reducer
函数作为value
的object
,合并成一个最终的reducer
函数,然后就可以对这个reducer
调用createStore
。
- 随着应用变得复杂,需要对
使用方法
- 将多个不同的
reducer
作为对象的属性传入combineReducers({})
函数中,
- 将多个不同的
const rootReducer = combineReducers({
reducer1,
reducer2,
...
})
返回值
(Function):一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象。
let store = createStore(rootReducer) //store = { reducer1: ... , reducer2: ... , ... } let {reducer1, reducer2} = store; //取出
模拟实现combineReducers
//研究逻辑看这个
const combineReducers = (reducers) => {
return (state = {}, action) => {
let newState = {};
Object.keys(reducers).forEach((key) =>{
newState[key] = reducers[key](state[key], action);
})
return newState;
}
}
//简写装逼看这个
const combineReducers = (reducers) => (state = {}, action) => Object.keys(reducers).reduce((newState, key) => {
newState[key] = reducers[key](state[key], action);
return newState;
},{})
react的跨级组件通信(虫洞)帮助理解(react-redux)
- 此方法为React中的方法,随着应用变的越来越复杂,组件嵌套越来越深,有时要从最外层将一个数据一直传递到最里层。
- 理论上,通过
prop
一层层传递下去当然是没问题的。不过这也太麻烦啦,要是能在最外层和最里层之间开一个穿越空间的虫洞就好了。 - React的开发者也意识到这个问题,为我们开发出了这个空间穿越通道 ——
Context
。 注意:
context
一直都在React源码中,但在React0.14版本才被记录官方文档。官方并不太推荐大量使用,虽然它可以减少逐级传递,但当组件复杂时,我们并不知道context
是从哪传来的。它就类似于全局变量。context使用方法
在外层定义一个
getChildContext
方法,在父层制定childContextTypes
。class Provider extends Component{ getChildContext() { return {store: ...}; } render(){ return( this.props.children ) } } Provider.childContextTypes = { store : React.PropTypes.object };
在内层设置组件的
contextTypes
后,即可在组件里通过this.context.
来访问。class child extends Component{ render(){ const store = this.context.store; return( <div>1</div> ) } } child.contextTypes = { store: React.PropTypes.object }
解读react-redux
- 前面说到Redux的核心只有一个
createStore()
方法,这样还不足以让Redux在我们的react应用中发挥作用,还需要react-redux库 ———— Redux官方提供的React绑定。 - react-redux提供了一个组件和一个API帮助Redux和React进行绑定,一个是React组件
<Provider />
, 一个是connect()
。关于它们,我们需要知道的是,<Provider />
接受一个store
作为props
,它是整个Redux应用的顶层组件,而connect()
提供了在整个React应用的任意组件中获取store
中数据的功能。
Provider
其实就是创建一个外层包裹住整个Redux应用。
<Provider />
主要源码
export default class Provider extends Component {
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
render() {
return Children.only(this.props.children)
}
}
- 用法
ReactDom.render(
<Provider store = {store}>
<App />
</Provider>,
document.getElementById("root")
)
connect
使用方法
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(TodoApp)
上面代码看似那么长,但其实理解起来不太难,前四个参数是选填属性,根据需求填入即可。
connect(...)
调用后会返回一个函数这个函数可传一个参数,即你需要绑定的组件。- 模拟实现
connect
函数,只针对前两个关键参数。
const connect = (mapStateToProps, mapDispatchToProps) => {
return (WrapperComponent) => {
class Connect extends Component {
componentDidMount() {
const store = this.context.store;
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
})
}
componentWillUnmount() {
this.unsubscribe();
}
render (){
const store = this.context.store;
const stateProps = mapStateToProps(store.getState());
const dispatchProps = mapDispatchToProps(store.dispatch);
const props = Object.assign({}, stateProps, dispatchProps);
// return <WrapperComponent {...props} />;
return React.createElement(WrapperComponent, props);
}
}
Connect.contextTypes = {
store: React.PropTypes.object
};
return Connect;
}
}
mapStateToProps
官方解释: 如果定义该参数,组件将会监听 Redux
store
的变化。任何时候,只要 Reduxstore
发生改变,mapStateToProps
函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的props
合并。如果你省略了这个参数,你的组件将不会监听 Reduxstore
。如果指定了该回调函数中的第二个参数ownProps
,则该参数的值为传递到组件的props
,而且只要组件接收到新的props
,mapStateToProps
也会被调用。使用方法(其实里面第一个参数就是最早在
<Provider store = {store}>
传入的store
,于是可以在子组件上访问store
里的属性)
const mapStateToProps = (state, [ownProps]) => {
return {
todos : state.todos
}
}
mapDispatchToProps
- 官方解释: 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux
action creator
,而且这个对象会与 Reduxstore
绑定在一起,其中所定义的方法名将作为属性名,合并到组件的props
中。如果传递的是一个函数,该函数将接收一个dispatch
函数,然后由你来决定如何返回一个对象,这个对象通过dispatch
函数与action creator
以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators())。如果你省略这个mapDispatchToProps
参数,默认情况下,dispatch
会注入到你的组件props
中。如果指定了该回调函数中第二个参数ownProps
,该参数的值为传递到组件的props
,而且只要组件接收到新props
,mapDispatchToProps
也会被调用。 - 使用方法(用于传递方法)。其实总而言之,
mapStateToProps
是用来传递属性状态的,而mapDispatchToProps
是用来传递改变的方法的。
const mapDispatchToProps = (dispatch, [ownProps]) => {
return{
... : () => {
dispatch(...)
}
}
}
mergeProps
mergeProps(stateProps, dispatchProps, ownProps)
可以接受stateProps, dispatchProps, ownProps三个参数。stateProps
就是传给connect
的第一个参数mapStateToProps
最终返回的props
。dispatchProps
就是传给connect
的第二个参数mapDispatchToProps
最终返回的props
。- 而
ownProps
则为组件自己的props
。
options
如果指定这个参数,可以定制 connector 的行为。
[pure = true] (Boolean): 如果为
true
,connector
将执行shouldComponentUpdate
并且浅对比mergeProps
的结果,避免不必要的更新,前提是当前组件是一个“纯”组件,它不依赖于任何的输入或state
而只依赖于props
和 Reduxstore
的state
。默认值为true
。[withRef = false] (Boolean): 如果为
true
,connector
会保存一个对被包装组件实例的引用,该引用通过getWrappedInstance()
方法获得。默认值为false
。