「 React 」系列,手动实现一个 redux 与 react-redux

1,540 阅读8分钟

一、实现一个简易的 Redux

由于公司项目的技术栈是 React,与之配套的公共状态管理的库是 Redux,最近也研究了其中的原理。由于我之前是硬背 Redux 的用法,时间搁久了总是忘记如何使用。每次要用的时候,就去翻文档,不仅效率低下,用起来也感觉到恶心自己了。搞清楚了背后的机制,写起来就很顺手。相信大家弄懂的基本的实现细节,用起来也会得心应手。

首先思考 Redux 解决了什么问题,为什么要使用它。每当有一些数据要在很多组件中使用的时候,而这些组件跨层级时,props 就不再适合。就需要把这些数据提取出来放在一个模块,我们把它称为公共状态,每当数据改变时,每当数据改变时,它会通知订阅它的说有组件。

要想实现这样的共享的数据,首先想到的必是建一个全局模块,里面存放很多公共的数据。就像这样

const obj = {
   count: 0
}

当状态需要改变的时候,直接显示的赋值,当然,这里要通过观察者模式数据已经改变。不过这样肯定不好

  • 容易误操作,试想我一不小心清空了对象,会影响其他组件都受到污染,肯定要有条件的改变公共状态
  • 不易排查错误

1、 实现 getState

既然是共享数据,外部又不能直接改变它,当然就是一个闭包啦。没错,Redux 内部这样的一个机制,通过 store 的一些方法,我们可以搭出大致的骨架

