深入浅出讲解redux

SpringBoot 指标监控——Actuator 1、 访问:localhost:8000/actuator 设置健康监控显示具体情况 metrics http://localhost:8000/actuator/metrics/jvm.gc.live.data.size 定制化health信息 //利用构造器,在注册中心中添加cityService.saveCity.count指标 public CityServiceImpl(MeterRegistry meter... 阅读详情

转自brickspert专家:https://github.com/brickspert/blog/issues/22

redux实现了:

    状态数据的纯修改操作reducer,

    数据变化的订阅subscribe,

    数据修改reducer的wrap和对订阅的发布处理dispatch;

    在数据修改dispatch的基础上,实现修改的插件扩展middleware。

目录

  1. 前言
  2. 状态管理器
    • 简单的状态管理器
    • 有计划的状态管理器
  3. 多文件协作
    • reducer 的拆分和合并
    • state 的拆分和合并
  4. 中间件 middleware
  5. 完整的 redux
  6. 最佳实践
    • 纯函数
  7. 总结

 

前言

记得开始接触 react 技术栈的时候,最难理解的地方就是 redux。全是新名词:reducer、store、dispatch、middleware 等等,我就理解 state 一个名词。

网上找的 redux 文章,要不有一本书的厚度,要不很玄乎,晦涩难懂,越看越觉得难,越看越怕,信心都没有了!

花了很长时间熟悉 redux,慢慢的发现它其实真的很简单。本章不会把 redux 的各种概念,名词解释一遍,这样和其他教程没有任何区别,没有太大意义。我会带大家从零实现一个完整的 redux,让大家知其然,知其所以然。

开始前,你必须知道一些事情:

  • redux 和 react 没有关系,redux 可以用在任何框架中,忘掉 react。
  • connect 不属于 redux,它其实属于 react-redux,请先忘掉它,下一章节,我们会介绍它。
  • 请一定先忘记 reducer、store、dispatch、middleware 等等这些名词。
  • redux 是一个状态管理器。

Let's Go!

 

状态管理器

简单的状态管理器

redux 是一个状态管理器,那什么是状态呢?状态就是数据,比如计数器中的 count。

let state = {
  count: 1
}

我们来使用下状态

console.log(state.count);

我们来修改下状态

state.count = 2;

好了,现在我们实现了状态(计数)的修改和使用了。

读者:你当我傻吗?你说的这个谁不知道?捶你?!

笔者:哎哎哎,别打我!有话好好说!redux 核心就是这个呀!我们一步一步扩展开来嘛!

当然上面的有一个很明显的问题:修改 count 之后,使用 count 的地方不能收到通知。我们可以使用发布-订阅模式来解决这个问题。

/*------count 的发布订阅者实践------*/
let state = {
  count: 1
};
let listeners = [];

/*订阅*/
function subscribe(listener) {
  listeners.push(listener);
}

function changeCount(count) {
  state.count = count;
  /*当 count 改变的时候,我们要去通知所有的订阅者*/
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i];
    listener();
  }
}

我们来尝试使用下这个简单的计数状态管理器。

/*来订阅一下,当 count 改变的时候,我要实时输出新的值*/
subscribe(() => {
  console.log(state.count);
});

/*我们来修改下 state,当然我们不能直接去改 state 了,我们要通过 changeCount 来修改*/
changeCount(2);
changeCount(3);
changeCount(4);

现在我们可以看到,我们修改 count 的时候,会输出相应的 count 值。

现在有两个新的问题摆在我们面前

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

我们尝试来解决这个问题,把公共的代码封装起来

const createStore = function (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
  }
}

我们来使用这个状态管理器管理多个状态 counter 和 info 试试

let initState = {
  counter: {
    count: 0
  },
  info: {
    name: '',
    description: ''
  }
}

let store = createStore(initState);

store.subscribe(() => {
  let state = store.getState();
  console.log(`${state.info.name}:${state.info.description}`);
});
store.subscribe(() => {
  let state = store.getState();
  console.log(state.counter.count);
});

