【React Hooks系列】之useReducer

3,516 阅读4分钟

前言

由于React的函数式组件使用起来方便(对比class组件),我将重点使用函数组件来运行开发。在这系列博客中,我将分享我所学到Hook系列API的知识。 Hooks系列主要分以下内容:

什么是useReducer

React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。

Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的state,Reducer 函数的形式是(state, action) => newState

useReducer用来引入 Reducer 功能。

useReducer语法

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

useReducer用来接收两个参数,分别是Reducer函数和初始state

useReducer 返回了一个数组,2个元素分别为 state 和 dispatch 方法。其中 state 在我们的例子中就是当前的 n 值,dispatch 方法接受一个参数,执行对应的 action。dispatch 执行后,对应的 state 会改变,组件会 rerender,来展示最新的状态。

reducer函数

reducer函数里面可以存放state的各种操作,它类似状态管理器(其实我觉得应该叫state数据管理器),通过dispatch函数的传递action,可以触发reducer函数内部运算操作,并返回当前state

用法

首先,我们要定义一个Reducer函数,下面的函数将定义加法和减法

const reducer=(state,action)=>{
	if(action.type==="add"){
    	return {
        	n:state.n+1
        }
    }else if(action.type==="sub"){
    	return {
        	n:state.n-1
        }
    }
}

其次,使用useReducer接收Reducer函数和一个初始state,并返回当前值state与dispatch函数

当触发事件时,使用dispatch传递action,让reducer计算出新的state

export default function App() {
  const initialState = { n: 1 };
  const [state, dispatch] = useReducer(reducer, initialState); 
//使用useReducer接收reducer参数和初始state
  return (
    <>
      <div>{state.n}</div>
      <button
        onClick={() => {
          dispatch({ type: "add" });  // 传递action
        }}
      >
        点击+
      </button>
      <button
        onClick={() => {
          dispatch({ type: "sub" });//传递action触发reducer函数
        }}
      >
        点击-
      </button>
    </>
  );
}

当传递的action的type是对应值时,就会算出对应的内容

dispatch()是发出 Action 的唯一方法。它发出action后使reducer执行,并返回一个新的state。

小结

我们学习到reducer的使用步骤

  • 先定义一个initialValue
  • 定义一个reducer函数,把所有操作方法都丢进去
  • 把initialValue跟reducer通过useReducer关联起来,并返回一个当前state和dispatch
  • 当需要计算时,使用dispatch传递一个action值,触发reducer函数执行,返回一个新的state

新的示例

来看一个比较复杂的示例

import React from "react";
import "./styles.css";
// 定义操作器
const reducer = (state, action) => {
  if (action.type === "change") {
    return { ...state, ...action.formData };
  } else if (action.type === "reset") {
    return { name: "", age: "", love: "" };
  }
};
export default function App() {
  const [state, dispatch] = React.useReducer(reducer, {
    name: "",
    age: "",
    love: ""
  });
  const onclick = () => {
    alert(`您的姓名是${state.name},年龄是${state.age},您喜欢${state.love}`);
  };
  return (
    <>
      <form>
        <div>
          姓名
          <input
            onInput={(e) => {
              dispatch({ type: "change", formData: { name: e.target.value } });
            }}
          />
        </div>
        <div>
          年龄
          <input
            onInput={(e) => {
              dispatch({ type: "change", formData: { age: e.target.value } });
            }}
          />
        </div>
        <div>
          喜欢
          <input
            onInput={(e) => {
              dispatch({ type: "change", formData: { love: e.target.value } });
            }}
          />
        </div>
        <button type="reset">重置</button>
        <button type="button" onClick={onclick}>
          确认
        </button>
      </form>
      <hr />
    </>
  );
}

上面使用了action传递更多的属性来进行更多的计算可能性。代码直接直接复制到代码沙盒中运行。

使用useReducer替代redux

我们可以使用useContext配合useReducer来帮助我们完成一些数据、操作的流转

主要分成以下步骤

创建store用来存放数据

示例:

const store={
	user:null,
	age:null,
   	movies:null
}

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

创建reducer函数存放操作

示例:

const reducer = (state, action) => {
  switch (action.type) {
    case "setUser":
      return { ...state, user: action.user };
    case "setBooks":
      return { ...state, age: action.age };
    case "setMovies":
      return { ...state, movies: action.movies };
    default:
      break;
  }
}

reducer是一个操作管理器,当Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

创建一个Context

const Context =React.createContext()

创建上下文

创建读写api

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

store需要知道reducer函数,做法就是使用useReducer将store与reducer关联起来,再通过dispatch发出action

Context.provider提供Context

主要渲染组件

return (
    <Context.Provider value={{state:state,dispatch:dispatch}}>
      <User />
      <hr />
      <Books />
      <Movies />
    </Context.Provider>
  );

在provider内的所有组件都可以通过useContext来获取到value里面的API

各个组件使用useContext获取读写api

子组件

 const { state, dispatch } = useContext(Context);

表结构编程

上面的步骤已经介绍完毕,但是有时候数据多,reducer的代码量很大,那么能否将代码完善一下?

可以使用表结构编程,将reduce里面的代码保存在一个对象里。

const fnObj = {
  setUser: (state, action) => {
    return { ...state, user: action.user };
  },
  setBooks: (state, action) => {
    return { ...state, books: action.books };
  },
  setMovies: (state, action) => {
    return { ...state, movies: action.movies };
  }
};

此时的reducer为

function reducer(state, action) {
  const fn = obj[action.type];
  if (fn) {
    return fn(state, action);
  } else {
    throw new Error("这是不对的");
  }
}

这种方法比较适合模块化开发,因为对象是可以合并的,所以当模块化后就可以通过...运算符将不同的逻辑运算的对象合并到一个表对象里面,类似于这样

const objFn={
	...app,
    ...children
}

总结

我们学习到使用reducer函数的步骤以及通过useContext(上下文)provider(提供)让各组件共享state的操作步骤

如果需要优化reducer的代码,则推荐表结构编程来让代码更加优雅

参考文档

www.ruanyifeng.com/blog/2016/0…

zh-hans.reactjs.org/docs/hooks-…