接續自己的文章 【React.js入門 - 21】 各階層Component的溝通
在React中常常會遇到需要在多層component的之間溝通的情形。在沒有其他插件下,就必須需要經由下面這種方式一層一層傳遞。
A元素
|
|【資料】
|
v
第1層子元素
|
|【資料】
|
v
第2層子元素
.
.
.
|
v
目標子元件
即使中間的中繼元件沒有要用到資料,還是必須要幫忙綁props,這種方式在層數太多的時候就顯得很麻煩。
於是Global state的概念就出現了。
原本的A元素提供資料的對象不再只是下一層的子元素,而是A元素往下所有階層的子元素。也就是元件之間的關係只剩下
|---> 第1層子元素
|
A元素--【資料】--|---> 第2層子元素
|
|---> 第n層子元素
---------------------------------
Provider | Consumer
而在React常見實現Global state的方式除了使用Redux外,還有搭配React hook使用React內建的Context api。
在接下來的內容中,我們將會把以下的程式碼改成使用Context api來傳遞資料。
FruitStore.js
|____Amy.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;
import React from 'react';
function Amy(props) {
return (
<div className="Amy">
Amy看到了 [ {props.apple} ] 個蘋果
</div>
);
}
export default Amy;
以下的Amy.js可以是在FruitStore子元素階層中任一層的子元素。
創造Global state的提供者(Provider)。
透過context api實作global state的方法是接收這個api的回傳值
React.createContext(初始值);
請先建立一個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,所以這裡寫使用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,就把存貨增加。
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