1.Redux是什么?
官方定义是:对于JavaScript应用而已,Redux是一个可预测状态的“容器”。
设计哲学:
1.数据来源单一性。不论是像计数器一样,还是复杂的聊天系统,我们都使用一个JavaScript对象来表述整个状态机的全部状态,并存储到store当中,而这个store是唯一的。获取状态:let state = store.getState();
2.状态是只读的。即store.getState()返回的结果是只读的,不允许改变它。当页面需要新的数据状态时再生成一颗全新的状态数据树,使得store.getState()返回一个全新的JavaScript对象。Redux规定:当页面需要展现新的数据状态时,我们只需要dispatch一个action即可。
3.使用reducer函数来接收action,并执行页面状态数据树的变更。经过reducer函数处理之后,store.getState()就会返回新页面的数据状态。当然,经过reducer函数根据处理后,返回一个新的JavaScript对象,而不会对原返回值进行更改,因此reducer是一个纯函数。reducer并不直接更改页面的状态数据树,而是根据action产生一颗新的页面状态数,并把它应用在store.getState()当中。
reducer和action需要开发者编写,reducer接收两个参数:当前的页面数据状态和被派发的action(previousState,action) => newState
2.Redux基本使用和实践
store
store是一个可预测状态的“容器”,保存着整个页面状态数据树,提供了重要的API。
API:
- dispatch(action):派发action。
- subscribe(listener):订阅页面数据状态,即store中state的变化。
- getState:获取当前页面状态数据树,即store中的state。
- replaceReducer(nextReducer):一般用不到…我也不知道用来干嘛的?
创建store
1 | import { createStore } from 'redux'; |
action
action描述了状态变更的信息,也就是需要页面做出的变化。这是由开发者定义并借助store.dispatch()派发的。action也是一个对象,Redux规定:action对象需要有一个type属性,作为确定这个action的名称。
action构造器一般为:
1 | const actionCreator = data => { |
然后使用dispatch派发action,dispatch来自store对象暴露的方法,负责派发action,这个action将作为dispatch的参数。store.dispatch(actionCreator('这里是数据')); // 派发上面的action
reducer
真正执行action的是reducer(),它必须是一个纯函数,以保证数据变化的可预测性。
reducer一般为:
1 | const updateStateTree = (previousState = {}, action) => { |
当多个reducer时,应考虑进行合理拆分。Redux提供了一个工具函数:combineReducers,它接收一个JavaScript对象类型的参数,这个对象的键值分别为页面数据状态分片和子reducer函数,最后合并返回一个reducer。let resultReducer = combineReducer({reducer1,reducer2});
多个reducer时,往往将reducer命名为其处理的页面状态数据树中的键值,例如有下面的状态数据树:
1 | const state = { |
然后reducer命名为:
1 | const data1 = (state.data1,action) => { ... }; |
最后合并reducer:const resultReducer = combineReducers({data1,data2,data3});
总结
当通过Redux的createStore()创建一个store实例后,我们便可以使用store.dispatch()派发一个action,这个action需要开发者结合自身业务去编写。同时在执行store.dispatch()之后,Redux会“自动”帮我们执行处理变化并更新数据的reducer函数。从store.dispatch()到reducer这个过程可以认为是Redux内部处理的,但具体的action以及reducer需要开发者编写,以完成应用的需求。那么当页面数据状态得到更新之后,实际上就需要store.subscribe(callbackFn)方法订阅数据的更新,并完成UI更新。
3.Redux开发基础
在Redux架构下保证reducer的不可变性
对于reducer是要保证它不可被修改的,不应该直接更改原有对象或者数组的值,因为他们是引用类型。
1).数组操作
1.增加一项:
显然push()不能满足需求,它会改变原来的数组,可以考虑用**concat()**,它返回一个新的数组。
1 | let arr = [1,2,3]; |
2.删除一项:
对于删除数组某一项,splice也不能满足需求,它会改变原来的数组,可以考虑用slice()。
1 | const rmArrReducer = (arr,index) => { |
3.更新一项:
同上面一致使用slice()来更新
1 | const insertOneReducer = (arr,index) => { |
2).对象操作
1.更新一项
直接修改会违背纯函数原则,一般选择ES Next新特性的Object.assign()来更新。
1 | let item = { |
2.增加一项
与上面同理,一般使用对象扩展运算符。
3.删除一项
1 | let item = { |
4.深入拷贝嵌套数据
需要注意的是:Object.assign()以及扩展运算符等都是浅操作。如果在item外在嵌套一层:
1 | let data = { |
当然,也可以自己实现一个深拷贝函数。
Redux中间件和异步
初始Redux中间件
中间件可以在派发任何一个action和执行reducer这两步之间,添加自定扩展功能,例如 异步请求、日志打印等等。
流程如图:
中间件可以在action到达reducer之前进行日志记录、中断action触发,甚至修改action,或者不进行处理。可以接入多个中间件。
使用:
1 | import { createStore,applyMiddleware } from 'redux'; |
Redux的异步处理
dispatch()派发的默认参数只能是一个JavaScript对象,如果要异步处理,则dispatch()接收一个函数为参数,在函数体内进行异步操作,并在异步完成后在派发相应的action。
而redux-thunk中间件正是解决了异步处理问题!
1 | // 引入redux创建store和应用中间件等代码省略... |
整体的过程如图:
redux-thunk对于异步处理的关键在于:使dispatch能够接收异步函数,我们完全可以控制dispatch响应action的时机。
4.结合react使用redux
使用react-redux库
react-redux是对React和Redux进行了连接,对Redux的方法进行了封装和增强,使得使用起来非常方便。
容器组件:指数据状态和逻辑的容器。它并不负责展示,而是只维护内部状态,进行数据分发和处理派发action。因此容器组件对Reudx是感知的,可以使用Redux的API,比如dispatch()等。
展示组件:只负责接收相应的数据,完成页面展示,它本身并不维护数据和状态。实际上,为了渲染页面,展示组件所需要的所有数据都由容器组件通过props层层传递。
| 描述 | 展示组件 | 容器组件 |
|---|---|---|
| 目的 | 展示内容 | 处理数据和逻辑 |
| 是否感知Redux | 不感知 | 感知 |
| 数据来源 | 从props获取 | 从Redux state订阅获取 |
| 改变数据 | 通过回调props派发action | 直接派发action |
| 由谁编写 | 开发者 | 由react-redux库生产 |
react-redux有个最重要的方法:connect()connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options]);
connect()是用来连接容器组件和展示组件。它的核心是将开发者定义的组件,包装转换生成容器组件。所生成的容器组件能使用Redux store中的那些数据,全由connect()的参数来确定。
一般用法:
1 | connect(mapStateToProps,mapDispatchToProps)(presentationalComponent); |
第一次执行的第一个参数是一个函数,其作用是给返回的组件注入props,这个props来自Redux store中的状态。所以这个函数一定要返回一个纯JavaScript对象。第一次的第二个参数可以是一个函数也可以是一个对象,如果是一个函数,则这个函数接收dispatch()以及容器的props作为参数,最终也返回一个对象;如果是一个对象,那么兼键值应该是一个函数,用来描述action的生成。第二次执行的参数是接收一个正常的展示组件,并在这基础上返回一个容器组件。
mapStateToProps一般编写形式如下:
1 | const mapStateToProps = (state) => { |
它完成从store中选取数据并通过props传递给将要创建的容器组件。
mapDispatchToProps一般编写形式如下:
1 | // 接收函数 |
mapStateToProps和mapDispatchToProps定义了展示组件需要用到的store内容。其中mapStateToProps负责输入逻辑,就是将状态数据映射到展示组件的参数(props)上;后者负责输出逻辑,即将用户对展示组件的操作映射成action。
两者个参数是可选的。当只有前者的参数时,默认情况下,dispatch()会注入最后返回的容器组件的props中,而只有后者则把前者填上null。两个都忽略时,Redux store的状态数据无法传递下来,因此返回的容器组件就不会监听store的任何变化。
最后,react-redux提供了Provider组件,一般用法是需要将Provider作为整个应用的根组件,并获取store为其prop,以便后续进行下发处理。
所以,在开发中,借助于react-redux的基本模式如下:
1 | let App = connect(mapStateToProps,mapDispatchToProps)(presentationalComponent); |
注:读完《React状态管理与同构实战》第三章的理解