zoukankan      html  css  js  c++  java
  • [React Hooks长文总结系列三]为所欲为,制作“穷人版”的redux

    前言

    在离职之后,我开始静下心来,思考原来在繁重的业务开发节奏中无暇思考的一些问题,本期的主题是纯函数钩子useReducer和共享状态钩子useContext

    什么是reducer函数?

    在react中,reducer函数是一个很重要的概念。它表示一个接收旧状态,返回新状态的函数。

    const nums = [123]
    const value = nums.reduce((acc, cur) => acc + cur, 0)

    在上述例子中,reduce函数的一个参数,就是一个标准的reducer函数。

    在之前的setState使用中,你可能会好奇setNum(prev => prev + 1)prev怎么来的,让我们深入到最底层看看吧,实际上useState并非最底层的元素,它内部仍然用到了useReducer来实现,在react源码中有个basicStateReducer,大致结构如下:

    function basicStateReducer(state, action{
      return typeof action === 'function' ? action(state) : action;
    }

    所以,当我们的setter接收的参数是一个函数时,旧的state将作为参数被该函数使用。

    useReducer

    useReducer基本用法如下:

    const [state, dispatch] = useReducer(reducer, initialState, initFunc);

    其中第三个参数是可选参数,我们一般只会用到前两个。

    一个简单的示例(实现数字+1)如下:

    import React, { useReducer } from "react";

    const initialState = {
      num0,
    };

    function reducer(state, action{
      switch (action.type) {
        case "increase":
          return { num: state.num + 1 };
        case "decrease":
          return { num: state.num - 1 };
        default:
          throw new Error();
      }
    }

    const NumsDemo = () => {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
        <>
          <h1>数字为: {state.num}</h1>
          <button onClick={() => dispatch({ type: "increase" })}>+</button>
          <button onClick={() => dispatch({ type: "decrease" })}>-</button>
        </>

      );
    };

    export default NumsDemo;

    你可能也发现了,useReduceruseState 非常类似:定义状态和修改状态的逻辑useReducer 用起来更加麻烦,但是如果需要维护的状态本身比较复杂,多个状态之间相互依赖,那么 useReducer的好处才真正显示出来了:用一个语义化的action来隐藏修改状态的复杂细节。

    useContext

    useContext则比较简单,它用于在局部组件树中共享数据,有点类似于vue中的provide/inject,基本使用如下:

    // 在模块的入口文件中定义
    // Home.tsx
    export const MyContext = React.createContext({num24});

    // 在模块内部的某个组件中获取
    // Sub.tsx
    import MyContext from '../Home';

    const Sub = () => {
      const state = useContext(MyContext);
      // ...
    }

    redux的基本理念以及解决了什么问题

    好了,现在我们已经确定要弄一个小型redux了,不过在这之前我们还是需要回顾一下redux的基本理念。

    在对于小型页面共享数据时,我们一般会有诸如“状态提升”这样的开发约定,也就是说,我们会将共享的状态放到上层最近的公共父级。但是当页面数量一多,组件拆分粒度变细,这种“共享状态”的机制变得很脆弱,很冗余。

    redux 的机制就是为了解决这个问题,redux 有几个非常明显的特点:

    1. 数据的唯一真相来源;
    2. reducer 纯函数;
    3. 单项数据流。

    redux 的单项数据流,可以概括为三个部分:ViewReducersStore

    View视图层发起更新动作(dispatch),会抵达更新函数层(Reducers),更新函数执行并返回最新状态,抵达状态层(Store),状态层更新后将通知视图层更新。

    reduxredux

    其实我觉得,无论是 vuex 也好,redux 也好,它的设计理念都是类似一个“前端数据库”。在store层应该只存放公共状态,不建议存放其他的东西,比如公共方法,因为这与reducer纯函数的理念是相悖的。

    实现一个简单的小型redux

    好了,让一切开始吧,我们这里只定义三个组件:根组件App,第一个子组件Sub1,第二个子组件Sub2。实现一个非常简单的数字加减功能,如下:

    // 说明:为了代码更加易读,已经将ts的类型定义做了删除操作
    // App.tsx
    import React, { useReducer } from "react";
    import Sub1 from "./Sub1";
    import Sub2 from "./Sub2";

    const INITIAL_STATE = {
      name"zhang",
      age24,
    };

    // 定义改变状态的几种操作
    function reducer(state, action{
      switch (action.type) {
        case "increaseAge":
          return { ...state, age: state.age + 1 };
        case "decreaseAge":
          return { ...state, age: state.age - 1 };
        case "changeName":
          return { ...state, name: action.payload };
        default:
          return state;
      }
    }

    // 选择性导出该context
    export const AppContext = React.createContext(null);

    function App() {
      const [state, dispatch] = useReducer(reducer, INITIAL_STATE);

      return (
        <AppContext.Provider value={{ statedispatch }}>
          <Sub1 />
          <Sub2 />
        </AppContext.Provider>

      );
    }

    export default App;


    // 在Sub1.tsx中
    import React, { useContext } from "react";
    import { AppContext } from "./App";

    const Sub1 = () => {
      const { state, dispatch } = useContext(AppContext);
      return (
        <>
          <h1>Sub1年龄为: {state.age}, 名字为:{state.name}</h1>
          <button onClick={() => dispatch({ type: "increaseAge" })}>+</button>
        </>

      );
    };

    export default Sub1;

    // 在Sub2.tsx中
    import React, { useContext } from "react";
    import { AppContext } from "./App";

    const Sub2 = () => {
      const { state, dispatch } = useContext(AppContext);
      return (
        <>
          <h1>Sub2年龄为: {state.age}, 名字为:{state.name}</h1>
          <button onClick={() => dispatch({ type: "decreaseAge" })}>-</button>
        </>

      );
    };

    export default Sub2;

    运行以上示例,可以发现在一处子组件更改公共状态,其他消费到该公共状态的组件(Consumer)都会更新。这有效避免了隔代传props所引发的代码臃肿脆弱问题。

    useReducer + useContext 能否代替 redux?

    不能,我在项目中虽然已经这么用了,但是还是发现对比redux的功能是有所欠缺的,其中最典型的就是更新公共状态后没有回调的问题。

    useReducer + useContext实际上是制造了一个“穷人版的 redux”。而且我们必须花费额外的心思去避免性能问题(React.memouseCallback等),然而这些烦人的 dirty works 其实 React-Redux 等工具已经默默替我们解决了。除此之外,我们还会面临以下问题:

    • 需要自行实现 combineReducers 等辅助功能
    • 失去 Redux 生态的中间件支持
    • 失去 Redux DevTools 等调试工具
    • 出了坑不利于求助……

    以上四个坑点摘抄于腾讯的这篇文章,仔细读完后发现确实写的可以:Redux with Hooks

  • 相关阅读:
    C++元编程和Boost编程库 (C++ Metaprogramming and Boost MPL )中部
    支持插件的消息中间件【msg broker with plugin】 知然 博客园
    sync date
    Rubular: a Ruby regular expression editor and tester
    当爬虫被拒绝时(Access Denied) 风中之炎 博客园
    quartz scheduler
    C++ 使用STL string 实现的split,trim,replace修订
    java脚本编程 语言、框架与模式
    C++标准转换运算符const_cast
    http://jsoneditoronline.org/
  • 原文地址:https://www.cnblogs.com/zhangnan35/p/14597489.html
Copyright © 2011-2022 走看看