function createStore({
  // 公共的状态
  let currentState = {}
  
  // getter
  function getState({
    return currentState
  } 
  
  // setter
  function dispatch({   
  } 
  
  function subscribe({
  } 
  
  return { 
    getState,
    dispatch,
    subscribe
  }
}

毫无疑问,每次调用 getState 就可以拿到最新的 currentState 的值

2、实现 dispatch

在 Redux 里,每当要改变数据,都是调用 dispatch 一个 action,根据 actionType 的值来返回不同的 state,好了,我们就可以这样写

function createStore({
  // 公共的状态
  let currentState = {}
  
  // getter
  function getState({
    return currentState
  }
  
  // setter
  function dispatch(action
    swicth(action.type) {
      case "increament":
        return {
          ...currentState,
          count: currentState.count + 1
        }
      default:
        return currentState
    }
  }  
  
  function subscribe({
  }
  
  return { 
    getState,
    dispatch,
    subscribe
  }
}

这样就实现了一个 dispatch,但这样写不好,我们把 switch 这样的逻辑可以提取成一个函数,这也是所说的 reducer,那么,代码就成了这样

const initialState = {
  count1
}

function reducer(state = initialState, {type, payload}{
  switch (type) {
    case 'increament':
      return {
        ...state,
        count: count + payload
      }
    case 'decrement':
      return {
        ...state,
        count: count - payload
      }
    default:
      return initialState
  }
}

function createStore({
  // 公共的状态
  let currentState = {}
  
  // getter
  function getState({
    return currentState
  }
  // setter
  function dispatch(action
    currentState = reducer(currentState, action)
  }
  function subscribe({
  }  
  return { 
    getState,
    dispatch,
    subscribe
  }
}

在我们创建 store 的时候,会初始化一次 state,也就是内部会 dispatch 一次,因而

function createStore({
  // 公共的状态
  let currentState = {}
  
  // getter
  function getState({
    return currentState
  }
  
  // setter
  function dispatch(action
    currentState = reducer(currentState, action)
  }
  dispatch({ type'@@REDUX_INIT' })
  
  function subscribe({
  }
  
  return { 
    getState,
    dispatch,
    subscribe
  }
}

到此,即完成了获取和改变状态的功能,验证一下代码

const store = createStore(reducer)
console.log(store.getState().count)   // 1
store.dispatch({type: 'increament', payload: 5})
console.log(store.getState().count)   // 6

尽管如此,当数据改变了,还是不能通知我视图更新。这里需要监听数据的变化,用到了观察者模式,也有人说是发布订阅模式,这两种模式基本的思想一致,但还是有一些区别的,这里观察者模式更加准确。状态数据相当于被观察者,在 createStore 中,我们要维护一个观察者的队列,当执行 subscribe时,把他们放入队列,dispatch的时候,执行队列里的回调

function createStore({
  // 公共的状态
  let currentState = {}
  
  // 存放观察者的队列
  let observers = []
  
  // getter
  function getState({
    return currentState
  }
  
  // setter
  function dispatch(action
    currentState = reducer(currentState, action)

    observers.foreach(fn => fn())
  }
  dispatch({ type'@@REDUX_INIT' })
  
  function subscribe(fn{
    observers.push(fn)
  }
  
  return { 
    getState,
    dispatch,
    subscribe
  }
}

如此以来,就完成了数据变化,通知组件改变视图的功能,模拟组件运行代码

const store = createStore(reducer)
store.subscribe(() => console.log('组件1收到通知', store.getState()))
store.subscribe(() => console.log('组件2收到通知', store.getState()))
store.dispatch({ type: 'increament', payload: 5 })

一个最简单的 redux 的应用已经完成,这里还缺饭了大量的参数类型判断,异常处理,异步action、中间件等等。但基本的原理大致相同

二、实现 react-redux

在 react 的项目中,尽管 redux 能实现我们的需求,但写法太过冗余,和 react 的组件写法也不太契合,于是,就有了 react-redux。简单来说,它提供了两大功能:

  • Provider 组件
  • connect 高阶函数

Provider原理是把 store 绑定在 context 上,至于 context,可以为子孙组件的上下文提供 store 对象

class Provider extends Component {
    static childContextTypes = {
        store: PropTypes.shape({
            subscribe: PropTypes.func.isRequired,
            dispatch: PropTypes.func.isRequired,
            getState: PropTypes.func.isRequired
        }).isRequired
    }
    
    constructor(props) {
        super(props);
        this.store = props.store;
    }

    getChildContext() {
        return {
            storethis.store
        }
    }

    render() {
        return this.props.children
    }
}

connect 是一个高阶函数,接收 mapStateToProps, mapDispatchToProps 为参数,并且它返回一个高阶组件。其中,这两个参数都是函数,mapStateToProps 接收 state 返回一个对象,这搞清楚了,mapDispatchToProps 接收一个 dispatch 返回一个对象,我们再来实现它:

// 形如 mapTostate、mapDispatchToProps
const mapStateToProps = state => {}
const mapDispatchToProps = dispatch => {}

function connect(mapStateToProps, mapDispatchToProps{
  return function(WrappedComponent{
    class Connect extends Component{
    
      constructor(props) {
        super(props)
        this.state = mapStateToProps(store.getState())
        this.mapDispatch = mapDispatchToProps(store.dispacth)
      }
      
      ComponentDidMount() {
        
      }
      
      <WrappedComponent {...this.props} {this.state} {this.mapDispatch} />
    }
    return Connect
  }
}

知道这里,我们通过高阶函数把一些属性挂载到了高阶组件的 props 上,接下来就可以通过 this.props.xxx 调用。此时,我们被 connect 包裹的新组件的 props 上虽然有了值,但是还不具备自动更新的功能,继续改进 connect

function connect(mapStateToProps, mapDispatchToProps{
  return function(WrappedComponent{
    class Connect extends Component{
    
      constructor(props) {
        super(props)
        this.state = mapStateToProps(store.getState())
        this.mapDispatch = mapDispatchToProps(store.dispacth)
      }
      
      ComponentDidMount() {
       //  如果 store 中的状态改变会执行回调函数,此时新获取的 mappedState 和旧的做对比,如若有变化,就setState
        this.unsub = store.subscribe(() => {
        const mappedState = mapStateToProps(store.getState());
        if(shallowEqual(this.state, mappedState)) {
            return;
        }
        this.setState(mappedState);
    });
      }
      
      <WrappedComponent {...this.props} {this.state} {this.mapDispatch} />
    }
    return Connect
  }
}

如此,一个基本功能的 react-redux 已经实现。