存在的问题以及redux的解决办法。
多组件共享状态,都要靠父组件一层层往下传,传递复杂
redux,将所有状态都存储到一个仓库中,由一个保安(reducer)进行管理。
状态变更时候需要注意的点
改变状态必须通过dispatch是因为改变状态的时候,可以通知订阅(subscribe)的组件。reducer中不能改变状态是因为遵循纯函数的规则,这样的话可以进行数据重放,时间旅行,利于debug。
只有一个reducer来维护
- redux规定只有一个仓库(store),一个保安(reducer)。这样存在的问题就是这个保安要维护的操作就会很多。我们不可能所有组件的操作都到这一个保安函数中去修改。
所以combineReducers就出现了。我们将其分拆成多个 reducer,拆分之后的 reducer 都是相同的结构(state, action),并且每个函数独立负责管理该特定切片 state 的更新。多个拆分之后的 reducer 可以响应一个 action,在需要的情况下独立的更新他们自己的切片 state,最后组合成新的 state。
react-redux是什么
react-redux是个连接库,用来连接仓库和组件。也用来帮我们做所有组件都要做的重复的事情,比如组件订阅仓库变化而做出响应,组件销毁时取消订阅(不取消会一直调用而报错),帮我们做comebineReducer那一步,生成一个大的reducer。
为什么需要mapStateToProps这个函数
因为整个仓库的数据太大了,可能一个组件用不到那么多,所以这个函数相当于过滤的功能,你想要仓库里的什么状态,就返回什么状态。相当于mobx
里面的inject
。第二个原因就是也可能需要对仓库中的数据进行修改,才是页面中所需要使用的。
mapDispatchToProps
connect函数的第二个参数,可以为对象或者函数。这个参数的作用是将actions挂载到props中,方便组件调用。如果是函数,参数为dispatch,返回一个对象,对象里分别是actions,要自己做dispatch。所以一般直接传入一个对象,对象的名为action名称,值为action creater(也就是返回一个由type的对象),由connect函数中统一处理重写函数,将该对象传入dispatch方法统一调用。
createStore
创建store,提供了订阅变更,派发变更,获取当前仓库状态三个方法。
export default function createStore (reducer, preloadedState) {
let currentState = preloadedState
let listeners = []
function getState () {
return currentState
}
function dispatch (action) {
currentState = reducer(currentState, action)
listeners.forEach((listener) => listener())
}
function subscribe (listener) {
listeners.push(listener)
return function () {
listeners.filter(item => item !== listener)
}
}
dispatch({
type: '@@INIT'
})
return {
dispatch,
subscribe,
getState
}
}
combineReducer的意义就是将reducer进行分离。统一由combineReducers进行组织。
每次在执行action的时候,其实会执行每个reducer。为什么要执行所有reducer呢?
因为保安队长也不知道通过dispatch派发过来的是谁。所以要执行下面的所有reduser
// combineReducer的意义就是将reducer进行分离。统一由combineReducers进行组织。
// 每次在执行action的时候,其实会执行每个reducer。为什么要执行所有reducer呢?
// 因为保安队长也不知道通过dispatch派发过来的是谁。所以要执行下面的所有reduser
export default function combineReducers (reducersObj) {
if (typeof reducersObj !== 'object' || reducersObj === null) {
throw Error('what is your problem?')
}
// 进行浅拷贝,不影响原有对象
let finalReducers = {}
Object.keys(reducersObj).forEach((key) => {
const reducer = reducersObj[key]
if (typeof reducer === 'function') {
finalReducers[key] = reducersObj[key]
}
})
return function (previewState = {}, action) { // 原来是在这里赋默认值。
let nextState = {}
let hasChanged = false
Object.keys(finalReducers).forEach((key) => {
const prevState = previewState[key]
const reducer = finalReducers[key]
nextState[key] = reducer(prevState, action)
if (nextState[key] !== prevState) {
hasChanged = true
}
})
return hasChanged ? nextState : previewState
}
}
bindActionCreators
该方法就是为了减少dispatch这个模版代码的使用,统一在这里调用。
function bindActionCreator (actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments))
}
}
// 该方法就是为了减少dispatch这个模版代码的使用,统一在这里调用。
export default function bindActionCreators (actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' && actionCreators === null) {
throw Error('what is your problem?')
}
const boundActionCreators = {}
Object.keys(actionCreators).forEach((key) => {
boundActionCreators[key] = bindActionCreator(actionCreators[key], dispatch)
})
return boundActionCreators
}
compose.js
感觉在js内为了解决回调的问题,都会用到相似的技术,比如koa、express、promise。
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
中间件
中间件的目的是为了执行reducer之前增加一些逻辑。避免直接修改ruducer或者完全重写dispatch,这样dispatch会变得很臃肿。
applyMiddleware.js
import compose from './compose'
// 这个方法出现的目的是为了处理多个中间件的情况。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// dispatch = compose(...chain)(store.dispatch)
let c = compose(...chain)
console.log(c) // (...args) => a(b(...args)) 得到一个函数(...args) => a(b(...args))
dispatch = c(store.dispatch) // 先执行b,将store.dispatch传入,此时,最后一个中间件的返回值(也就是中间件最里层的那个参数为action的函数)作为了a的参数。
// 一层层执行,这个时候就返回了一个大的函数dispatch。这个函数就是第一个中间件的最里层的参数为action的函数。
// dispatch还是个函数。这样和koa一样是个洋葱模型了。
// 所以也造成的结果就是,如果在中间件内部不调用next,后面的中间件就不会执行,action也不会被派发。
// 总结来说,写在前面的中间件最里层的函数先执行,不过在diaoyongnext的时候,会执行下一个中间件最内层的函数。
// 直到最后一个中间件执行。这时候就执行了dispatch(action)这时候再一层一层往回执行。像洋葱一样。
return {
...store,
dispatch // !!!! dispatch还是个函数。
}
}
}
redux-thunk中间件
// 中间件干的事就是判断当前的action是否对应当前中间件的条件,如果是,就对该action进行处理。
export default function reduxPromise ({dispatch, setState}) {
return (next) => {
return (action) => {
const payload = action.payload
if (!!payload && (typeof payload === 'function' || typeof payload === 'object') && typeof payload.then === 'function') {
return payload.then((result) => {
dispatch({...action, payload: result})
}).catch((err) => {
dispatch({...action, payload: err, error: true})
debugger
return Promise.reject(err)
})
} else {
next(action)
}
}
}
}
redux-promise中间件
function createThunkMiddleware (extraArgument) {
return function ({getState, dispatch}) {
return (next) => {
return (action) => {
if (typeof action === 'function') {
return action(dispatch, extraArgument)
}
return next(action)
}
}
}
}
const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware
export default thunk