[React Hooks长文总结系列三]随心所欲,制做“穷人版”的redux

2021年03月30日 阅读数:29
这篇文章主要向大家介绍[React Hooks长文总结系列三]随心所欲,制做“穷人版”的redux,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

前言

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

什么是reducer函数?

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

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

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

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

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

因此,当咱们的setter接收的参数是一个函数时,旧的state将做为参数被该函数使用。vuex

useReducer

useReducer基本用法以下:数据库

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

其中第三个参数是可选参数,咱们通常只会用到前两个。redux

一个简单的示例(实现数字+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来隐藏修改状态的复杂细节。ide

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