store.changeState({
  ...store.getState(),
  info: {
    name: '前端九部',
    description: '我们都是前端爱好者!'
  }
});

store.changeState({
  ...store.getState(),
  counter: {
    count: 1
  }
});

到这里我们完成了一个简单的状态管理器。

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

本小节完整源码见 demo-1

有计划的状态管理器

我们用上面的状态管理器来实现一个自增,自减的计数器。

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

store.subscribe(() => {
  let state = store.getState();
  console.log(state.count);
});
/*自增*/
store.changeState({
  count: store.getState().count + 1
});
/*自减*/
store.changeState({
  count: store.getState().count - 1
});
/*我想随便改*/
store.changeState({
  count: 'abc'
});

你一定发现了问题,count 被改成了字符串 abc,因为我们对 count 的修改没有任何约束,任何地方,任何人都可以修改。

我们需要约束,不允许计划外的 count 修改,我们只允许 count 自增和自减两种改变方式!

那我们分两步来解决这个问题

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

我们来设置一个 plan 函数,接收现在的 state,和一个 action,返回经过改变后的新的 state。

/*注意:action = {type:'',other:''}, action 必须有一个 type 属性*/
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 以后改变 state 要按照我的计划来改。

/*增加一个参数 plan*/
const createStore = function (plan, initState) {
  let state = initState;
  let listeners = [];

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

  function changeState(action) {
    /*请按照我的计划修改 state*/  
    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
  }
}

我们来尝试使用下新的 createStore 来实现自增和自减

let initState = {
  count: 0
}
/*把plan函数*/
let store = createStore(plan, initState);

store.subscribe(() => {
  let state = store.getState();
  console.log(state.count);
});
/*自增*/
store.changeState({
  type: 'INCREMENT'
});
/*自减*/
store.changeState({
  type: 'DECREMENT'
});
/*我想随便改 计划外的修改是无效的!*/
store.changeState({
  count: 'abc'
});

到这里为止,我们已经实现了一个有计划的状态管理器!

我们商量一下吧?我们给 plan 和 changeState 改下名字好不好?**plan 改成 reducer,changeState 改成 dispatch!**不管你同不同意,我都要换,因为新名字比较厉害(其实因为 redux 是这么叫的)!

本小节完整源码见 demo-2

 

多文件协作

reducer 的拆分和合并

这一小节我们来处理下 reducer 的问题。啥问题?

我们知道 reducer 是一个计划函数,接收老的 state,按计划返回新的 state。那我们项目中,有大量的 state,每个 state 都需要计划函数,如果全部写在一起会是啥样子呢?

所有的计划写在一个 reducer 函数里面,会导致 reducer 函数及其庞大复杂。按经验来说,我们肯定会按组件维度来拆分出很多个 reducer 函数,然后通过一个函数来把他们合并起来。

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

let state = {
  counter: {
    count: 0
  },
  info: {
    name: '前端九部',
    description: '我们都是前端爱好者!'
  }
}

他们各自的 reducer

/*counterReducer, 一个子reducer*/
/*注意:counterReducer 接收的 state 是 state.counter*/
function counterReducer(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*/
/*注意:InfoReducer 接收的 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;
  }
}

那我们用 combineReducers 函数来把多个 reducer 函数合并成一个 reducer 函数。大概这样用

const reducer = combineReducers({
    counter: counterReducer,
    info: InfoReducer
});

我们尝试实现下 combineReducers 函数

function combineReducers(reducers) {

  /* reducerKeys = ['counter', 'info']*/
  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 的威力吧

const reducer = combineReducers({
  counter: counterReducer,
  info: InfoReducer
});

let initState = {
  counter: {
    count: 0
  },
  info: {
    name: '前端九部',
    description: '我们都是前端爱好者!'
  }
}

let store = createStore(reducer, initState);

store.subscribe(() => {
  let state = store.getState();
  console.log(state.counter.count, state.info.name, state.info.description);
});
/*自增*/
store.dispatch({
  type: 'INCREMENT'
});

/*修改 name*/
store.dispatch({
  type: 'SET_NAME',
  name: '前端九部2号'
});

本小节完整源码见 demo-3

state 的拆分和合并

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

这一小节比较简单,我就不卖关子了,用法大概是这样(注意注释)

/* counter 自己的 state 和 reducer 写在一起*/
let initState = {
  count: 0
}
function counterReducer(state, action) {
  /*注意:如果 state 没有初始值,那就给他初始值!!*/  
  if (!state) {
      state = initState;
  }
  switch (action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      }
    default:    
      return state;
  }
}

