redux实现目标
redux是一个状态管理容器,提供一个全局对象,这个对象只能通过派发action去修改。创建一个js文件,这个文件提供三个api:
1.createStore,提供三个api: dispatch、getState、subscribe
2.combineReducers,将多个reducer整合成一个reducer
3.applyMiddleware,接收并处理多个中间件
createStore
function createStore(reducers, enhancer) {
// 处理中间件
if (enhancer) {
return enhancer(createStore)(reducers)
}
// 唯一的store,数据的源头,包含多个state,有多少个reducer就有多少个state
let store = {};
// 初始化一下store的值
store = reducers(store, {});
// 存放订阅的回调
const queques = [];
function dispatch(action) {
// 这里会将reducers中所有reducer全跑一遍,只要是有这个type的,全都会执行
store = reducers(store, action);
// 将队列中所有注册的回调,全跑一遍
queques.forEach(queque => queque());
}
// 只返回了当前store,快照
function getState() {
return store
}
// 只是将回调存了起来,并不会执行
function subscribe(cb) {
queques.push(cb)
}
return {
dispatch,
getState,
subscribe
}
}
combineReducers
/**
* 将所有reducers整合整一个reducer
* 返回一个接受state、action的函数,调用该函数会依次调用reducer,改变各自的state
* 返回值是对象,以reducer名字为key,各自reducer返回值为值
* @param {*} reducers
*/
function combineReducers(reducers) {
let keys = Object.keys(reducers);
// 看到这里应该理解为什么reducer名字的由来了
return (state = {}, action) => keys.reduce((a, b) => {
// a, b 对应:{}、'curReducer',{curReducer: {}}, 'userReducer',打印一下就知道
a[b] = reducers[b](state[b], action);
return a
}, {})
}
applyMiddleware
先写两个常用的中间件:
function logger() {
return dispatch => action => {
console.log('logger -> action.type:', action.type);
return dispatch(action)
}
}
// 初始的dispatch是不支持接收函数的,只接收对象
function thunk() {
return dispatch => action => {
// 这里处理异步action
if (typeof action === 'function') {
// 先执行异步,再将dispatch作为参数传过去
action(dispatch)
} else {
return dispatch(action)
}
}
}
中间件是个三层嵌套的函数:store => next => action,最后返回执行上一个中间件,next就是上一个中间件。最后一个next就是最开始传入的store.dispatch。
// 页面触发dispatch的方法
fnHandle(type) {
if (type === 'delay') {
// 异步action
store.dispatch((dispatch) => {
setTimeout(() => {
// 这里的dispatch就是上边中间件传过来的
dispatch({type: 'add'})
}, 1000)
})
} else {
store.dispatch({type})
}
}
compose
这个是理解中间件的基础,先举个例子,写一个函数,依次执行依次三个函数:
function fn1(arg) {
console.log('fn1', arg);
return arg
}
function fn2(arg) {
console.log('fn2', arg);
return arg
}
function fn3(arg) {
console.log('fn3', arg);
return arg
}
这就用到了compose,组合函数:
function compose(...fns) {
// 这个最终会返回一个函数,每层函数的参数都是上一层函数的返回值
return fns.reduce((a, b) => (arg) => {
return a(b(arg))
})
}
compose(fn1, fn2, fn3)('00')
执行结果:
fn3 00
fn2 00
fn1 00
"00"
// 如果需要执行更多的方法,只需添加到参数中就行了。中间件原理就是这样
这里的顺序是 fn3 -> fn2 -> fn1
// 返回值和createStore一样
function applyMiddleware(...middleWares) {
return createStore => (reducers) => {
let store = createStore(reducers);
let dispatch;
// 这里传入的middleWareOptions,暂时用不到,这里可能是提供给中间件更多的api吧
const middleWareOptions = { store: store.getState(), dispatch: store.dispatch}
let _middleWares = middleWares.map(middleWare => middleWare(middleWareOptions));
// 源码的写法:dispatch = compose(...chain)(store.dispatch)
let _compose = compose(..._middleWares);
dispatch = _compose(store.dispatch);
return {
...store,
dispatch
}
}
}
剖析中间件过程
1.let _middleWares = middleWares.map(middleWare => middleWare(middleWareOptions));
2.let _compose = compose(..._middleWares);
3.dispatch = _compose(store.dispatch);
写三个中间件,分析一下以上三步代码:
const A = () => {
return next => action => {
// do someting...
return next(action)
}
}
const B = () => {
return next => action => {
// do someting...
return next(action)
}
}
const C = () => {
return next => action => {
// do someting...
return next(action)
}
}
第一步,传入store之类的配置项,依次执行中间件,此时_middleWares里的fn,是这样的:
const fnA = next => action => {
// do someting...
return next(action)
}
const fnB = next => action => {
// do someting...
return next(action)
}
const fnC = next => action => {
// do someting...
return next(action)
}
第二步,compose(...chain),将传入的函数的组合成一个函数:
(arg) => a(b(arg))
第三步,传入store.dispatch,是这样的:
执行 fnA(fnB(fnC(store.dispatch)))
这个顺序肯定是由内向外的,分析一下这个步骤:
// 执行最里层的fnC:
fnA(fnB((store.dispatch) => action => {
// do someting...
return store.dispatch(action)
}))
// 那么fnB是这样:
const fnB = action => {
// do someting...
return fnC(action)
}
// 执行fnB这一层:
const fnA = action => {
// do someting...
return fnB(action)
}
// 最后执行最外层的fnA,得到最终的dispatch:
(action) => {
// do someting...
return fnA(action)
}
当执行dispatch(action), 还是先执行fnA函数体,再执行fnB,最后的那个next才是原始的store.distach。这里广泛用到了科里化,保存了之前的参数引用。
梳理下以上的工作流程:
- 1.ui调用dispatch派发一个action
- 2.reducer根据action.type,修改相应的state并返回给store,下一步更新ui
- 3.组件在挂载后,在subscribe中订阅了forceUpdate,完成了以上两步就将store中的订阅回调执行一遍
以上就是redux的实现流程了,但是存在两个问题:
- 1.数据的来源,需要通过store.getState(),太麻烦
- 2.更新ui试图,还需要手动去触发
react-redux实现目标
// 入口文件:
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
...
// 子组件
const mapStateToProps = state => (
{
curReducer: state.curReducer,
userReducer: state.userReducer
}
)
// 源码中可以是对象,也可是函数,这里使用对象
const mapDispatchToProps = {add, minus, delay}
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxComp)
Provider
看上去就是实现一个类似React.createCentext的Provide标签,注入store,将子组件包裹进去。作用就是所有的组件都变成了它的子组件,后续就可以直接获取这个上下文。
class Provider extends React.Component {
render() {
return (
<ReactContext.Provider value={this.props.store}>
{this.props.children}
</ReactContext.Provider>
)
}
}
当然了,首先要在首部声明一个上下文:
const ReactContext = React.createContext();
connect
connect作用就是将redux的数据和react组件联系到一起。
1.将组件需要的store的值合并props中
2.将派发的action方法,包装一层dispatch,也合并到props中
3.connect内部的副作用注册更新props方法,不需要再手动去触发页面更新了
const connect = (mapStateToProps, mapDispatchToProps) => Comp => props => {
// 获取当前上下文中的context,即Provider的参数store
let store = useContext(ReactContext);
const {dispatch, getState} = store;
// 获取所有state和dispatch
let getProps = () => {
let states = mapStateToProps(getState());
// 将dispatch绑定到各个action上
let dispatchs = bindActionCreators(mapDispatchToProps, dispatch);
return {
...states,
...dispatchs
}
}
const [multipleProps, setMultipleProps] = useState(getProps());
useEffect(() => {
store.subscribe(() => {
// 当store里的值变化了,重新计算props,props改变后触发组件render
setMultipleProps({
...multipleProps,
...getProps()
})
})
}, [])
return <Comp {...props} {...multipleProps} />
}
const bindActionCreators = (actions, dispatch) => {
let creators = {};
Object.keys(actions).map(key => {
creators[key] = (arg) => dispatch(actions[key](arg))
})
return creators
}
以上方法就是把
const add = () => ({type: 'add'})
变成了
add: (arg) => dispatch(add(arg))
相比较来看,个人觉得react-redux实现要比redux简单一些,主要是redux的中间件有点绕。