Featured image of post 学习React第4章-状态管理与上下文

学习React第4章-状态管理与上下文

这篇笔记介绍了如何使用React的Reducer钩子和Context API进行状态管理,并详细讲解了Redux的使用方法及其与Context API的比较。

状态变量是React保持UI与数据同步的重要方式。本次我们会学习使用React库中的Reducer钩子和React Context API上下文来管理状态变量,消除之前组件中的大量无用道具。我们还要学习使用新版本的Redux进行状态管理。

Reducer 钩子

随着项目的增大,仅使用State及其setState函数来管理状态显得力不从心。这时,使用Reducer钩子可以让我们在代码的某处统一管理状态。

State无法满足需求

简单来说,若是多种状态变量产生了交涉,即某种状态变量的更新需要另一种状态变量,我们就应该考虑使用更专业的状态管理工具。

使用方法

Reducer使用方法

我们使用useReducer钩子创建state状态变量及其其设置函数dispatch。我们将state的初始值设置为一个对象,并在其中包含需要管理的各种状态变量。

然后,我们在自定义的reducer函数中设置对各种action的处理方法。

在开发过程中,我们通常对action进行封装,以区分action中的不同种类(typepayload)与载荷。

最后,我们在页面中取出当前的state并使用。

使用state

与setState的区别

实际的代码中,我们使用dispatch而非原先的setState函数作为handler。其会触发reducer内的相关处理,并最后和state一样造成页面重渲染。

useReducer与useState

区别

使用指南

Context API

在之前的开发中,我们简单地使用props来传递不同组件之间的状态变量,这导致组件中包含大量的冗余道具,影响了代码的可读性。一种解决方法就是使用Context API来全局地传递状态变量到需要的组件当中。

“Prop Drilling"及其解决方案

作用原理

Context API原理

使用方法

  • 创建、提供上下文;

创建上下文

包裹上下文

传递值

  • 消费上下文。

使用useContext

实际开发中,我们将上下文及其相关状态的新建等单独放置在一份js代码中,将上下文的value提前打包好。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//PostContext.js

const PostContext = createContext();

function PostProvider({children}){
    //add state in this component
    //......
    return (<PostContext.Provider value={{/*...*/}}>
        {children}
        </PostContext.Provider>
    );
}

function usePosts(){
    //warp 'useContext' hook
    const context = useContext(PostContext);
    if(context) return context;
    else throw new Error("Outside of provider.");
}

export { PostProvider, usePost };

Redux

Redux是一个用来管理全局状态的第三方库,集成了常用的状态管理、全局状态结局方案,可以用作大型程序的状态管理工具。

Redux介绍

学习Redux的意义

Redux作用机理

ReduxReducer相近,都使用dispatch作为操作变量的入口。它们的区别体现在Redux使用一个store来储存多种reducer,Reducer使用一个reducer来对应不同的动作。

使用方法

  • 使用Redux ToolkitconfigureStore来创建store
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { configureStore } from "@reduxjs/toolkit";

import accountReducer from "./features/accounts/accountSlice";
import customerReducer from "./features/customers/customerSlice";

const store = configureStore({
  reducer: {
    account: accountReducer,
    customer: customerReducer,
  },
});

export default store;
  • 使用Redux-slice来创建store中的reducer切片。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  fullName: "",
  nationalID: "",
  createdAt: "",
};

const customerSlice = createSlice({
  name: "customer",
  initialState,
  reducers: {
    createCustomer: {
      prepare(fullName, nationalID) {
        return {
          payload: {
            fullName,
            nationalID,
            createdAt: new Date().toISOString(),
          },
        };
      },
      reducer(state, action) {
        state.fullName = action.payload.fullName;
        state.nationalID = action.payload.nationalID;
        state.createdAt = action.payload.createdAt;
      },
    },
    updateName(state, action) {
      state.fullName = action.payload;
    },
  },
});

export const { createCustomer, updateName } = customerSlice.actions;

export default customerSlice.reducer;
  • 在程序中使用useDispatchuseSelect来操作、读取状态变量。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { useState } from "react";
import { useDispatch } from "react-redux";
import { createCustomer } from "./customerSlice";

function Customer() {
  const [fullName, setFullName] = useState("");
  const [nationalId, setNationalId] = useState("");

  const dispatch = useDispatch();

  function handleClick() {
    if (!fullName || !nationalId) return;
    dispatch(createCustomer(fullName, nationalId));
  }

  return (
    //...
  );
}

export default Customer;
// account.js

import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { deposit, payLoan, requestLoan, withdraw } from "./accountSlice";

function AccountOperations() {
  const [depositAmount, setDepositAmount] = useState("");
  const [withdrawalAmount, setWithdrawalAmount] = useState("");
  const [loanAmount, setLoanAmount] = useState("");
  const [loanPurpose, setLoanPurpose] = useState("");
  const [currency, setCurrency] = useState("USD");

  const dispatch = useDispatch();
  //use selector
  const {
    loan: currentLoan,
    loanPurpose: currentLoanPurpose,
    balance,
    isLoading,
  } = useSelector((store) => store.account);
    //...
}

还可以使用中间件(Middleware)Thunk来在Reducer中引入副作用。

与Context API的比较

优缺点

使用场景

总的来说,Redux适合大型软件的开发,但是不够轻量化,使用难度也更高。我们需要根据异步操作性能的需求来选择使用ReduxContext API进行状态管理。

状态管理总结

对SPA来说,可以归纳出以下四种状态变量:

  • 本地状态,仅在某些子组件中存在;
  • 全局状态,被大部分子组件使用;
  • 远程状态,需要从外部API获取;
  • UI状态,除远程状态外的状态。

状态种类

我们可以使用不同的工具,针对不同的状态进行管理。

状态管理工具