我们修改下 createStore 函数,增加一行 dispatch({ type: Symbol() })

const createStore = function (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();
    }
  }

  function getState() {
    return state;
  }
  /* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */
  dispatch({ type: Symbol() })

  return {
    subscribe,
    dispatch,
    getState
  }
}

我们思考下这行可以带来什么效果?

  1. createStore 的时候,用一个不匹配任何 type 的 action,来触发 state = reducer(state, action)
  2. 因为 action.type 不匹配,每个子 reducer 都会进到 default 项,返回自己初始化的 state,这样就获得了初始化的 state 树了。

你可以试试

/*这里没有传 initState 哦 */
const store = createStore(reducer);
/*这里看看初始化的 state 是什么*/
console.dir(store.getState());

本小节完整源码见 demo-4

到这里为止,我们已经实现了一个七七八八的 redux 啦!

 

中间件 middleware

中间件 middleware 是 redux 中最难理解的地方。但是我挑战一下用最通俗的语言来讲明白它。如果你看完这一小节,还没明白中间件是什么,不知道如何写一个中间件,那就是我的锅了!

中间件是对 dispatch 的扩展,或者说重写,增强 dispatch 的功能!

记录日志

我现在有一个需求,在每次修改 state 的时候,记录下来 修改前的 state ,为什么修改了,以及修改后的 state。我们可以通过重写 store.dispatch 来实现,直接看代码

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

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

我们来使用下

store.dispatch({
  type: 'INCREMENT'
});

日志输出为

this state { counter: { count: 0 } }
action { type: 'INCREMENT' }
1
next state { counter: { count: 1 } }

现在我们已经实现了一个完美的记录 state 修改日志的功能!

记录异常

我又有一个需求,需要记录每次数据出错的原因,我们扩展下 dispatch

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

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

这样每次 dispatch 出异常的时候,我们都会记录下来。

多中间件的合作

我现在既需要记录日志,又需要记录异常,怎么办?当然很简单了,两个函数合起来呗!

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

如果又来一个需求怎么办?接着改 dispatch 函数?那再来10个需求呢?到时候 dispatch 函数肯定庞大混乱到无法维护了!这个方式不可取呀!

我们需要考虑如何实现扩展性很强的多中间件合作模式。

  1. 我们把 loggerMiddleware 提取出来

    const store = createStore(reducer);
    const next = store.dispatch;
    
    const loggerMiddleware = (action) => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    
    store.dispatch = (action) => {
      try {
        loggerMiddleware(action);
      } catch (err) {
        console.error('错误报告: ', err)
      }
    }
  2. 我们把 exceptionMiddleware 提取出来

    const exceptionMiddleware = (action) => {
      try {
        /*next(action)*/
        loggerMiddleware(action);
      } catch (err) {
        console.error('错误报告: ', err)
      } 
    }
    store.dispatch = exceptionMiddleware;
  3. 现在的代码有一个很严重的问题,就是 exceptionMiddleware 里面写死了 loggerMiddleware,我们需要让 next(action)变成动态的,随便哪个中间件都可以

    const exceptionMiddleware = (next) => (action) => {
      try {
        /*loggerMiddleware(action);*/
        next(action);
      } catch (err) {
        console.error('错误报告: ', err)
      } 
    }
    /*loggerMiddleware 变成参数传进去*/
    store.dispatch = exceptionMiddleware(loggerMiddleware);
  4. 同样的道理,loggerMiddleware 里面的 next 现在恒等于 store.dispatch,导致 loggerMiddleware 里面无法扩展别的中间件了!我们也把 next 写成动态的

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

