Skip to content

redux基本原理简易版 #36

@CodeDreamfy

Description

@CodeDreamfy

Redux

首先是一个状态管理

该文档是跟着九库文档学习的:

假设有一个对象

let state = {
	count: 1
}

修改状态

state.count = 2;

如果我们希望修改后能够被通知呢?那么此时就需要发布订阅模式来解决

let state = {
  count: 1
}
let listeners = [];

function subscribe(listener) {
  listener.push(listener)
}

function changeState(count) {
  state.count = count;
  for(let i = 0; i <listeners.length; i++) {
    const listener = listeners[i];
    listener();
  }
}

目前算是可以了,但是存在两个问题:

  • 状态管理器只能管理count,不通用
  • 公共的代码要封装起来

可以尝试封装下:

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
  }
}

使用一下:

let initState = {
  counter: {
    count: 0,
  },
  info: {
    name: ''
  }
}
let store = createStore(initState);

store.subscribe(() => {
  console.log('state', store.getState());
})

store.changeState({
  ...store.getState(),
  info: {
    name: 'test'
  }
})

于是一个简单的状态管理雏形就有了:createStore、changeState、getState

但是上面的函数还存在一个问题,就是可以随意改动状态的值,为此我们需要分两步来解决这个问题:

  • 制定一个state修改计划,告诉store修改计划是什么
  • 修改store.changeState方法,告诉它修改state的时候按照我们的计划修改

设置一个plan函数,用来接收现有state和一个action要执行的操作,返回经过改变后的state

function plan(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default: 
      return state;
  }
}

然后我们将这个计划告诉store,store.changeState以后改变要按照我们的计划修改

function createStore(plan, initState) {
  let state = initState;
  let listeners = [];
  
  function subscribe(listeners) {
    listeners.push(listener);
  }
  
  function changeState(action) {
    state = plan(state, action);
    for(let i = 0; i < listeners.length; i ++) {
      const listener = listeners[i];
      listener();
    }
  }
  
  function getState() {
    return state;
  }

  return {
    subscribe,
    changeState,
    getState
  }
}

尝试使用

let initState = {
  count: 0
}
let store = createStore(plan, initState);

store.subscribe(() => {
  let state = store.getState();
  console.log(state.count);
})

store.changeState({
  type: 'INCREMENT'
})

到这里,我们给plan和changeState换一个名字,plan改为reducer,changeState改成dispatch

到这里我们会发现reducer的问题:项目中我们有大量的state, 每个state都需要计划函数,如果全部写在一起无法管理,所有的计划写在一个reducer函数里面,会导致reduer函数及其庞大复杂。按照组件维度会拆分出多个reducer函数,然后通过一个函数合并起来。

我们来管理两个state,一个counter,一个info。

let state = {
  counter: {
    count: 0
  },
  info: {
    name: 'xxx'
  }
}

它们各自的reducer

/* counterReducer, 一个子reducer,接收的 state 是 state.counter */
function counterRenducer(state, action) {
  switch(action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      }
    case: 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state;
  }
}

/* InfoReducer 子reducer 接收 state 是 state.info */
function InfoReducer(state, action) {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name
      }
    case 'SET_DESCRIPTION':
      return {
        ...state,
        description: action.description
      }
    default:
      return state;
  }
}

我们需要一个函数来合并多个reducer函数合并成一个reducer函数

function combineReducers(reducers) {
  const reducersKeys = Object.keys(reducers); // [ 'counter', 'info' ]
  
  return combination(state = {}, action) {
    // 生成新的state
    const nextState = {};
    for(let i = 0; i < reducersKeys.length; i++) {
      const key = reducerskeys[i];
      const reducer = reducer[i];
      const previousStateForKey = state[key];
      const nextStateForKey = reducer(previousStateForKey, action);
      nextState[key] = nextStateForKey;
    }
    return nextState;
  }
}

// 使用
const reducer = combineReducers({
  counter: counterReducer,
  info: InfoReducer
})

ok,我们已经把reducer按维度拆分了,通过combineReducers合并起来。但是state还是写在一起,会造成state树很庞大,因此需要对state拆分

// counter 自己的 state 和 reducer 写在一起
let initState = {
  count: 0
}
function counterReducer(state, action) {
  // 如果state没有初始值,那就给他初始值
  if(!state) {
    state = initState;
  }
  switch(action.type) {
      // ...code
  }
}
// createStore函数需要修改下
function dispatch(action) {
  state = reducer(state, action);
}

// 用来获取不匹配任何计划的type来获取初始值
dispatch({ type: Symbol() })

ok,目前为止我们已经有一个基本完整的状态管理流程了,但是如果我们想要对dispatch进行扩展或者重写,增强dispatch的功能的

假设我们有一个记录日志的中间件

const store = createStore(reducer);
const next  = store.dispath;

// 重写dispatch
store.dispatch = (action) => {
  console.log('this state', store.getState());
  next(action);
}

// 使用
store.dispatch({
  type: 'INCREMENT'
})

还有一个记录异常的中间件

const store = createStore(reducer);
const next  = store.dispath;

store.dispatch = (action) => {
  try {
    next(action);
  } catch (err) {
    console.error('错误': err)
  }
}

如果需要多个中间件进行合作呢?

