Redux专题内容简介:
- Redux核心
它是JavaScript的状态容器(就是一个JavaScript对象),提供可预测化的状态管理。在这个容器当中可以保存很多元素的状态。
在大型项目中,当状态发生变化的时候,使用redux使之变得可预测。当项目当中的状态发生问题的时候,我们可以很容易地定位到问题出现在哪里。
Redux的核心概念及其工作流程:
Store:存储状态的容器,是一个JavaScript对象
View:视图,也就是我们常见的HTML页面,他要想响应store中状态的变化,则需要通过subscribe方法来为视图设置状态的变化订阅。要想对store中的状态进行修改,则需要通过dispatch方法来发布通知要对状态进行的操作Actions,进而调用相对应的Reducers方法以更新store的状态。
Actions:对象,描述对状态进行怎样的操作
Reducers:函数,操作状态并返回新的状态(最后会被更新到Store对象中)
工作流程为:
1、创建store对象:首先使用Redux.createStore(reducer) 方法创建状态容器Store
2、创建reducer函数:reducer函数有两个参数,分别为 state 和 action。在reducer函数中,我们可以通过action参数,通过switch判断语句来判断针对所触发(dispatch)的action要进行的状态操作,从而修改并返回修改后的state。该函数会被作为store对象创建时的第一个参数。
3、声明initialState作为默认的初始状态,它是一个对象,可以作为reducer函数的state参数的默认值。
4、定义描述要进行的状态变更的action对象
5、通过在视图中触发相应的操作时,在操作监听的回调函数中通过调用store对象的dispatch(action)(发布通知)方法通知触发相应的action,以此来改变store中的状态。
6、当页面交互事件触发后就会触发相应的action被reducer接收到,从而作出相对应的状态变更。
7、当store中的状态发生了改变后,需要同步视图,这是通过调用store.subscribe(callback)来实现的,在callback回调当中进行我们视图的相应操作或者页面内容更新。需要用到当前相应的状态数据可以通过调用store.getState().xxx来获得。store.getState()就是获取store对象中存储的状态。
总结:redux使用时用到的核心API 如下,
1)创建Store状态容器
const store = Redux.createStore(reducer);
2)创建用于状态处理的reducer函数
function reducer(state=initialState, action) {...}
3)获取store容器中的状态
store.getState()
4)订阅状态
store.subscribe(function(){...})
5)触发Action
store.dispatch(action); action是一个描述要进行怎样的状态操作的对象,如,{type: 'description...'}
- React + Redux
在React中使用Redux解决的问题: 组件架构中的数据流向是单向的,只能由父传子,如果没有很好的组织管理这些数据,就会容易造成数据混乱,出现问题时难以定位。
在react项目中加入redux的好处:使用redux管理数据,由于Store独立于组件,使得数据管理独立于组件,解决了组件与组件之间的数据传递困难的问题。
工作流程:
1)组件通过 dispatch 方法触发 action
2)Store 接受 action, 并将action 分发给 Reducer
3)Reducer 根据 action 类型对状态进行更改并将更改后的状态返回给 Store
4)组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件
要想使之完美结合,我们需要用到 Provider 组件与 connect 方法: react-redux
也就是通过Provider组件作为最外部组件,将store 放在全局的组件可以获取的地方。在需要用到Provider提供的store的组件中,引入并使用react-redux的 connect 方法,这个方法内部会帮助我们去订阅store,
1)不再需要我们手动的去调用store.subscribe方法来订阅状态数据变更。当store中的state状态发生变化的时候,它会帮我们重新渲染这个组件。
2)通过connect 方法可以拿到store中的状态,使用connect方法的第一个参数(mapStateToProps),可以通过connect当中的方法将store中的状态映射到组件的props属性中,这样一来,我们就可以在组件当中通过props属性来获取组件中的状态了。
3)通过connect 方法我们还可以拿到 dispatch 方法。
使用connect方法的第二个参数(mapDispatchToProps)简化组件视图:
将dispatch方法映射到组件的props属性中。他是一个函数,这个函数要求返回一个对象,在这个返回的对象当中定义什么,那么这些内容都会被映射到组件的props属性中,在组件定义时直接使用props的结构,可以大大简化组件视图的定义。
bindActionsCreators方法的使用:它是redux当中定义的方法,可以帮助我们生成dispatch发送通知的函数。要告知bindActionsCreators我们要生成的方法名叫什么,要触发的action 是什么。它的第一个参数是一个对象,第二个参数就是dispatch方法,也就是上述connect方法中获得的dispatch函数。bindActionsCreators 方法的返回值就是一个对象。从而方便我们不依赖dispatch方法,抽离重复代码,也就是ActionCreators。
Action 传递参数:
在定义ActionCreator的方法定义时可以接受参数,返回包含payload属性的对象,在调用时使用参数进行传递并使用即可。
拆分合并Reducer:
需要借助 redux 中提供的 combineReducers 方法,那么reducer最终的返回对象数据结构则发生了变化:combineReducers({ counter: CounterReducer, modal: ModalReducer }) // 最后的reducer返回对象为: { counetr: {count: 0}, modal: {show: false}}
- Redux 中间件
中间件允许我们扩展redux应用程序。它本质上是一个函数,action处理的流程会先经过中间件处理后再交给reducer,让reducer继续处理这个action。 - 开发Redux中间件
开发redux中间件有模版代码,他本质就是一个函数。// 柯里化函数形式 export default store => next => action => {}
只有把创建好的中间件注册给store,那他才能够在redux的工作流程中生效。它的注册需要使用到redux的一个 applyMiddleware 方法,这个方法就是用来注册中间件的,该方法的返回值放在 createStore 方法的第二个参数中,这样一来,当组件去触发action的时候,这个中间件的代码就可以得到执行了。
如果注册的中间件有多个,那么它的执行顺序是如何的呢?
它会按照注册的顺序分别调用相对应的中间件,当第一个中间件调用执行完成以后,它里面会调用next方法返回action对象传递给下一个中间件,依次类推。
注意:中间件必须要调用next(action),把action 外后面传递,后面的中间件以及最后的reducer才能正确的接收到action并执行。
值得注意的是:
1、当前的中间件函数是不关心你想执行什么样的异步操作的,只关心你执行的是不是异步操作。
2、如果你执行的是异步操作,你在触发action的时候给中间件传递一个函数, 如果执行的是同步操作,就传递action对象。
3、异步操作代码要写在传递进来的函数当中。
4、当前这个中间件函数在调用你传递进来的函数时,要将dispatch 方法传递进来。
export default ({dispatch}) => next => action => { if(typeof action === 'function') { return action(dispatch) } next(action) }
- Redux实践 综合案例
工作中常见常用中间件:
1) redux-thunk。运行我们在redux工作流中加入异步代码。
2)redux-saga。解决的问题是可以将异步操作从action creator 中抽离出来,放在一个单独的文件中。takeEvery 接收action。put 触发action。delay 设置执行延迟时间。
合并用到all方法,传入一个数组。
3)redux-actions。帮我门解决的问题是redux流程中大量的样板代码读写很痛苦,使用redux-actions 可以简化 action 和 reducer 的处理。
这里的 createAction就相当于我们之前自己所定义ActionCreator 函数。// 使用 redux-actions 来创建 action import { createAction } from 'redux-actions' const increment_action = createAction('increment') // 第一个字符串就是原来我们所创建的action对象中 type 的属性值 const decrement_action = createAction('decrement')
redux-actions 中还有一个方法是用来为上边定义的action 创建对应的 reducer 处理函数逻辑的,它就是 handleActions 方法。该方法的返回值就是我们之前自己创建的那个reducer 函数。
shopping购物车案例进本实现思路:// counter reducer 使用 redux-actions 来实现 import { handleActions as createReducer } from 'redux-actions' import { increment_action, decrement_action } from '../actions/counter.action' const initialState = {count: 0} const counterReducer = createReducer({ [increment_action]: (state, action) => ({ count: state.count + 1}), [decrement_action]: (state, action) => ({ count: state.count - 1}) }, initialState) export default counterReducer
服务器的异步请求交由redux-saga处理;
页面本地store的数据交由redux 的 reducer 来处理;
action由redux-actions来简化创建。
Redux源码实现:核心逻辑
redux中主要是一个createStore方法,该方法接收三个参数,createStore(reducer, preloaddedState, enhancer)。
reducer 是根据action的 类型来对 store 当中的状态进行更改。
preloadedState 是预先存储的初始化state 状态对象。
enhancer 是对store的功能进行增强,是我们所说的插件应用。
// redux的核心逻辑 createStore 方法
function createStore(reducer, preloadedState, enhancer) {
// 保存初始化store 状态对象, 需要在后续的方法中使用到,所需需要在方法中形成闭包,以便该值不会在createStore方法调用后被回收
var currentState = preloadedState;
var listeners = [];
// 定义getState 方法,返回当前store 中的currentState 对象
function getState() {
return currentState;
}
// 定义 dispatch 方法,用于触发action, 从而把action 交给 reducer 进行处理,并且将reducer处理过后的返回值更新到 currentState 的引用上,最后遍历所有已注册的订阅者,触发相对应的后续操作,如页面更新等
function dispatch (action) {
// 把action 交给 reducer 进行处理,并且将reducer处理过后的返回值更新到 currentState 的引用上
currentState = reducer(action);
// 遍历所有已注册的订阅者,触发相对应的后续操作,如页面更新等
for(let i = 0; i<= listeners.length; i++) {
var listener = listeners[i]
listener()
}
}
// 定义subscribe 方法,用于为store 添加的订阅者
function subscribe(listener) {
listeners.push(listener)
}
return {
getState, // 获取store的状态
dispatch, // 触发action
subscribe // 订阅状态
}
}
上面的代码实现过去简单粗暴,对参数的类型没有做限制,但参数不符合的时候没有错误提示,整个运行会直接挂掉。所以这里我们需要改进一下,对一些细节问题作容错处理。
1、 reducer 参数的类型必须是一个函数,该函数有两个参数,state 和 action 分别指示 store 中的状态以及本次action的对象。
2、是判断 dispatch 方法中的参数对象,action 中是否是对象,且对象中包含type 属性。
// redux的核心逻辑 createStore 方法
function createStore(reducer, preloadedState, enhancer) {
// 约束reducer 参数类型,它必须是一个function 类型
if(typeof reducer !== 'function') throw new TypeError('reducer must be a function');
// 保存初始化store 状态对象, 需要在后续的方法中使用到,所需需要在方法中形成闭包,以便该值不会在createStore方法调用后被回收
var currentState = preloadedState;
var listeners = [];
// 定义getState 方法,返回当前store 中的currentState 对象
function getState() {
return currentState;
}
// 定义 dispatch 方法,用于触发action, 从而把action 交给 reducer 进行处理,并且将reducer处理过后的返回值更新到 currentState 的引用上,最后遍历所有已注册的订阅者,触发相对应的后续操作,如页面更新等
function dispatch (action) {
// 判断action 是否为对象
if(!isPlainObject(action)) throw new TypeError('action must be a plain object');
// 判断action 对象中是否有type 属性
if(typeof action.type === 'undefined') throw new Error('action object must contain a type property'