到这里为止,我们已经探索出了一个扩展性很高的中间件合作模式!

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

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

const exceptionMiddleware = (next) => (action) => {
  try {
    next(action);
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

store.dispatch = exceptionMiddleware(loggerMiddleware(next));

这时候我们开开心心的新建了一个 loggerMiddleware.js,一个exceptionMiddleware.js文件,想把两个中间件独立到单独的文件中去。会碰到什么问题吗?

loggerMiddleware 中包含了外部变量 store,导致我们无法把中间件独立出去。那我们把 store 也作为一个参数传进去好了~

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

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

const exceptionMiddleware = (store) => (next) => (action) => {
  try {
    next(action);
  } catch (err) {
    console.error('错误报告: ', err)
  }
}

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

到这里为止,我们真正的实现了两个可以独立的中间件啦!

现在我有一个需求,在打印日志之前输出当前的时间戳。用中间件来实现!

const timeMiddleware = (store) => (next) => (action) => {
  console.log('time', new Date().getTime());
  next(action);
}
...
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));

本小节完整源码见 demo-6

中间件使用方式优化

上一节我们已经完全实现了正确的中间件!但是中间件的使用方式不是很友好

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)));

其实我们只需要知道三个中间件,剩下的细节都可以封装起来!我们通过扩展 createStore 来实现!

先来看看期望的用法

/*接收旧的 createStore,返回新的 createStore*/
const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);

/*返回了一个 dispatch 被重写过的 store*/
const store = newCreateStore(reducer);

实现 applyMiddleware

const applyMiddleware = function (...middlewares) {
  /*返回一个重写createStore的方法*/
  return function rewriteCreateStoreFunc(oldCreateStore) {
     /*返回重写后新的 createStore*/
    return function newCreateStore(reducer, initState) {
      /*1. 生成store*/
      const store = oldCreateStore(reducer, initState);
      /*给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/
      /* const chain = [exception, time, logger]*/
      const chain = middlewares.map(middleware => middleware(store));
      let dispatch = store.dispatch;
      /* 实现 exception(time((logger(dispatch))))*/
      chain.reverse().map(middleware => {
        dispatch = middleware(dispatch);
      });

      /*2. 重写 dispatch*/
      store.dispatch = dispatch;
      return store;
    }
  }
}

让用户体验美好

现在还有个小问题,我们有两种 createStore 了

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

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

为了让用户用起来统一一些,我们可以很简单的使他们的使用方式一致,我们修改下 createStore 方法

const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
    /*如果有 rewriteCreateStoreFunc,那就采用新的 createStore */
    if(rewriteCreateStoreFunc){
       const newCreateStore =  rewriteCreateStoreFunc(createStore);
       return newCreateStore(reducer, initState);
    }
    /*否则按照正常的流程走*/
    ...
}

最终的用法

const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);

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

本小节完整源码见 demo-7

 

完整的 redux

退订

不能退订的订阅都是耍流浪!我们修改下 store.subscribe 方法,增加退订功能

  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();

中间件拿到的store

现在的中间件拿到了完整的 store,他甚至可以修改我们的 subscribe 方法,按照最小开放策略,我们只用把 getState 给中间件就可以了!因为我们只允许你用 getState 方法!

修改下 applyMiddleware 中给中间件传的 store

/*const chain = middlewares.map(middleware => middleware(store));*/
const simpleStore = { getState: store.getState };
const chain = middlewares.map(middleware => middleware(simpleStore));

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

