最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Rematch vs Redux?

    正文概述 掘金(小志Chris)   2021-05-22   629

    看完 【译】重新设计 Redux 后,这篇文章会继续聊聊 Rematch 和 Redux 的区别。我将分别用 Redux 和 Rematch 实现一个简易的计数器(Counter),其中 Redux 实现版本中对异步的处理分别使用 redux-thunk 和 redux-saga。通过这个例子,我们来比较 Rematch 和 Redux 的差异,以及 Rematch 的优点在哪(减少了哪些学习成本,减少了哪些代码等等),最后,来讲讲 Rematch 如何包装 Redux,来做到平滑过渡的,这将涉及到 Rematch 的代码架构,我把它分成了几个部分,在后面的文章中会逐一讲解。

    文章大纲

    Rematch vs Redux?

    简易计数器(Counter)案例

    下面我会用 Redux 和 Rematch 分别实现一个 React 版本的简易计数器,计数器包含增加、减少和异步增加功能。

    页面截图如下:

    Rematch vs Redux?

    Redux 实现

    纯 Redux 实现版本中,对于异步增加功能的实现,一种方案使用了 redux-thunk,另一种使用了 redux-saga。

    目录结构很简单:

    src
    |—— components
    |  |—— Counter.js
    |—— reducers
    |  |—— index.js
    |—— index.js
    

    其中 components/Counter.js 代码如下:

    import React, { Component } from "react";
    import PropTypes from "prop-types";
    
    class Counter extends Component {
      render() {
        const { value, onIncrement, onDecrement, onIncrementAsync } = this.props;
        return (
          <p>
            Clicked: {value} times <button onClick={onIncrement}>+</button>{" "}
            <button onClick={onDecrement}>-</button>{" "}
            <button onClick={onIncrementAsync}>Increment async</button>
          </p>
        );
      }
    }
    
    Counter.propTypes = {
      value: PropTypes.number.isRequired,
      onIncrement: PropTypes.func.isRequired,
      onDecrement: PropTypes.func.isRequired,
      onIncrementAsync: PropTypes.func.isRequired,
    };
    
    export default Counter;
    

    reducers/index.js 代码如下:

    export default (state = 0, action) => {
      switch (action.type) {
        case "INCREMENT":
          return state + 1;
        case "DECREMENT":
          return state - 1;
        default:
          return state;
      }
    };
    

    index.js 中的代码,由于包含异步逻辑,因此按使用的方案不同,代码也有差异,下面分别说明。

    异步基于 redux-thunk

    完整代码请点击

    如果异步使用 redux-thunk,index.js 中代码如下:

    import React from "react";
    import ReactDOM from "react-dom";
    import { applyMiddleware, createStore } from "redux";
    import Counter from "./components/Counter";
    import counter from "./reducers";
    import thunk from "redux-thunk";
    
    const store = createStore(counter, applyMiddleware(thunk));
    const rootEl = document.getElementById("root");
    
    function fakeAsyncLogic() {
      return new Promise(function (rs) {
        setTimeout(rs, 1000);
      });
    }
    
    function makeAsyncIncrementAction() {
      return async function (dispatch) {
        await fakeAsyncLogic();
        dispatch({ type: "INCREMENT" });
      };
    }
    
    const render = () =>
      ReactDOM.render(
        <Counter
          value={store.getState()}
          onIncrement={() => store.dispatch({ type: "INCREMENT" })}
          onDecrement={() => store.dispatch({ type: "DECREMENT" })}
          onIncrementAsync={() => store.dispatch(makeAsyncIncrementAction())}
        />,
        rootEl
      );
    
    render();
    store.subscribe(render);
    

    使用了 thunk 中间件以后,dispatch() 可以接收函数作为参数,然后 redux store 会将 dispatchgetState 作为参数传入该函数中,这样一来,如果该函数是异步函数,则可以实现异步派发 action。

    异步基于 redux-saga

    完整代码请点击

    如果异步使用 redux-saga,index.js 中代码如下:

    import React from "react";
    import ReactDOM from "react-dom";
    import { createStore, applyMiddleware } from "redux";
    import createSagaMiddleware from "redux-saga";
    import Counter from "./components/Counter";
    import counter from "./reducers";
    import defaultSaga from "./reducers/saga";
    
    const sagaMiddleware = createSagaMiddleware();
    
    const store = createStore(counter, applyMiddleware(sagaMiddleware));
    const rootEl = document.getElementById("root");
    
    sagaMiddleware.run(defaultSaga);
    
    const render = () =>
      ReactDOM.render(
        <Counter
          value={store.getState()}
          onIncrement={() => store.dispatch({ type: "INCREMENT" })}
          onDecrement={() => store.dispatch({ type: "DECREMENT" })}
          onIncrementAsync={() => store.dispatch({ type: "INCREMENT_ASYNC" })}
        />,
        rootEl
      );
    
    render();
    store.subscribe(render);
    

    除了中间件配置代码那里的不同,saga 不是像 redux-thunk 那样派发一个函数作为 action,而是需要定义一些 saga 的异步逻辑(使用 saga 自带的一些异步 API),因此,src/reducers 目录下增加一个 saga.js

    import { takeEvery, call, put } from "redux-saga/effects";
    
    async function fakeAsyncLogic() {
      return new Promise((rs) => setTimeout(rs, 1000));
    }
    
    function* increamentAsync() {
      yield call(fakeAsyncLogic);
      yield put({ type: "INCREMENT" });
    }
    
    export default function* defaultSaga() {
      yield takeEvery("INCREMENT_ASYNC", increamentAsync);
    }
    

    saga 使用迭代器函数(generator)来更加精细地控制异步流。上面代码导出了一个默认 saga 函数,takeEvery("INCREMENT_ASYNC", increamentAsync) 表示监听所有 action.typeINCREMENT_ASYNC 的 action,一旦监听到,执行 increamentAsync() 函数,在该函数中,使用 call(fakeAsyncLogic) 模拟异步调用,然后使用 put({ type: "INCREMENT" }) 来派发一个 action,最终这个 action 会使得 reducer 执行。

    Rematch 实现

    完整代码请点击

    Rematch 中没有单独的 reducers,reducer 都归属于一个数据结构叫做 model,因此目录结构稍有不同(将 reducers 换为 models):

    src
    |—— components
    |  |—— Counter.js
    |—— models
    |  |—— index.js
    |—— index.js
    

    Counter.js 中代码不变,models/index.js 中代码如下:

    async function fakeAsyncLogic() {
      return new Promise((rs) => {
        setTimeout(rs, 1000);
      });
    }
    
    export const count = {
      state: 0,
      reducers: {
        increment: (state) => {
          return state + 1;
        },
        decrement: (state) => {
          return state - 1;
        },
      },
      effects: (dispatch) => ({
        async incrementAsync() {
          await fakeAsyncLogic();
          dispatch.count.increment();
        },
      }),
    };
    

    可以看到,我们定义了一个叫做 count 的 model,其中包含了 statereducers 以及 effectesstate 是归属于该 model 的数据,相当于 redux 中 reducer 函数的第一个参数(或者其返回值)。reducers 等同于 redux reducer,而最后的 effects 则是一些有副作用的逻辑(例如异步的接口调用等等)。

    最后是 index.js

    import { init } from "@rematch/core";
    import React from "react";
    import ReactDOM from "react-dom";
    import Counter from "./components/Counter";
    import * as models from "./models";
    
    const store = init({ models });
    // const store = createStore(counter, applyMiddleware(thunk));
    const rootEl = document.getElementById("root");
    
    const render = () =>
      ReactDOM.render(
        <Counter
          value={store.getState().count}
          onIncrement={store.dispatch.count.increment}
          onDecrement={() => store.dispatch({ type: "count/decrement" })}
          onIncrementAsync={() => store.dispatch({ type: "count/incrementAsync" })}
        />,
        rootEl
      );
    
    render();
    store.subscribe(render);
    

    此时 store 不再需要用 redux 的 API createStore 创建,而是使用 rematch 的 init。这里还需要注意 2 点:

    1. 此时 store.getState() 返回的不再是一个数值,而是 { count: number }
    2. store.dispatch 不仅仅是一个函数(维持 redux 调用方式),同时支持了 dispatch.modelName.xxx 这样调用一个 reducer 或者 effect。不过需要注意的是,action.type 此时为 modelName/reducerName 或者 modelName/effectName 这种形式

    前面提到过,rematch 中的最小组成单元就是一个 model。因此上述的改动也是为了兼容 model 这种形式。

    rematch vs redux

    通过上面的例子,可以看出两者有如下几点不同:

    1. 如果使用 redux,异步需要单独使用中间件,例如 thunk 或 saga。而 rematch 中可以直接使用 ES 的 async/await 异步语法来实现异步派发 action。
    2. redux 中没有 model 的概念,如果 state 结构复杂,可以使用 combineReducers 来合并不同的 reducer,同时形成一个类似的 state 结构。而 rematch 原生支持。
    3. redux 的 store.dispatch 就是一个函数。但 rematch 中保留了函数功能,同时提供了链式调用的方式。

    由于上面例子比较简单,因此差异不多。更多差异可以参考【译】重新设计 Redux。这里再提两点比较常见的差异:

    1. redux 更多的使用了函数式编程的思想,例如 store 初始化时,其提供了一个工具函数 compose 用于组合 store enhancer。
    2. 简化的 reducer,主要包括省略了 action.type 常量定义,省略了 reducer 中的 switch/case 分支判断,因此 rematch 中的 model 的一个 reducer 就相当于一个 case 分支,其 name 就相当于 action.type

    个人觉得,rematch 优于 redux 的主要在于三个地方:

    1. 更”合理“的数据结构设计,rematch 使用 model 的概念,整合了 state, reducer 以及 effect,这种整合在前端开发中非常实用,例如可以针对不同的页面路由设计不同的 model。
    2. 更简洁的 API 设计,redux 中使用的函数组合配置方法,对于不熟悉函数式编程的开发者来说,一开始可能比较困惑,而 rematch 使用的是基于对象的配置项,更加易于上手。
    3. 更少的代码。
    • 移除了 redux 中大量的 action.type 常量以及分支判断
    • 原生语法支持异步,无需使用中间件。使用 saga 有一定学习成本,使用 thunk,派发的 action 类型各异,也会产生一定困惑

    除此之外,rematch 还提供了插件机制,除了社区开发的很多插件,我们还可以进行定制开发,关于插件会在后面文章中详细介绍。

    rematch 的代码结构

    我们知道,rematch 其实只是基于 redux 的包装,它把 redux 复杂的语法变得简单化了:

    正因为如此,它顶层还是 redux,并没有减弱 redux 的功能。那么,rematch 是如何做到的,他是如何设计的?我接下来会基于 rematch v1.4.0(rematch v1 的最后一个版本)来讲解 rematch 核心代码结构。

    先来看看 rematch v1.4.0 的代码目录结构:

    ...
    plugins
    |—— ...
    |—— loading
    |—— immer
    |—— select
    src
    |—— plugins
    |  |—— dispatch.ts
    |  |—— effects.ts
    |—— typings
    |  |—— index.ts
    |—— utils
    |  |—— deprecate.ts
    |  |—— isListener.ts
    |  |—— mergeConfig.ts
    |  |—— validate.ts
    |—— index.ts
    |—— pluginFactory.ts
    |—— redux.ts
    |—— rematch.ts
    

    根据以上结构,我将 rematch 拆分为如下几个组成部分:

    Rematch vs Redux?

    rematch 由 core 和 plugin 组成,其中 core 分为两部分,分别是 rematch 类和 redux.ts 这个文件,前者为 rematch 核心源码,后者主要包含 reducer 合并的一些代码,用于创建 redux store。

    而 plugin 是 rematch 提供的插件机制,用于增强 rematch 的功能,主要代码定义在 plugin factory 中。rematch core 中包含了两个核心的 plugin,分别是 dispatch 和 effect,dispatch 插件可以增强 store.dispatch 功能,让其支持链式调用。而 effect 插件主要是用于支持 async/await 这种异步模式。除了这两个 plugin,rematch 团队还开发了其他的第三方插件,例如 loading, select 等等,集成了异步请求 loading 状态和 selector。

    接下来,我会分别讲解这些部分,拆细一点,就是 rematch core,plugin factory && core plugins,3rd party plugins,一共 3 篇文章。3 篇文章结束后,我还会写 2 篇文章,其中一篇为 rematch v1 到 v2 升级的变化,另一篇介绍 rematch 类型系统(这是升级到 v2 带来的最大变化)以及这个类型系统残留的一些问题和难点,与大家探讨。

    敬请期待!


    起源地下载网 » Rematch vs Redux?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元