写点什么

深度理解 Redux 原理并实现一个 redux

  • 2022-11-01
    浙江
  • 本文字数:5360 字

    阅读完需:约 18 分钟

Redux 的作用是什么

Redux的作用在于实现状态传递状态管理。在这里你可能会说了,如果是状态传递,那我props的传递不也是可以达到这样的效果吗?context上下文方案不也是可以达到这样的效果吗?没错,是这样的,但是上述的两种方案是有局限性的。


  • props方案只适用于父子组件传递状态。

  • context上下文方案虽然能够在根组件上定义上下文,但是有两种缺陷

  • 只要上下文里面的状态发生改变,就会重新渲染整个组件树,进而会产生庞大的性能开销。

  • 组件的逻辑与状态的耦合度太高,不利于解耦,也就是无法实现对状态的统一管理。


既然Redux的作用是对状态的管理与传递,那么他的作用场景呢?当然了你可以根据上面说的两种方案对Redux的使用做取舍,Redux的本质就是全局变量被协调管理。


  • 如果涉及多个状态,且多个状态会被多个组件使用到,比如商城购物车场景,你就可以毫不犹豫的考虑用Redux

  • 如果涉及多个状态,但是状态虽多但是是用的组件唯一,或者有关联关系的组件使用,你就大可不必使用Redux,如果状态不是那么多,那就更不必使用Redux了。


除此之外,Redux还有一个优点就是,不仅仅是React本身能够使用,就连别的框架,比如jQuerykerry_domvue等都可以使用,但是对比于vue来讲的话,vue 有自己比较好的的状态管理库vuex,好了废话不多说了,我们先来看看Redux在项目中是如何是用的。

Redux 的使用

// store.jsimport { createStore } from "redux";import reducer from "./reducer";
export default createStore(reducer);

