Redux源码系列(一)从一个store开始

454 阅读3分钟

简单的API

回忆一下创建一个store的方式

import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer)

store中包含有以下方法:

  • getState() 获取state
  • dispatch(action) 更新state
  • subscribe() 注册监听器

通过subscribe添加事件监听,dispatch更改state,触发事件,达到我们的目的,这看起来是不是就像是一个订阅发布系统?自信一点,把像去掉

createStore

createStore主要作用,保存state,返回三个方法

const createStore = (reducer) => {
  let state;
  const getState = () => state;
  const subscribe = (listener) => {
    //
  }
  const dispatch = (action) => {
    //
  }
  return {
    getState,
    subscribe,
    dispatch,
  }
}

getState 方法最简单,作用就是将state直接返回

subscribe

这里我们需要使用一个listeners数组来存储所有的订阅,subscribe的作用就是将传入的listener加入该数组,注意subscribe是有返回值的,用来取消订阅

const subscribe = (listener) => {
  listeners.push(listener);
  return () => {
    const index = listeners.indexOf(listener);
    listeners.splice(index, 1);
  }
}

dispatch

调用reducer更新state,并触发所有回调函数

const dispatch = (action) => {
  state = reducer(state, action);
  listeners.forEach(listener => listener());
}

我们还需要在createStore加上一个dispatch({})用作初始化state

combinReducers

在写之前我们看看他的用法吧

import { createStore, combineReducers } from 'redux';

const nameReducer = (prevState, action) => {
  switch (action.type) {
    case 'CHANGE_NAME':
      return action.payload;
    default:
      return 'jack';
  }
};

const ageReducer = (prevState, action) => {
  switch (action.type) {
    case 'INCREA_AGE':
      return prevState + 1;
    case 'DECREA_AGE':
      return prevState - 1;
    default:
      return 18;
  }
}

const store = createStore(combineReducers({ name: nameReducer, age: ageReducer }));
console.log(store.getState().name) // jack

他接收一个对象,对象的key表示最后在store中储存的值得key,value则是处理这个值得reducer;这个reducer中拿到的prevState为自身的值。

createStore将combineReducers的返回值的作为第一个参数,这里可以看出combineReducers为一个高阶函数,返回值是一个归总的reducer,可以推导出大致结构

const combineReducers = (reducers) => {
  return (state = {}, action) => {
	///
  }
}

内部返回的这个reducer,我们需要将reducers的key进行遍历,然后执行相应的reducer,最后改变state的值返回;

const combineReducers = (reducers) => {
  return (state = {}, action) => {
    const nextState = {};
    Object.keys(reducers).forEach((key) => {
      const prevState = state[key];
      const currentReducers = reducers[key];
      nextState[key] = currentReducers(prevState, action);
    });
    return nextState;
  }
}

完成并使用

那么redux基本的几个api就实现了,搭配上react检验一波,reducer我们使用上面使用过的name和age

const nameReducer = (prevState, action) => {
  switch (action.type) {
    case 'CHANGE_NAME':
      return action.payload;
    default:
      return 'jack';
  }
};
const ageReducer = (prevState, action) => {
  switch (action.type) {
    case 'INCREA_AGE':
      return prevState + 1;
    case 'DECREA_AGE':
      return prevState - 1;
    default:
      return 18;
  }
}
const rootReducer = combineReducers({ name: nameReducer, age: ageReducer });
const store = createStore(rootReducer);
const Hello = () => {
  const state = store.getState();
  const dispatch = store.dispatch;
  return (
    <div>
      <p>name: {state.name}----- age: {state.age}</p>
      <button onClick={() => dispatch({ type: 'INCREA_AGE' })}>年龄+</button>
      <button onClick={() => dispatch({ type: 'DECREA_AGE' })}>年龄-</button>
    </div>
  )
}

const render = () => {
  ReactDom.render(<Hello />, document.getElementById('root'));
}

store.subscribe(render);

render();

完美运行,界面如下

总结

本章对总结了redux基本的几个api,实现思路和源码一致,细节上扔有差异,主要体现在

  • 省去了异常处理
  • 源码初始化为触发一个私有的action type -- @@redux/INIT 本文直接使用 dispatch({}) 替代
  • replaceReducer 因为个人开发使用较少,且实现简单,基本就是当前的 reducer 替换为传入的 replaceReducer
  • redux 新版本已转为ts

redux源码很少,且简单易懂,推荐大家去github阅读源码

文中示例代码目录地址:github.com/Yang-yu-don…

系列文章

Redux源码系列(一)从一个store开始

Redux源码系列(二)出彩的设计:中间件