最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写redux核心原理

    正文概述 掘金(Sonic)   2021-07-13   644

    本文从零实现一个简单的 redux ,主要内容在于redux 的设计思路及实现原理

    redux 是一个状态管理器,里面存放着数据,比如我们创建 store.js,在里面我们存放着这些数据,只需要在任何地方引用这个文件就可以拿到对应的状态值:

    let state = {
      count: 1
    }
    
    console.log(state.count)
    state.count = 2
    

    复制代码现在我们实现了状态(计数)的修改和使用了!当然上面的有一个很明显的问题:这个状态管理器只能管理 count,修改 count 之后,使用 count 的地方不能收到通知。

    实现 subscribe

    我们可以使用发布-订阅模式来解决这个问题。我们用个函数封装一下 redux

    function createStore(initState) {
      let state = initState
      let listeners = []
    
      /* 订阅函数 */
      function subscribe(listener) {
        listeners.push(listener)
      }
    
      function changeState(newState) {
        state = newState
        /* 执行通知 */
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
      }
    
      function getState() {
        return state
      }
    
      return { subscribe, changeState, getState }
    }
    

    这里我们完成了一个简单的状态管理器。state 的数据可以自由的定, 我们修改状态,在订阅的地方监听变化,可以实现监听。

    let initState = {
      count: 1,
      detail: {
        age: 24
      }
    }
    
    let store = createStore(initState)
    
    store.subscribe(() => {
      let state = store.getState()
      console.log('t1: ', state)
    })
    
    store.changeState({ ...store.getState(), count: store.getState().count + 1 })
    
    // t1:  { count: 2, detail: { age: 24 } }
    

    这里需要理解的是 createStore,提供了 changeStategetStatesubscribe 三个能力。

    在上面的函数中,我们调用 store.changeState 可以改变 state 的值,这样就存在很大的弊端了。比如 store.changeState({})

    我们一不小心就会把 store 的数据清空,或者误修改了其他组件的数据,那显然不太安全,出错了也很难排查,因此
    我们需要有条件地操作 store,防止使用者直接修改 store 的数据。
    

    因此,我们需要一个约束来修改 state 的值,而不允许意外的情况来将 state 的值清空或者误操作。分两步来解决这个问题:

    • 1. dispatch: 制定一个 state 修改计划,告诉 store,我的修改计划是什么。

    • 2. reducer: 修改 store.changeState 方法,告诉它修改 state 的时候,按照我们的计划修改。

    我们将 store.changeState 改写为 store.dispatch, 在函数中传递多一个 reducer 函数来约束状态值的修改。

    实现 reducer

    reducer 是一个纯函数,接受一个 state, 返回新的 state

    function createStore(reducer, initState) {
      let state = initState
      let listeners = []
    
      /* 订阅函数 */
      function subscribe(listener) {
        listeners.push(listener)
      }
    
      /* state 值的修改 */
      function dispatch(action) {
        state = reducer(state, action)
        /* 执行通知 */
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
      }
    
      function getState() {
        return state
      }
    
      return { subscribe, dispatch, getState }
    }
    

    我们来尝试使用 dispatchreducer 来实现自增和自减

    let initState = {
      count: 1,
      detail: {
        age: 24
      }
    }
    
    function reducer(state, action) {
      switch (action.type) {
        case 'INCREMENT':
          return { ...state, count: state.count + 1 }
        case 'DECREMENT':
          return { ...state, count: state.count - 1 }
        default:
          return state
      }
    }
    
    let store = createStore(reducer, initState)
    
    store.subscribe(() => {
      let state = store.getState()
      console.log('t1: ', state)
    })
    
    store.dispatch({ type: 'INCREMENT' }) // 自增
    store.dispatch({ type: 'DECREMENT' }) // 自减
    store.dispatch({ count: 2 }) // 计划外:不生效
    

    我们知道 reducer 是一个约束函数,接收老的 state,按计划返回新的 state。那我们项目中,有大量的 state,每个 state 都需要约束函数,如果所有的计划写在一个 reducer 函数里面,会导致 reducer 函数及其庞大复杂。所以我们需要将封装 combineReducers 来优化 reducer 函数。

    实现 combineReducers

    粒子化 reducer

    • 传入对象参数,key 值即为 state 状态树的 key 值, value 为对应的 reducer 函数。

    • 遍历对象参数,执行每一个 reducer 函数,传入 state[key], 函数获得每个 reducer 最新的 state 值。

    • 耦合 state 的值, 并返回。返回合并后的新的 reducer 函数。

    function combineReducers(reducers) {
      const reducerKeys = Object.keys(reducers)
    
      /*返回合并后的新的reducer函数*/
      return function combination(state = {}, action) {
        /*生成的新的state*/
        const nextState = {}
    
        /*遍历执行所有的reducers,整合成为一个新的state*/
        for (let i = 0; i < reducerKeys.length; i++) {
          const key = reducerKeys[i]
          const reducer = reducers[key]
          /*之前的 key 的 state*/
          const previousStateForKey = state[key]
          /*执行 分 reducer,获得新的state*/
          const nextStateForKey = reducer(previousStateForKey, action)
    
          nextState[key] = nextStateForKey
        }
        return nextState
      }
    }
    
    //使用 combineReducers:
    let state = {
      counter: { count: 0 },
      detail: { age: 24 }
    }
    
    function counterReducer(state, action) {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 }
        default:
          return state
      }
    }
    
    function detailReducer(state, action) {
      switch (action.type) {
        case 'INCREMENT-AGE':
          return { age: state.age + 1 }
        default:
          return state
      }
    }
    
    const reducers = combineReducers({
      counter: counterReducer,
      info: detailReducer
    })
    
    let store = createStore(reducers, initState)
    
    store.subscribe(() => {
      let state = store.getState()
      console.log('t1: ', state)
    })
    
    store.dispatch({ type: 'INCREMENT' })
    store.dispatch({ type: 'INCREMENT-AGE' })
    

    我们把 reducer 按组件维度拆分了,通过 combineReducers 合并了起来。 但是还有个问题, state 我们还是写在一起的,这样会造成 state 树很庞大,不直观,很难维护。我们需要拆分,一个 state,一个 reducer 写一块。

    粒子化 state

    改写 combineReducers 函数,在 createStore 函数中执行 dispatch({ type: Symbol() })

    function createStore(reducer, initState) {
      let state = initState
      let listeners = []
    
      /* 订阅函数 */
      function subscribe(listener) {
        listeners.push(listener)
      }
    
      function dispatch(action) {
        state = reducer(state, action)
        /* 执行通知 */
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
      }
    
      /* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */
      dispatch({ type: Symbol() })
    
      function getState() {
        return state
      }
    
      return { subscribe, dispatch, getState }
    }
    
    //将 state 分别传入各自的 reducer:
    function counterReducer(state = { count: 1 }, action) {
      //...
    }
    
    function detailReducer(state = { age: 24 }, action) {
      //...
    }
    
    // 合并 reducer
    const reducers = combineReducers({
      counter: counterReducer,
      info: infoReducer
    })
    
    // 移除 initState
    let store = createStore(reducers)
    
    console.log(store.getState()) // { counter: { count: 1 }, detail: { age: 24 } }
    
    • createStore 的时候,用一个不匹配任何 typeaction,来触发 state = reducer(state, action)
    • 因为 action.type 不匹配,每个子 reducer 都会进到 default 项,返回自己初始化的 state,这样就获得了初始化的 state 树了。

    起源地下载网 » 手写redux核心原理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元