// reducer.jsimport {cloneDeep} from 'lodash';const initilaValue = { count: 0};
const reducer = (state = initilaValue, action) => { state = cloneDeep(state) const { type, payload } = action; switch (type) { case "add": state.count += payload; break; case "reduce": state.count -= payload; break default: } return state;};
export default reducer;
// App.jsimport React, {Component} from 'react';import store from "./store";
export default class App extends Component { componentDidMount() { //reducer不会触发页面变化,需要state来触发 store.subscribe(() =>{ this.setState(store.getState()) }) }
render() { //获取reducer数据 const {count} = store.getState() return ( <div> <div type='primary' onClick={this.reduce}>-</div> <span>{count}</span> <div type='primary' onClick={this.add}>+</div> </div> ); } reduce = () => { //通知reducer页面数据变化了 store.dispatch({ type: 'reduce', payload: 1 }) } add = () => { //通知reducer页面数据变化了 store.dispatch({ type: 'add', payload: 1 }) }}
复制代码


上述代码就可以实现count的加减计算了,我们可以看到有几处需要注意的地方。


  • store.js文件里面的createStore

  • reducer.js文件里面的cloneDeepreturn statestate = initialValue

  • App.js文件里面的dispatchgetStatetypepayload


很明显createStore的作用就是创建仓库,getState为取得当前的state值,dispatch为某个操作之后派发给store去更新某个statetype为具体的某种交互,payload为每次交互的具体内容。各位同学可以看得到我在reducer中做了一次state的深克隆,这是为什么呢?是因为在每一次的action中我们拿到的是同一个state内存地址,我们的期望是不管你在switch中如何更改state但是我不希望在这一步就改变了公共状态中的count,只有在我return的时候才会去更改真正的公共状态,也就是说reducer函数执行产生的私有闭包里面的公共状态信息。而state = initialValue这一步的操作就是第一次派发的时候,reducer接收的state为空,我们把基础值赋给它。了解了这么多,我们还是去看一下他的源码是如何实现的吧。

Redux 的源码

//Redux/redux/src/index.tsexport {  createStore, // 创建仓库api  combineReducers, // 合并Reducer  bindActionCreators, // 转化action  applyMiddleware, // 中间件应用方案  compose,// 函数组合  __DO_NOT_USE__ActionTypes}
复制代码


上面的index.ts文件夹里面暴露出了几个api,我们主要针对createStore看看。


export default function createStore(  reducer: Reducer<S, A>,  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,  enhancer?: StoreEnhancer<Ext, StateExt>): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {  // createStore函数不支持第二三四参数为函数  // 详见https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers  if (    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||    (typeof enhancer === 'function' && typeof arguments[3] === 'function')  ) {    throw new Error(      ...    )  }
// 第二参数是函数,且第三参数不传 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState as StoreEnhancer<Ext, StateExt> preloadedState = undefined }
// 有第三参数且不是函数 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error( `Expected the enhancer to be a function. Instead, received: '${kindOf( enhancer )}'` ) }
return enhancer(createStore)( reducer, preloadedState as PreloadedState<S> ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext }
// 如果reducer不是函数,要报错 if (typeof reducer !== 'function') { throw new Error( `Expected the root reducer to be a function. Instead, received: '${kindOf( reducer )}'` ) }
let currentReducer = reducer // 当前的reducer let currentState = preloadedState as S // 当前的state let currentListeners: (() => void)[] | null = [] // 事件池 let nextListeners = currentListeners let isDispatching = false // 正在派发
/** * This makes a shallow copy of currentListeners so we can use * nextListeners as a temporary list while dispatching. * * This prevents any bugs around consumers calling * subscribe/unsubscribe in the middle of a dispatch. */ function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
// 返回当前的state function getState(): S { if (isDispatching) { throw new Error( ... ) } return currentState as S }
//向事件池中添加更新事件 function subscribe(listener: () => void) { // 校验是不是函数 if (typeof listener !== 'function') { throw new Error( ... ) }
if (isDispatching) { throw new Error( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.' ) }
let isSubscribed = true //避免重复添加 ensureCanMutateNextListeners() nextListeners.push(listener)
//subscribe函数每执行一次,都会返回一个unsubscribe用来从事件池中移除当前事件 return function unsubscribe() { if (!isSubscribed) { return }
if (isDispatching) { throw new Error( ... ) }
isSubscribed = false
ensureCanMutateNextListeners() // 移除当前事件 const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) currentListeners = null } }
// 派发函数 function dispatch(action: A) { // 如果传过来的action不是对象报错 if (!isPlainObject(action)) { throw new Error( ... ) }
//每一个action中都需要一个type字段,没有就报错 if (typeof action.type === 'undefined') { throw new Error( ... ) }
//正在派发中.. if (isDispatching) { throw new Error('Reducers may not dispatch actions.') }
try { isDispatching = true // 执行reducer,改变state currentState = currentReducer(currentState, action) } finally { isDispatching = false }
// dispatch通知事件池去执行事件,遍历执行 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
return action }
// 替换replaceReducer function replaceReducer<NewState, NewActions extends A>( nextReducer: Reducer<NewState, NewActions> ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
// 入参不是函数,报错 if (typeof nextReducer !== 'function') { throw new Error( ... ) }
... // 刚开始进来要派发一次,同步state,其中{ type: ActionTypes.REPLACE }为唯一标识 // 倘若不为唯一标识的话,那可能一开始就破坏了状态value dispatch({ type: ActionTypes.REPLACE } as A) // change the type of the store by casting it to the new store return store as unknown as Store< ExtendState<NewState, StateExt>, NewActions, StateExt, Ext > & Ext }
...
// 初始化store的时候,需要派发一次同步state dispatch({ type: ActionTypes.INIT } as A)
const store = { dispatch: dispatch as Dispatch<A>, subscribe, getState, replaceReducer, [$$observable]: observable } as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
// 返回仓库 const store = createStore({count:0}) return store}
复制代码


参考 React 实战视频讲解:进入学习


确实短短几百行代码实现了redux,为此我们也来实现一个简易版的redux表示敬意,我们的redux只实现getStatedispatchcreateStore方法。


// myReduximport {cloneDeep} from 'lodash'export function createStore(reducer) {  if(typeof reducer !== 'function') {    throw new Error('reducer must be an function')  }
let state, listeners = [];
const getState = () => { // 深克隆一个state return cloneDeep(state); }
const subscribe = (listener) => { if(typeof listener !== 'function'){ throw new Error('listener must be an function') }
//去重 if(!listeners.includes(listener)){ listeners.push(listener) } // 源码里面执行subscribe,返回一个卸载函数 return function unsubscribe(){ listeners.filter(action => action!== listener) } }

const dispatch = (action) => { if(typeof action !== 'object' && action !== null){ throw new Error('action must be a object') }
// 判断有没有type if(typeof action.type === undefined){ throw new Error('action.type must existence') }
//执行 try { state = reducer(state, action) } catch (error) { //... }
// 通知事件池中的方法执行 listeners.forEach(listener=>{ if(typeof listener === 'function'){ listener(); } }) }
//第一次进来要派发一次,同步初始状态 dispatch({ type:typeof Symbol !== undefined ? Symbol('ABC') : '唯一值' })
// 暴露出方法 return { getState, dispatch, subscribe }}
复制代码


各位可以去 codeSandBox 上面去实践一下,当然了这只是简易版的redux,官方推荐使用 react-redux 来进行实际的项目开发,因为他只关注于数据管理。

总结

redux的大致工作流如此:



用户头像

还未添加个人签名 2022-09-05 加入

还未添加个人简介

评论

发布
暂无评论
深度理解Redux原理并实现一个redux_React_夏天的味道123_InfoQ写作社区