在写Redux的时候我们就了解了 如果使用Redux的话配合React是最好的 Dan Abramov为此还特意封装了一个react-redux库来提供便利
一旦我们选择使用了这个react-redux库 那么我们的组件概念就要加以区分了 从现在起我们的组件分为展示组件和容器组件两种 (参考了通俗易懂的阮大神博客)
展示组件(PResentational component) 也叫UI组件、纯组件 特点如下:
负责UI显示无状态不使用this.state数据来自this.props不使用任何redux的API展示组件其实就是把我们的普通组件的数据与逻辑抽离出来
容器组件(container component) 特点如下:
负责管理数据和业务逻辑带有内部状态使用redux的API容器组件是由我们react-redux库的API通过展示组件生成的
从它们的名字也可以猜到,它们是内外关系 容器组件包裹着展示组件
说的再通俗一些 我们原来是将结构和逻辑都装在一个组件中 现在将这个组件继续拆成负责视图的组件和负责逻辑数据的组件 这样做有如下优点:
理解 数据与逻辑分开,更便于我们理解分离 必须将标签拆分,可用性更强重用 一个展示组件可以搭配不同容器组件视图 展示组件可以放到单独页面中调整UI下面我们来看一下react-redux库的核心 connect()方法与Provider组件
上面也说到了 我么的容器组件是由库API得到的 而这个函数就是connect connect的意思就是连接展示组件与容器组件的意思 为了加以区分,我用Container表示容器组件,用Component表示展示组件 用法如下
import {connect} from 'react-redux';const Container = connect()(Component);结构就是这个样子
<Container> <Component/></Container>不过现在我们仅仅是通过展示组件生成了一个容器组件 并且将它们连接了起来 但是容器组件中并没有数据和逻辑 只是一具空壳,毫无意义 所以我们还需要向这个connect函数中传入两个参数 它接收两个值作为参数:(实际是四个,另外两个不常用暂时不讲)
mapStateToProps(输入逻辑) 负责将通过state获得的数据映射到展示组件的this.propsmapDispatchToProps(输出逻辑) 负责将用户操作转化为Action的功能函数映射到展示组件的this.props名字就和reducer一样,只是官方的概念性叫法(不过还是蛮形象的) 使用的时候可以自定义名字(不过一定要语义化)
所以完整的用法应该是这样的
const Container = connect( mapStateToProps, mapDispatchToProps)(Component);但是此时mapStateToProps与mapDispatchToProps我们还没有定义
mapStateToProps负责将state的数据映射到展示组件的this.props 它是一个函数,接收参数state对象 如果有必要的话,还可以使用第二个参数:容器组件的props属性 返回一个对象表示state到展示组件props的映射关系
const mapStateToProps = (state) => { return { list: state.list }}此时你会发现这个函数名有多合适
返回对象中的“值”——state.list
表示我们要将state的list数组传递给内部的展示组件返回对象中的“键”—— list
表示我们在展示组件中可以通过this.props.list来获取这个数组但有时,我们不能这么轻松的就通过state的某个属性值获得要传递的数据 这时我们可以自定义一个处理函数返回要传递的数据
const mapStateToProps = (state) => { return { list: handler(state.list, state.option); }}比如说这里handler就是我们的处理函数 拿我上一篇文章的toDoList待办事项列表为例 这个handler大概是这样的
const handler = (list, option) => { switch(option){ case "SHOW_ACTION": return list.filter(...); case "SHOW_CROSSED": return list.filter(...); ... default: return list; }}这个函数我没有写完整,相信大家应该都能看明白 通过判断option我来将list数组进行 “过滤” 函数返回后作为数据返回给展示组件
mapStateToProps会订阅store,state更新后,就会触发展示组件重绘 不过在connect( )函数中,我们可以省略mapStateToProps 如果这么做的话,store更新就不会触发展示组件重绘了
上面也说道了,除了state我们还可以使用容器组件的属性props
const mapStateToProps = (state, ownProps) => { return { ... }}如果容器组件的props发生改变的话,同样会触发展示组件重绘
mapDispatchToProps负责定义发送action的函数映射到展示组件的this.props 与它的兄弟不同,它既可以是函数也可以是对象 作为函数,它会得到store.dispatch作为参数 同样还有一个容器组件的props属性可以使用 返回值我不用说大家也能猜到 就是一个表示映射关系的对象 但是这里表示的是用户如何发出Action(比如触发事件)
const mapDispatchToProps = (state, ownProps) => { return { onClick: () => { dispatch({ type: 'SET_FILTER', filter: ownProps.filter }) } }}返回对象中的“值”——() => {dispatch(...)}
表示我们要传递给内部展示组件的函数(函数功能:dispatch一个action)返回对象中的“键”—— onClick
表示我们在展示组件中可以通过this.props.onClick来获取这个函数如果是作为对象的话,就更简单了 上面的写法和下面的等价
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_FILTER', filter: filter }}这个对象的值是一个函数,它被认为是一个Action Creator 函数的参数可以填入容器组件的props 返回的Action会由redux自动dispatch
在完成了Container与Componet的连接 实现了Container的管理数据与业务逻辑之后还没完 还有问题 我们使用了mapStateToProps,它的参数是state 也就是说,他需要传入state 如果我们手动将state对象一层一层的传入容器组件 应用小还好说,大应用深层的组件简直累死了,绝对让你传到怀疑人生
好在,react-redux提供了Provider组件让我们省了不少功夫 它就相当于我们整体的容器组件(不过区别很大) 用法就是在我们根组件外部嵌套一层Provider,传入store (使用全局的store有风险) 这样所以的子组件都可以开心地拿到state了 我们也省心了
render( <Provider store={store}> <App/> </Provider>, document.getElementById('root'));内部的原理是: Provider接受store作为其props,并声明为context的属性之一 子组件在声明了contextTypes之后可以通过this.context.store访问到store
上一次介绍Redux的时候介绍了一个简单的计数器 这次我把那个代码拿过来改装一下
import React from 'react';import {Component} from 'react'import ReactDom from 'react-dom';import {createStore, combineReducers} from 'redux';import {connect, Provider} from 'react-redux';首先定义单纯用来展示UI的展示组件
class Counter extends Component { render(){ const {value, reduceHandler, addHandler} = this.props; return ( <div> <p>{value}</p> <button onClick={reduceHandler}>-</button> <button onClick={addHandler}>+</button> </div> ) }};然后定义映射函数,生成容器组件
const mapStateToProps = (state) => { return { value: state.cnt }}const mapDispatchToProps = (dispatch) => { return { reduceHandler: () => { dispatch({type: 'REDUCE'}); }, addHandler: () => { dispatch({type: 'ADD'}); } }}const APP = connect(mapStateToProps, mapDispatchToProps)(Counter);Reducer稍微修改一下
const reducer = (state = {cnt: 0}, action) => { switch (action.type) { case 'ADD': return {cnt: state.cnt + 1}; case 'REDUCE': return {cnt: state.cnt - 1}; default: return state; }};const store = createStore(reducer);渲染函数中的结构外部嵌套Provider并添加store
ReactDom.render( <Provider store={store}> <APP/> </Provider>, document.getElementById('root'));有了Provider,我们也就不需要 store.dispatch()
了 它会帮我们处理渲染 最后的样式依然是那个样子
==主页传送门==
新闻热点
疑难解答