当然 compose 函数对于新人来说可能比较难理解,你只需要他是做什么的就行啦!

省略initState

有时候我们创建 store 的时候不传 initState,我们怎么用?

const store = createStore(reducer, {}, rewriteCreateStoreFunc);

redux 允许我们这样写

const store = createStore(reducer, rewriteCreateStoreFunc);

我们仅需要改下 createStore 函数,如果第二个参数是一个object,我们认为他是 initState,如果是 function,我们就认为他是 rewriteCreateStoreFunc。

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

2 行代码的 replaceReducer

reducer 拆分后,和组件是一一对应的。我们就希望在做按需加载的时候,reducer也可以跟着组件在必要的时候再加载,然后用新的 reducer 替换老的 reducer。

const createStore = function (reducer, initState) {
  ...
  function replaceReducer(nextReducer) {
    reducer = nextReducer
    /*刷新一遍 state 的值,新来的 reducer 把自己的默认状态放到 state 树上去*/
    dispatch({ type: Symbol() })
  }
  ...
  return {
    ...
    replaceReducer
  }
}

我们来尝试使用下

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

/*生成新的reducer*/
const nextReducer = combineReducers({
  counter: counterReducer,
  info: infoReducer
});
/*replaceReducer*/
store.replaceReducer(nextReducer);

replaceReducer 示例源码见 demo-5

bindActionCreators

bindActionCreators 我们很少很少用到,一般只有在 react-redux 的 connect 实现中用到。

他是做什么的?他通过闭包,把 dispatch 和 actionCreator 隐藏起来,让其他地方感知不到 redux 的存在。

我们通过普通的方式来 隐藏 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: name
  }
}

const actions = {
  increment: function () {
    return store.dispatch(increment.apply(this, arguments))
  },
  setName: function () {
    return store.dispatch(setName.apply(this, arguments))
  }
}
/*注意:我们可以把 actions 传到任何地方去*/
/*其他地方在实现自增的时候,根本不知道 dispatch,actionCreator等细节*/
actions.increment(); /*自增*/
actions.setName('九部威武'); /*修改 info.name*/

我眼睛一看,这个 actions 生成的时候,好多公共代码,提取一下

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

来看一下 bindActionCreators 的源码,超级简单(就是生成了刚才的 actions)

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

/* actionCreators 必须是 function 或者 object */
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error()
  }

  const keys = Object.keys(actionCreators)
  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 boundActionCreators
}

bindActionCreators 示例源码见 demo-8

大功告成

完整的示例源码见 demo-9,你可以和 redux 源码做一下对比,你会发现,我们已经实现了 redux 所有的功能了。

当然,为了保证代码的理解性,我们少了一些参数验证。比如 createStore(reducer)的参数 reducer 必须是 function 等等。

 

最佳实践

纯函数

什么是纯函数?

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。

通俗来讲,就两个要素

  1. 相同的输入,一定会得到相同的输出
  2. 不会有 “触发事件”,更改输入参数,依赖外部参数,打印 log 等等副作用
/*不是纯函数,因为同样的输入,输出结果不一致*/
function a( count ){
   return count + Math.random();
}

/*不是纯函数,因为外部的 arr 被修改了*/
function b( arr ){
    return arr.push(1);
}
let arr = [1, 2, 3];
b(arr);
console.log(arr); //[1, 2, 3, 1]

/*不是纯函数,以为依赖了外部的 x*/
let x = 1;
function c( count ){
    return count + x;
}

我们的 reducer 计划函数,就必须是一个纯函数!

只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

 

总结

到了最后,我想把 redux 中关键的名词列出来,你每个都知道是干啥的吗?

  • createStore

    创建 store 对象,包含 getState, dispatch, subscribe, replaceReducer

  • reducer

    reducer 是一个计划函数,接收旧的 state 和 action,生成新的 state

  • action

    action 是一个对象,必须包含 type 字段

  • dispatch

    dispatch( action ) 触发 action,生成新的 state

  • subscribe

    实现订阅功能,每次触发 dispatch 的时候,会执行订阅函数

  • combineReducers

    多 reducer 合并成一个 reducer

  • replaceReducer

    替换 reducer 函数

  • middleware

    扩展 dispatch 函数!

 

 

Android14_MTK调试LCD屏功能 android14 MTK平台调试LCD屏 阅读详情

相关推荐

如何使用superpowers

"这个 bug 帮我修一下" → 我会自动走 systematic-debugging → TDD → verify 流程。- "帮我添加一个登录功能" → 我会自动走 brainstorming → plan → TDD → verify 流程。只要任务有 1% 的可能性适用某个技能,我就会先调用它再行动。→ TDD(测试驱动开发)→ verification(验证)→ code review(代码审查)提出需求 → brainstorming(头脑风暴)→ writing-plans(编写计划)

xiaojingjianqiang的博客 610

说说redux的实现原理是什么,写出其原理的核心代码?

redux

m0_65545080的博客 647

链路聚合Eth-trunk以及华为交换机配置实例

链路聚合简介 以太网链路聚合Eth-Trunk简称链路聚合,它通过将多条以太网物理链路捆绑在一起成为一条逻辑链路,从而实现增加链路带宽的目的。同时,这些捆绑在一起的链路通过相互间的动态备份,可以有效地提高链路的可靠性。 目的: 随着网络规模不断扩大,用户对骨干链路的带宽和可靠性提出越来越高的要求。在传统技术中,常用更换高速率的接口板或更换支持高速率接口板的设备的方式来增加带宽,但这种方案需要付出高额的费用,而且不够灵活。 采用链路聚合技术可以在不进行硬件升级的条件下,通过将多个物理接口捆绑为一个逻辑接口

ChaseAug的博客 2万+

【转】完全理解 redux(从零实现一个 redux)

本周在阅读redux源码时,发现一个文章,由简至深,甚好,原文地址在这里,学习一下。 但是本篇内容并不是介绍redux API的,redux的使用可以参考阮一峰老师的博客,或者示例 目录 [前言] [状态管理器] 简单的状态管理器 有计划的状态管理器 [多文件协作] reducer 的拆分和合并 state 的拆分和合并 [中间件 middleware] [完整的 redux] [最...

weixin_34066347的博客 251

UFS 3.1协议分析(第一至四章) -- UFS概述

Contents协议规范规范参考术语和定义简单概述通用特性接口特性功能特性 协议规范     UFS3.1协议规定了UFS电气接口和存储器件的特性以及系统架构。 特性:低功耗、高数据吞吐量、低电磁干扰、大容量存储器子系统效率的优化 电气接口:使用基于MIPI M-PHY的高级差分接口;M-PHY作为物理层,MIPI UniPro作为链路层,二者结合形成UFS接口的互联。 架构模型:参考 INCITS T10(SCSI) SAM 标准,命令协议基于 INCITS T1

Frank_sample的专栏 1万+

避坑指南:ZYNQ平台上AXI 1G/2.5G Ethernet Subsystem实现IEEE1588的5个关键点

本文详细解析了在ZYNQ平台上使用AXI 1G/2.5G以太网子系统实现IEEE1588精密时间协议(PTP)的5个关键避坑点。从硬件选型、Vivado工程配置到Linux驱动适配,提供了实战经验与优化技巧,帮助工程师在工业自动化、电力系统等场景中实现纳秒级时间同步。

weixin_30687051的博客 232

实现一个简单的Redux

一、前言 Redux并不是一门语言,也不是一个框架,不过可以理解为是一种数据流框架,而不是我们平常用的React类似的UI框架。Redux更像是一种思想或者设计模式。以数据对象为核心,根据不同的事件类型,响应不同的状态,然后执行状态修改之后的事件。这里其实跟我上一篇的博客,面向过程与面向对象的区别可以结合着看。 先说一下这种思想的步骤: 1. 以数据对象为核心,具体说是数据状态; 2. 分...

杏子 475

基于 Qt C++ 的金融行情交易终端

由于完整的商业级源码非常庞大,我将为你提供一个 **结构完整、可直接编译运行的基础框架**,并详细说明各模块的实现思路和扩展方向。double波动 = quote.lastPrice * (rand()%200 - 100) / 10000.0;// 订单缓存(orderId -> Order)

寒寒豆豆软件公司的博客 189

通过实现一个简易版 react-redux ,我更理解了!

大厂技术高级前端Node进阶 点击上方程序员成长指北,关注公众号 回复1,加入高级Node交流群作者:周婷是个死胖子本文来自小伙伴投稿,欢迎大家投稿哦!写在前面本文尝试参照react-redux的源码实现一个简易版的react状态管理功能、看懂本文再去看react-redux和redux的源码应该能事半功倍。React-redux的使用react-redux的基本使用可以参照如下代码说...

程序员成长指北 205

42、InfiniBand技术详解:QP状态机、操作及错误处理

本文深入解析InfiniBand技术中的核心组件QP(Queue Pair)状态机、工作请求处理机制、支持的操作类型及错误处理策略。详细介绍了QP的创建与状态转换流程,包括INIT、RTR、RTS等关键状态,并提供了状态修改的代码示例。文章还探讨了工作请求的发布、资源使用限制、围栏机制以及重试和RNR流程,帮助开发者理解通信可靠性保障机制。此外,涵盖了多播组的应用场景与API使用方法,并对比了用户空间与内核级RDMA API的调用差异,最后给出了实践建议和常见问题解答,为构建高性能分布式系统提供技术指导。

oauth7security的博客 130

Ubuntu22.04如何安装cuDNN8.9.7

本文详细介绍了在Ubuntu 22.04系统上为CUDA 12.2安装适配cuDNN 8.9.7的完整流程,特别针对RTX A6000显卡。主要内容包括:1)选择与CUDA 12.2兼容的cuDNN版本;2)使用deb包方式的安装步骤(添加仓库、导入密钥、安装组件);3)通过MNIST示例验证安装;4)版本锁定、卸载旧版等补充说明;5)性能测试方法。文中强调了版本匹配的重要性,并提供了多卡配置建议,为搭建深度学习开发环境提供完整指导。

2501_93651177的博客 1844

手写Redux核心原理

Redux手写实现

随风浅入画 311

uniapp - Invalid value “iife” for option “output.format” - UMD and IIFE output formats are not suppo

Invalid value “iife” for option “output.format” - UMD and IIFE output formats are not supported for code-splitting builds. uniapp 编译报错,vite vue3 Hbuilder 中编译报错,uniapp 打包 App 报错(vue3)hbuilder提示错误,uniapp运行到手机/模拟器 App 报错(vue3)uniapp项目启动报错,如何解决这个问题,安卓app,苹果app

🏆 前端领域优质创作者 3964

Redux and React-redux 莞式教程(新手必看)

Redux and React-redux 莞式教程(新手必看) 为了方便使用者使用,这里就redux和react-redux 一并讲究使用了,会了react-redux,redux也就没问题了,只不过稍微封装了一下 教程方式 我们先讲理论,先明白 render、action、 store 、connect 、 dispatch 、{ mapStateToProps mapDispat...

weixin_34025151的博客 1万+

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

视频教程-2020全新React16.12第二季:Redux/React-Redux/Redux-Thunk-ReactJS

2020全新React16.12第二季:Redux/React-Redux/Redux-Thunk ...

weixin_33867262的博客 200

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

从零开始学React:四档(上)一步一步学会react-redux (自己写个Redux)

手挽手带你学React入门四档,用人话教你react-redux,理解redux架构,以及运用在react中。学完这一章,你就可以开始自己的react项目了。 之前在思否看到过某个大神的redux搭建 忘记了大神的名字 这里只记得内容了 凭借记忆和当时的学习路线写下本文 隔空感谢 视频教程 本人学习react-redux的时候遇到了很多坎,特别是不理解为什么这么用,这是什么东西,用来做什么。...

weixin_33888907的博客 153

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

React学习(三):Redux入门教程

Redux入门教程

Stella的博客 615

使用Redux管理你的React应用

原文作者:http://www.cnblogs.com/Leo_wl/p/4780750.html   因为redux和react的版本更新的比较频繁,博客园这里用的redux版本是1.0.1,如果你关心最新版本的使用技巧,欢迎来我的Github查看(https://github.com/matthew-sun/blog/issues/18) ,我会在这里进行持续的更新和纠错。 Re

1778

Redux,React-Redux简单示例

Redux,React-Redux简单示例 Counter示例 //index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import * as serviceWorker from './serviceWorker'; import App from "./compo...

不知道写的什么 230

[译] 2019 React Redux 完全指南

原文地址:A Complete React Redux Tutorial for 2019 原文作者:Dave Ceddia 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:xilihuasi 校对者:xionglong58、Fengziyin1234 2019 React Redux 完全指南 想要理解 Redux 完整的工作机制真的让人头疼...

weixin_33948416的博客 494

Redux 与 React-Redux:从“全局变量”到“响应式状态”的优雅之道

但光有 Redux 还不够,如何让 React 组件和这个“保险箱”优雅地对话呢?这就是 React-Redux 的功劳了。今天,我就用一个简单的例子,帮你彻底理清这两者的关系和用法。

欢迎来到我的前端博客!这里汇集了关于前端开发的最新技术、实用工具和经验分享。我将为你提供详细的教程、代码示例和实战案例,帮助你掌握Web开发的核心概念和技能。不论你是初学者还是有经验的开发者,这个博客都适合你。 915

Redux 与 React 状态管理精讲:从基础到实战

Redux 与 React 状态管理精讲:从基础到实战

DZSpace,专注于计算机科学与技术领域的技术博主,致力于分享实用知识与技巧,帮助读者快速掌握技术要点,共同探索计算机世界的无限可能。欢迎访问DZSpace的博客,一起学习进步! 1359

对React-redux中connect方法的理解

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34414650的博客 167

如何使用redux

redux的实现了解过vuex的同学再来学redux可能会觉得无从下手,个人觉得redux要比vuex要难以理解,但他们的功能都是一样的,就是共同维护一个公共的状态,一个地方改变了,其他引用到这个state的地方也会跟着改变。下面用最简单的 加和减的例子讲解redux的用法:首先功能呢是这样的,默认count是0,在increase加法页面,每点击一次按钮进行+2的操作,当然这个2是通过参数传递的...

qq_35430000的博客 3756

DNESP32P4 USB Host实战:U盘识别与FAT32读写全链路解析

USB Host是嵌入式系统实现外设扩展的关键能力,其本质是硬件PHY控制、USB协议栈枚举、MSC类设备通信与文件系统挂载的多层协同。理解USB 2.0主机模式原理,掌握VBUS供电管理、CC引脚检测逻辑及SCSI命令交互机制,是解决U盘识别失败、枚举卡死、文件读取异常等工程问题的技术基础。在ESP32-P4平台上,尤其需关注DNESP32P4定制化硬件带来的PHY引脚偏移、VBUS驱动能力限制与BootROM初始化差异。结合usb_host、usb_class_msc和fatfs组件,构建从物理连接到f

weixin_34187862的博客 417

Redux解析

介绍 redux是管理State的一个东东,所有State都需要经过redux来操作。 基本概念 redux中有三个基本概念,Action,Reducer,Store。 Action 官方的介绍: Actions are payloads of information that send data from your application to your store. They ar

qq_15789229的专栏 417
上一篇: npm安装项目所有依赖包
下一篇: pgsql的guid生成函数
兀兀术
博客等级 码龄19年 25粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值