iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0
自我挑戰組

我與 React 的 30天系列 第 23

Day 23 useContext + useReducer 管理狀態

  • 分享至 

  • xImage
  •  

昨天介紹了 useState 的強化版工具,useReducer,今天就來是這搭配我們先前介紹的 useContext,讓我們可以不再用 props,一層一層傳遞state,我們可以從子層控制父層的狀態,那就讓我們開始吧

定義 reducer & createContext

首先我們先做出一個 CountContext.js ,在裡面定義,reducer & createContext
這麼做的好處是,我們只要找到這個檔案很清楚的知道我們 初始狀態,以及action 的觸發條件是什麼,這也讓我們可以方便去管理

// CountContext,js
import { createContext } from "react";

const initialState = { count: 0, secondCount: 10 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return {
        count: state.count + 1, 
        secondCount: state.secondCount + 1,  
      };
    case 'decrement':
      return {
        count: state.count - 1,
        secondCount: state.secondCount - 2,  
      };
    case 'reset':
      return initialState
    default:
      throw new Error();
  }
}

const CountContext = createContext();

export { CountContext, reducer, initialState }

因為我們是要用 Provider 去傳遞我們的狀態,所以我們在 App.js,變更結構,並且 import 剛剛的做的 CountContext

我們將在這使用 useReducer,並傳給 CountContext.Provider,還是記得 Provider 只能傳遞 value 這個值,我們將 statedispatch 包裝成一個物件,並交由 Provider 傳遞

// App.js
import { useReducer } from "react";
import { CountContext, reducer, initialState } from "./CountContext";
import { Parent } from "./Parent";
import "./index.css"



function App() {
  
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <CountContext.Provider value={{ state, dispatch }}>
      <div className="border">
        APP 第一層
        <div>
          first Count: {state.count}
        </div>
        <div>
          second Count: {state.secondCount}
        </div>
        <Parent />
      </div>
    </CountContext.Provider>
  );
}

export default App;

現在畫面呈現是這樣,我們可以看到我們的state都有成功出現在畫面上

好了,有了父層的傳球,接下來就是讓我們在子層去接球吧

我們將 Child.js 定義成這樣

// Child.js
import "./index.css"
import { CountContext } from "./CountContext";
import { useContext } from "react";

const Child = () => {
  const value = useContext(CountContext);
  const { dispatch } = useContext(CountContext);
  console.log(value);
  
  return (
    <div className="child" >
      child 第三層
      <button onClick={()=> dispatch({type: "increment"})}>++++++</button>
      <button onClick={()=> dispatch({type: "reset"})}>重置</button>
      <button onClick={()=> dispatch({type: "decrement"})}>------</button>
    </div>
  )
}

export { Child }

並且在Child.js,使用 useContext 去接球

我們可以將我們所接收的東西印出來看看,這就是我們要的東西

接著我們在這裡,只會使用到 dispatch,因為我們想要在這層,去控制父層的狀態,所以我們將 dispatch 解構出來

const { dispatch } = useContext(CountContext);

這樣我們就可以在這控制父層的state

為什麼不用 useState 呢?

其實也沒有說不行,但是我們在傳遞的時候你要先把多個state 先包裝成物件,像是下面這樣,之後再交給 provider 去傳遞

// App.js
  const [count, setCount] = useState(0)
  const [secCount, setSecondCount] = useState(10)

  const value = {
    first: [count, setCount],
    second: [secCount, setSecondCount]
  }
  
// Child.js
import "./index.css"
import { CountContext } from "./CountContext";
import { useContext } from "react";

const Child = () => {
  const {first, second} = useContext(CountContext);
  const setCount = first[1]
  const setSecondCount = second[1]

  const increment = () => {
    setCount(perv => perv + 1)
    setSecondCount(perv => perv + 1)
  }
  const decrement = () => {
    setCount(perv => perv - 1)
    setSecondCount(perv => perv - 1)
  }
  const reset = () => {
    setCount(0)
    setSecondCount(0)
  }
  
  return (
    <div className="child" >
      child 第三層
      
      <button onClick={increment}>++++++</button>
      <button onClick={reset}>重置</button>
      <button onClick={decrement}>-----</button>

    </div>
  )
}

export { Child }

這樣寫雖然也是有用,但是相對比較複雜,所以如果要使用 useContext 傳遞狀態的話,我認為配合 useReducer,會更加清楚,且好管理

小結

今天介紹了 useContext+useReducer` 來管理全局狀態,來實現輕量版的 Redux,


上一篇
Day 22 useReducer 有比 useState
下一篇
Day 24 React 的框架有哪些?
系列文
我與 React 的 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言