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 2 3 4 5 import { createStore } from 'redux' ;const store = createStore (reducer,preloadedState,enhancer);
action action描述了状态变更的信息,也就是需要页面做出的变化。这是由开发者定义并借助store.dispatch()派发的。action也是一个对象,Redux规定:action对象需要有一个type属性,作为确定这个action的名称。 action构造器一般为:
1 2 3 4 const actionCreator = data => { type :'ACTION_TYPE' , data }
然后使用dispatch派发action,dispatch来自store对象暴露的方法,负责派发action,这个action将作为dispatch的参数。store.dispatch(actionCreator('这里是数据')); // 派发上面的action
reducer 真正执行action的是reducer(),它必须是一个纯函数 ,以保证数据变化的可预测性。 reducer一般为:
1 2 3 4 5 6 7 8 9 10 11 12 const updateStateTree = (previousState = {}, action ) => { switch (action.type ) { case 'case1' : return newState1; case 'case2' : return newState2; default : return previousState; } }
当多个reducer时,应考虑进行合理拆分。Redux提供了一个工具函数:combineReducers,它接收一个JavaScript对象类型的参数,这个对象的键值分别为页面数据状态分片和子reducer函数,最后合并返回一个reducer。let resultReducer = combineReducer({reducer1,reducer2});
多个reducer时,往往将reducer命名为其处理的页面状态数据树中的键值,例如有下面的状态数据树:
1 2 3 4 5 const state = { data1 : { ... }, data2 : { ... }, data3 : { ... } }
然后reducer命名为:
1 2 3 const data1 = (state.data1,action ) => { ... };const data2 = (state.data2,action ) => { ... };const data3 = (state.data3,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 2 3 4 5 6 7 let arr = [1 ,2 ,3 ];const addArrReducer = (arr,action ) => { return arr.concat (action.data ); } let newArr = addArrReducer (arr,{type : 'ADD' , data :[4 ]});console .log (arr); console .log (newArr);
2.删除一项: 对于删除数组某一项,splice也不能满足需求,它会改变原来的数组,可以考虑用slice()。
1 2 3 4 5 6 7 const rmArrReducer = (arr,index ) => { return [ ...arr.slice (0 ,index), ...arr.slice (index+1 ) ] }
3.更新一项: 同上面一致使用slice()来更新
1 2 3 4 5 6 7 8 9 const insertOneReducer = (arr,index ) => { return [ ...arr.slice (0 ,index), arr[index] + 1 , ...arr.slice (index+1 ) ] }
2).对象操作 1.更新一项 直接修改会违背纯函数原则,一般选择ES Next新特性的Object.assign()来更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let item = { id :0 , book :'JavaScript' , readed :false } const setReaded = (item ) => { return Object .assign ({},item,{ readed :true }); } const setReaded = (item ) => { return { ...item, readed :true } }
2.增加一项 与上面同理,一般使用对象扩展运算符。 3.删除一项
1 2 3 4 5 6 7 8 9 10 11 12 let item = { id :0 , book :'JavaScript' , readed :false , note :13 } const newItem = Object .keys (item).reduce ((obj,key ) => { if (key !== 'note' ) { return { ...obj, [key] : item[key]} } return obj }, {});
4.深入拷贝嵌套数据 需要注意的是:Object.assign()以及扩展运算符等都是浅操作。如果在item外在嵌套一层:
1 2 3 4 5 6 7 8 9 10 11 12 13 let data = { item1 :{ id :0 , book :'javascript' , readed :false } } let item1 = Object .assign ({}, data.item1 );let newDate = Object .assign ({}, {item1});newData.item1 .readed = true ; console .log (data.item1 .readed ); console .log (newData.item1 .readed );
当然,也可以自己实现一个深拷贝函数。
Redux中间件和异步 初始Redux中间件 中间件可以在派发任何一个action和执行reducer这两步之间,添加自定扩展功能,例如 异步请求、日志打印等等。 流程如图: 中间件可以在action到达reducer之前进行日志记录、中断action触发,甚至修改action,或者不进行处理。可以接入多个中间件。 使用:
1 2 3 4 5 6 7 8 9 import { createStore,applyMiddleware } from 'redux' ;import thunk from 'redux-thunk' ;import createLogger from 'redux-logger' ;const logger = createLogger ();const store = createStore ( reducer, applyMiddleware (thunk,logger) )
Redux的异步处理 dispatch()派发的默认参数只能是一个JavaScript对象,如果要异步处理,则dispatch()接收一个函数为参数,在函数体内进行异步操作,并在异步完成后在派发相应的action。 而redux-thunk中间件正是解决了异步处理问题!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 store.dispatch (fetchNewBook ('learnRedux' )); function fetchNewBook (book ) { return (dispatch ) => { dispatch ({ type :'START_FETCH_NEW_BOOK' , data :book }) ajax ({ url :`/api/${book} .json` , type :'POST' , data :{} }).then (res => { dispatch ({ type :'FETCH_NEW_BOOK_SUCCESS' , data :res }) }); } }
整体的过程如图: 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 2 3 4 5 const mapStateToProps = (state ) => { return { value :state } }
它完成从store中选取数据并通过props传递给将要创建的容器组件。
mapDispatchToProps一般编写形式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const mapDispatchToProps = (dispatch,ownProps ) => { return { onAct :() => dispatch ({ type :'CLICK_ACTION' , data :ownProps.data }) } } const mapDispatchToProps = { onAct :(data ) => { type :'CLICK_ACTION' , data :data } }
mapStateToProps和mapDispatchToProps定义了展示组件需要用到的store内容。其中mapStateToProps负责输入逻辑,就是将状态数据映射到展示组件的参数(props)上;后者负责输出逻辑,即将用户对展示组件的操作映射成action。 两者个参数是可选的。当只有前者的参数时,默认情况下,dispatch()会注入最后返回的容器组件的props中,而只有后者则把前者填上null。两个都忽略时,Redux store的状态数据无法传递下来,因此返回的容器组件就不会监听store的任何变化。 最后,react-redux提供了Provider组件,一般用法是需要将Provider作为整个应用的根组件,并获取store为其prop,以便后续进行下发处理。 所以,在开发中,借助于react-redux的基本模式如下:
1 2 3 4 5 6 let App = connect (mapStateToProps,mapDispatchToProps)(presentationalComponent);ReactDom .render ( <Provider store ={store} > <App /> </Provider > );
注:读完《React状态管理与同构实战》第三章的理解