store.dispatch = (action) => {
  try {
    console.log('this state', store.getState());
    console.log('action', action);
    next(action);
  } catch {
    console.log('错误', err)
  }
}

看起来我们将两个中间件合并了,如果后面中间件变多了怎么处理?

这个时候我们就需要将每个中间件单独提取出来

// logger middleware
const loggerMiddleware = (next) => (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
}

// exception middleware
const exceptionMiddleware = (next) => (action) => {
  try {
    next(action);
  } catch(err) {
    console.log('错误', err);
  }
}

// 使用
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = exceptionMiddleware(loggerMiddleware(next));

这个时候如果我们想要把js文件独立出去,会发现一些中间件依赖store,那么就需要再封装一次了~

const store = createStore(reducer);
const next = store.dispatch;

const loggerMiddleware = (store) => (next) => (action) => { // code... }
const exceptionMiddleware = (store) => (next) => (action) => { // code... }

const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));
  
// 单独文件
import loggerMiddleware from './middlewares/loggerMiddleware';
import exceptionMiddleware from './middlewares/exceptionMiddleware';
import timeMiddleware from './middlewares/timeMiddleware';

...

const store = createStore(reducer);
const next = store.dispatch;

const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));

这种虽然实现了中间件的独立,但是当中间件多的时候,还需要一个合并中间件的方法来统一管理

我们定义一个applyMiddleware方法

const applyMiddleware = (...middlewares) => (oldCreateStore) => (reducer, initState) => {
  // 生成初始store
  const store = oldCreateStore(reducer, initState);
  // 给每个middleware传store,
  // const chain = [exception, logger];
  const chain = middlewares.map(middleware => middleware(store));
  let dispatch = store.dispatch;
  // 实现 exception(logger(dispatch))
  // [a,b,c] => a(b(c))
  const reDispatch = chain.reduce((prev, curr) => (...arg) => prev(curr(...arg)));
  store.dispatch = reDispatch(store.dispatch);
  return store;
}

我们通过applyMiddleware复写了store的dispatch方法,返回了新的store,因此我们目前有两种store

// 没有中间件的createStore
import { createStore } from './redux';
const store = createStore(reducer, initState);

// 存在中间件的 createStore
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, loggerMiddleware);
const newCreateStore = rewriteCreateStoreFunc(createStore);
const store = newCreateStore(reducer, initState);

可以肯定不符合我们需要的,因此我们需要将createStore增加点判断兼容一下存在中间件的情况

const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
  if(rewriteCreateStoreFunc) {
    const newCreateStore = rewriteCreateStoreFunc(createStore);
    return newCreateStore(reducer, initState);
  }
}

最终用法如下:

const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, loggerMiddleware);

const store = createStore(reducer, initState, rewriteCreateStoreFunc);

完整的Redux

增加退订功能

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

// 退订
const unsubscribe = store.subscribe(() => {
  let state = store.getState();
  console.log(state.counter.count);
});
/*退订*/
unsubscribe();

省略initState

function craeteStore(reducer, initState, rewriteCreateStoreFunc){
  if (typeof initState === 'function'){
    rewriteCreateStoreFunc = initState;
    initState = undefined;
  }
  ...
}

compose

我们写的applyMiddleware中,把[A,B,C]转换成A(B(C(next))),是如下实现:

const chain = [A, B, C];
let dispatch = store.dispatch;
chain.reverse().map(middleware => {
  dispatch = middleware(dispatch);
})

而redux提供了compose方式

const chain = [A, B, C];
dispatch = compose(...chain)(store.dispatch);

export default function compose(...funcs) {
  if(funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

bindActionCreator

使用闭包,隐藏dispatch和actionCreator

const reducer = combineReducers({
  counter: counterReducer,
  info: infoReducer
})
const store = createStore(reducer);

// 返回 action 的函数叫做 actionCreator
function increment() {
  return {
    type: 'INCREMENT'
  }
}

function setName(name) {
  return {
    type: 'SET_NAME',
    name,
  }
}

const actions = {
  increment:  function() {
    return store.dispatch(increment.apply(this, arguments))
  },
  setName: function() {
    return store.dispatch(setName.apply(this, arguments))
  }
}
/* 其他地方在实现自增的时候,根本不知道 dispatch,actionCreator等细节 */
actions.increment(); // 自增
actions.setName('codedreamfy'); // 名称

简化一下

const actions = bindActionCreators({ increment, setName }, store.dispatch);

/* 核心的代码在这里,通过闭包隐藏了 actionCreator 和 dispatch */
function bindActionCreator(actionCreator, dispatch) {
	return () => dispatch(actionCreator.apply(this, arguments))
}

/* actionCreators 必须是 function 或者 object */
const bindActionCreators = (actionCreators, dispatch) => {
  if(typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch);
  }
  if(typeof actionCreator !== 'object' || actionCreator === null) {
    throw new Error()
  }
  
  const keys = Object.keys(actionCreator);
  const boundActionCreators = {};
  for(let i = 0; i< keys.length; i++){
    const key = keys[i];
    const actionCreator = actionCreators[key];
    if(typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
  }
  return boundActionCreator;
}

该源码参考来源于九库前端,在此感谢

Metadata

Metadata

Assignees

No one assigned

    Labels

    ReactReact相关ReduxRedux相关

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions