Skip to content

redux

宗旨:很简单,不允许用户 immutate 数据。actions 只是描述,reducers 根据这些描述返回新的值替代原始数据。

用户通过 dispatch 派发 actions,通过 reducers 改变 state。

dispatch 的实质 也是 redux 的实质:

function dispatch(action) {
    currentState = currentReducer(currentState, action)   
}

Redux middleware

Redux middleware 确实是这么简单的概念中拔地而出的技术亮点。传入 createStore 的 storeEnhancer 高阶函数,以及 middleware 的连接过程 + 透传原生 dispatch,无不体现着作者强大函数式编程功底…

加强过的 store.dispatch 已经不是原生的 dispatch 了,可以理解为被链式 monkeyPatching 过后的 fake dispatch,一直到这些 middlewares 被处理完毕,最后一个 middleware 才去最后调用原生的 dispatch。

源码思路

注:真正有效的 redux middleware 最外面还有一层接收store的函数,因为这层函数在初始化中间件操作时就已调用剥离,不参与到最终的链式调用,故下列简写也剥离掉了。

function compose(funcs) {
  return funcs.reduce((a, b) => dispatch => a(b(dispatch)));
}

const fn1 = next => action => {
  console.log('fn1 called', action);
  return next(action);
};
const fn2 = next => action => {
  console.log('fn2 called', action);
  return next(action);
};
const fn3 = next => action => {
  console.log('fn3 called', action);
  return next(action);
};
const fn4 = dispatch => action => {
  console.log('fn4 called', action);
  return dispatch(action);
};

// step 1: 初始化 compose 函数
const composedFns = compose([fn1, fn2, fn3, fn4]);
// step 2: 启动 composedFn
const composedFnsCalledWithDispatch = composedFns(() => console.log('real dispatch'));
// step 3: 启动 action 系列的调用
composedFnsCalledWithDispatch('action');

// step 1: 初始化 compose 函数
// (dispatch) => fn1(fn2(dispatch))
// (dispatch) => ( (dispatch) => fn1(fn2(dispatch)) )( fn3(dispatch) )
// (dispatch) => ( (dispatch) => ( (dispatch) => fn1(fn2(dispatch)) )( fn3(dispatch) ) )( fn4(dispatch) )

// step 2: 启动 composedFn
// ( (dispatch) => ( (dispatch) => fn1(fn2(dispatch)) )( fn3(dispatch) ) )( fn4(dispatch) )
// ( (dispatch) => fn1(fn2(dispatch)) )(fn3( fn4(dispatch) ))
// fn1(fn2(fn3(fn4(dispatch)))
// 此时 disptach 已存入闭包,f1 ~ f4 的最外层都已剥去,主体 action 函数作为 next 倒传给左侧的函数。这也是暴露给用户的`store.dispatch`方法

// step 3: 启动 action 系列的调用(即用户调用 store.dispatch('action'))。

我觉得最复杂的一步在于,初始化 compose 时函数列表是从左到右调用,结果生成了一串 IIFE (立即调用)函数。启动 composedFn 时是从右到左执行,所以才可以把右侧的函数主体传给左侧。而这份隐含的从右到左执行,是整个步骤可行性的关键。

而思路上最重要的两步在于:

  • 主体函数:主体函数是 params -> next()函数。next -> action()函数只是为了闭包 dispatch。
  • dispatch 的透传:实际上只有最后一个 middleware 接收到的 next 才是真实的亦即启动 composedFn 时传入的 dispatch,前面的一系列 next 都代表着下一个主体函数。

简易版实现

如果用循环而不是递归实现,微调下 api,是更适合常人的方式。这也是 koa 的 compose 实现机制。

function compose(funcs) {
  const len = funcs.length;

  return dispatch => action => {
    function next(i, passedAction) {
      if (i === len - 1) dispatch(passedAction);
      else {
        const fn = funcs[i];
        fn(next.bind(this, i + 1), passedAction);
      }
    }

    next(0, action);
  };
}

const fn1 = (next, action) => {
  console.log('fn1 called', action);
  return next(action);
};
const fn2 = (next, action) => {
  console.log('fn2 called', action);
  return next(action);
};
const fn3 = (next, action) => {
  console.log('fn3 called', action);
  return next(action);
};
const fn4 = (next, action) => {
  console.log('fn4 called', action);
  return next(action);
};

const dispatch = () => console.log('real dispatch');
const composedFns = compose([fn1, fn2, fn3, fn4]);
const composedFnsCalledWithDispatch = composedFns(dispatch);
composedFnsCalledWithDispatch('action');

其余

可以看出 redux 的数据设计也印证了 OOP,因为并没有选择更改对象,反而是通过函数返回一个全新的对象。

React-redux

粗略看了看~maintainer 超长文发展史

主要涉及到了 API 编写、如何更新整个组件…etc 这些问题 相关源码

image-20230224105148190

image-20230224105205327

https://zhuanlan.zhihu.com/p/85306555

update 摘: Most applications deal with multiple types of data, which can be broadly divided into three categories: - Domain data: data that the application needs to show, use, or modify (such as "all of the Todos retrieved from the server") - App state: data that is specific to the application's behavior (such as "Todo #5 is currently selected", or "there is a request in progress to fetch Todos") - UI state: data that represents how the UI is currently displayed (such as "The EditTodo modal dialog is currently open")

特点: - state 变化,不会造成 react root 的 rerender - 组件未消费的 state 变化,不会造成组件 rerender

我的常见误解: - React-redux 实质也是用了 context 来触发组件重新渲染 => 不是,redux 只是利用 context 来传递 store 实例而不是当前 value, the redux context never changes (in 99% of implementations anyway). React-redux 自己会比较变更,同时通过 forceRender / setState 的方式触发子组件的渲染 - refs: - reactjs - redux differentiation with context api - Stack Overflow - [javascript - The difference between Context API and Redux - Stack Overflow](https://stackoverflow.com/questions/67254562/the-difference-between-context-api-and-redux - can someone TLDR-explain how react-redux is able to trigger rerenders for only the pieces of state that changed? As opposed to context+useReducer : r/reactjs