一、定义与功能
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)
1、UI 组件特征:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用
this.state
这个变量) - 所有数据都由参数(
this.props
)提供 - 不使用任何 Redux 的 API
2、容器组件特征:
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
总结:
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
二、使用与方法
1、connect方法 输入UI组件, 输出容器组件
React-Redux 提供connect
方法,用于从 UI 组件生成容器组件。
// TodoList-> UI组件; VisibleTodoList -> 容器组件 // mapStatetoProps,mapDispatchToProps -> 它们定义了 UI 组件的业务逻辑。 // mapStatetoProps 可省略,若省略,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新 import { connect } from 'react-redux' const VisibleTodoList = connect(mapStatetoProps, mapDispatchToProps)(TodoList);
(1) mapStateToProps(
state
[,props
]) 负责输入逻辑,即将state
映射到 UI 组件的参数(props
)
state: 必选,
state
对象;props: 可选,
代表容器组件的
props
对象mapStateToProps
会订阅 Store,每当state
更新的时候,就会自动执行,重新计算 UI 组件的参数(props),从而触发 UI 组件的重新渲染。
// map Redux state to component props (把 Redux state 映射到 component props) const mapStatetoProps = (state) => { return {num: state} } // 使用ownProps作为参数后,若容器组件的参数(ownProps)发生变化,也会引发 UI 组件重新渲染 const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } }
(2) mapDispatchToProps 负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
// map Redux actions to component props (把 Redux actions 映射到 component props) const mapDispatchToProps = dispatch => { return { onIncreaseClick: () => dispatch(increaseAction) }; }
mapDispatchToProps 分为两种类型:函数和对象
mapDispatchToProps
是一个函数,会得到dispatch
和ownProps
(容器组件的props
对象)两个参数。
mapDispatchToProps
作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }
mapDispatchToProps
是一个对象,键名是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; }
三、原理与流程
为了让子组件能够获得context属性,React强制要求根组件(此处为Provider组件)提供getChildContext实例方法,以及类属性childContextTypes。而子组件想要获取context,也必须定义类级别的Counter. contextTypes属性。定义是双向的,如果缺少了任何一块,子组件都获取不到context属性。
我认为父组件的那块定义是在Provider的代码中实现的,而子组件的那部分是在connect方法中实现的。
因此connect方法为Counter组件添加的context属性实质上是由Provider传下来的,这样在mapStatesToProps方法里的state参数实质上就是this.context.store.getState()方法获得的。
页面首次加载以及之后有互动行为之后整个逻辑的流程:
(1)页面首次加载时,store里的初始state获取过程:
createStore(reducers,defaultParams)的调用,其中reducers可以使一个reducer,也可是redux.combineReducers过的reducer的集合。
createStore方法会对每个reducer去dispatch一个action.type=@@redux/INIT类型的action,而这个action一般在reducer的代码里不会被handle,直接掉入default块,于是就返回了state的初始状态。
然后一般就会ReactDom.render()将应用渲染出来,每个子组件的容器组件通过传入this.context.store.getState()方法获得的state对象, 以及容器组件上自带的ownProps给mapStatesToProperties方法,来构建props,最后将props应用到子组件的UI组件上。
(2)互动行为之后整个逻辑:
当在子组件上发生交互行为,如click时,mapDispatchToProps会定义click触发时应该dispatch哪一个action的映射。
然后store接收到这个action后会进行reduce,得到最新的state,然后再调用所有的子组件的mapStatesToProps方法生成新的props。
最后对Provider进行重新渲染。当然上面的事件计算出来的很多state可能都不会发生变化,所以diff算法不会去修改这些没有发生变化的组件,因此性能也比较好。