【React.js 笔记】- 使用useContext和useReducer进行多层子父元件沟通

接续自己的文章 【React.js入门 - 21】 各阶层Component的沟通

在React中常常会遇到需要在多层component的之间沟通的情形。在没有其他插件下,就必须需要经由下面这种方式一层一层传递。

A元素||【资料】|v第1层子元素||【资料】|v第2层子元素...|v目标子元件

即使中间的中继元件没有要用到资料,还是必须要帮忙绑props,这种方式在层数太多的时候就显得很麻烦。

于是Global state的概念就出现了。

Global state

原本的A元素提供资料的对象不再只是下一层的子元素,而是A元素往下所有阶层的子元素。也就是元件之间的关係只剩下

提供资料的元件(Provider) - A使用资料的元件(Consumer) - A以下所有子元素
                |---> 第1层子元素                 |A元素--【资料】--|---> 第2层子元素                 |                |---> 第n层子元素                --------------------------------- Provider       |     Consumer     

而在React常见实现Global state的方式除了使用Redux外,还有搭配React hook使用React内建的Context api。

在接下来的内容中,我们将会把以下的程式码改成使用Context api来传递资料。

结构
FruitStore.js|____Amy.js
FruitStore.js
import React,{useState} from 'react';import Amy from './Amy.js';function FruitStore() {    const [ apple, setApple ] = useState(0);    return (        <>            <div className="FruitStore">目前水果店有 [ {apple} ] 个苹果</div>            <Amy apple={apple}/>        </>    );}export default FruitStore;
Amy.js
import React from 'react';function Amy(props) {    return (        <div className="Amy">            Amy看到了 [ {props.apple} ] 个苹果        </div>    );}export default Amy;

注:这里的Amy应该要能同步显示苹果的数量。

Context的读取

以下的Amy.js可以是在FruitStore子元素阶层中任一层的子元素。

创造Global state的提供者(Provider)。
透过context api实作global state的方法是接收这个api的回传值

React.createContext(初始值);

请先建立一个FruitContext.js,并输入以下内容:

FruitContext.js
import React from "react";export const FruitContext = React.createContext({    appleContext: 1,})

在这里,我们先创造了一个为Object的Global state,他的初始值为{appleContext:1},并用一个变数FruitContext来接收他,并透过export让其他档案可以引入。

在父元素引入并使用Context。
使用Context的方法是使用<名称.Provider value={state值}>把你想要允许可以读取这个context的子元素阶层包起来。例如:

<FruitContext.Provider value={{ appleContext:apple }} >    <Amy/></FruitContext.Provider>

实作的方法如下: 先引入刚刚建立的FruitContext
FruitStore.js

import React,{useState} from 'react';import Amy from './Amy.js';import {FruitContext} from "./FruitContext.js";

再把Amy用引入的FruitContext包起来

import React,{useState} from 'react';import Amy from './Amy.js';import {FruitContext} from "./FruitContext.js";function FruitStore() {    const [ apple, setApple ] = useState(0);    return (        <>            <div className="FruitStore">目前水果店有 [ {apple} ] 个苹果</div>            <FruitContext.Provider value={{ appleContext:apple }} >                <Amy/>            </FruitContext.Provider>        </>    );}export default FruitStore;

在需要读取这个context的子元素中,利用useContext()这个React hook去监听读取的context。

const fruitInfo=useContext(FruitContext);

如上所示,此时使用fruitInfo的效果就跟一般state一样,当FruitContext被改变时,使用到fruitInfo的这个元件就会被重新render。

在刚刚的範例中是这样实现的:
Amy.js

import React,{useContext} from 'react';import {FruitContext} from "./FruitContext.js";function Amy() {    const fruitInfo=useContext(FruitContext);    return (    <div className="Amy">        Amy看到了 [ {fruitInfo.appleContext} ] 个苹果    </div>  );}export default Amy;

Context的修改 - 搭配useReducer实现Redux

Context也可以直接传入函式,但我想练一下useReducer,所以这里写使用useReducer的方法。

useReducer是一个乍看之下有点像state和setState的工具。但运作上和setState不同的地方在,setState(传入值)是直接把state=传入值,但useReducer是透过预先定义好数种运算传入参数的方式,让操作state的呼叫者从当中择一使用、传入对应参数后进行运算,再将结果赋予给state。

Redux把这个「决定要如何运算传入参数」的过程称为reducer,呼叫者的选择方式、传入参数....等操作称为action,而把呼叫者的action传给reducer的过程称为dispatch。

dispatch(action) -> reducer

由于笔者目前对Redux还不熟悉,建议可以查看看有关Redux更详细、正确的流程架构。

现在,我们来用useReducer这个React hook来实现Redux。

在FruitStore.js中定义reducer(决定要如何运算传入参数)
如果子元素(客人)要跟FruitStore买,就把存货减少,要卖给FruitStore,就把存货增加。

参数state: 当前的state状况参数action: 子元素传来的参数回传值: 新的state的值。
function reducer(state, action) {    switch (action.type) {      case 'buy':        return state-action.value;      case 'sell':        return state+action.value;      default:        throw new Error();    }}

引入useReducer,取得state和dispatch。
使用useReducer的语法为

型态 [state, dispatch] = useReducer(reducer函式, state的初始值);

dispatch是有点像是setState的函式,主要功用是用它传入action后,它会把改变前的state和action传给reducer,让reducer去决定要做什么事。

实际用在FruitStore.js中:

import React,{useReducer} from 'react';const [appleState, appleDispatch] = useReducer(reducer, 3);

将state和dispatch放入context中,提供给子元素使用。
要注意的是因为useReducer已经提供了state和用来设定state的dispatch,原本的state和setState就不用了。

FruitStore.js

import React,{useReducer} from 'react';import Amy from './Amy.js';import {FruitContext} from "./FruitContext.js";function FruitStore() {    function reducer(state, action) {        switch (action.type) {          case 'buy':            return state-action.value;          case 'sell':            return state+action.value;          default:            throw new Error();        }    }    const [appleState, appleDispatch] = useReducer(reducer, 3);    return (        <>            <div className="FruitStore">目前水果店有 [ {appleState} ] 个苹果</div>            <FruitContext.Provider value={{                 appleContext: appleState,                 setAppleByDispatch: appleDispatch            }} >                <Amy/>            </FruitContext.Provider>        </>    );}export default FruitStore;

在子元素中,使用state,并呼叫dispatch

Amy.js

import React,{useContext} from 'react';import {FruitContext} from "./FruitContext.js";function Amy() {    const fruitInfo=useContext(FruitContext);    return (    <div className="Amy">        Amy看到了 [ {fruitInfo.appleContext} ] 个苹果        <button onClick={()=>{fruitInfo.setAppleByDispatch({type:"buy",value:1})}}>买一个苹果</button>    </div>  );}export default Amy;

{type:"buy",value:1}就是action。

此时点击按键,苹果就会减少。如果你把action改成{type:"sell",value:1}后点击按键,苹果就会增加。

运作的流程类似是这样(模拟而已,不等于真实的状况)

dispatch(action={type:"buy",value:1}){    setAppleState(reducer(appleState,action));}

结语

最近比较没时间,把随手的笔记先放在这,这篇缺满多东西的,有空的时候再回来补缺的地方QQ


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章