React的数据模型分为共有数据和私有数据,共有数据可以在组件间进行传递,私有数据为当前组件私有。共有数据在React中使用props对象来调用,它包含标签所有的属性名称和属性值,props对象有三个特性,单向流动性、显示传递性和只读性。单向流动性是指React的数据只能由父组件传递到子组件,而不能由子组件传递到父组件;显示传递性是指必须明确地在子组件中通过属性赋值,数据才可以传递到子组件;只读性是指props数据是只读的,数据修改后并未改变原始的数据模型,而是会新生成一份数据模型,并将新的数据模型加载到原始父元素,从而完成数据的传递和组件状态的更改。私有数据为组件私有,在React使用state对象来调用,state数据模型可以方便地进行更新操作,并且不会影响到其他组件。
需要理解的是,props是一个父组件传递给子组件的数据流,这个数据流可以一直传递到子孙组件。而state代表的是一个组件内部自身的状态(可以是父组件、子孙组件)。改变一个组件自身状态,从语义上来说,就是这个组件内部已经发生变化,有可能需要对此组件以及组件所包含的子孙组件进行重渲染。而props是父组件传递的参数,可以被用于显示内容,或者用于此组件自身状态的设置(部分props可以用来设置组件的state),不仅仅是组件内部state改变才会导致重渲染,父组件传递的props发生变化,也会执行。
既然两者的变化都有可能导致组件重渲染,所以只有理解props与state的意义,才能很好地决定到底什么时候用props或state。
下面说一下react-redux中是如何维护state的,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 = (
<Provider store={store} key="provider">
<Router history={history} routes={routes} />
</Provider>
) // 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。
然后几个问题:
- React组件如何响应store的变化?
- 为什么connect选择性的merge一些props,而不是直接将整个state传入?
Connect的组件,它在包装原有组件的基础上,还在内部监听了Redux的store的变化。
但是通常,我们connect的是某个Container组件,它并不承载所有App state,然而我们的handler是响应所有state变化的,于是我们需要优化的是:当storeState变化的时候,仅在我们真正依赖那部分state变化时,才重新render相应的React组件,那么什么是我们真正依赖的部分?就是通过mapStateToProps
和mapDispatchToProps
得到的。具体优化的方式就是在shouldComponentUpdate
中做检查,如果只有在组件自身的props改变,或者mapStateToProps
的结果改变,或者是mapDispatchToProps
的结果改变时shouldComponentUpdate
才会返回true,检查的方式是进行shallowEqual的比较。
关于connect的使用:
Then, we wrap the components we want to connect to Redux with connect() function from react-redux. Try to only do this for a top-level component, or route handlers. While technically you can connect() any component in your app to Redux store, avoid doing this too deeply because it will make the data flow harder to trace.
Deep prop chains was one of the issues with React that led me to use Flux; the tradeoff for making it easier to trace the data flow is that you need to setup/maintain/trace the prop flow, and more importantly, parents need to know about the data requirements of their children.
redux的作者说从技术上虽然允许连接任意组件,但是避免过多使用,否则数据流会很难追踪,尽量只连接根组件,有开发者评论说redux如果只连接根组件,子组件嵌套过多,prop也会一级一级传递,很繁琐,这应该也是redux一直被诟病的一点。
结语:所以redux中的state和组件自身维护的state并不是一个东西,每个组件本身有自己的state,但在Redux中,应用的状态是全部存在一个单一的树结构中的,换句话说,应用的所有状态信息都存储在这个包含map和array的数据结构中。一个Redux应用的状态树是不可变的数据结构。这意味着,一旦你得到了一棵状态树,它就不会在改变了。任何用户行为改变应用状态,你都会获取一棵映射应用改变后新状态的完整状态树。这说明任何连续的状态(改变前后)都被分别存储在独立的两棵树。你通过调用一个函数来从一种状态转入下一个状态。