目前這個系列已挑選ChatGPT、前端&React相關章節出版成書!
感謝大家的閱讀與支持!
《用 ChatGPT 詠唱來點亮 React & 前端技能樹》
歷經重新整理、校正、編排、與修改後,讓章節內容之間的銜接更平順,除了補充上更多的解說之外,甚至有部分章節為重新撰寫的內容,讓理解變得更輕鬆!
目前已在天瓏書局、博客來、金石堂等通路都買得到了! 也有電子書的選擇!
歡迎各位點擊連結選購,謝謝大家~
在【Day19】ChatGPT請教教我:React入門(五)- 更多Hook!useMemo!useCallback!自定義Hook!
這個篇章的時候,ChatGPT就有提到useContext 和 useRedecer 這兩個hook
終於讓我講完redux可以來探討這兩個hook了
為什麼這兩個會被跟redux一起討論? 這兩個hook的作用和目的又是什麼?
先來請ChatGPT講解 useContext
先了解這兩個hook作用是什麼,再來討論和Redux的差異

當然可以!首先,我們來詳細地介紹 React 的
useContexthook,並提供一個完整的 TypeScript 實作範例。使用
useContextHook
useContext是 React 提供的一個 hook,它可以讓你在函數組件中使用 Context。Context 允許你在組件樹中傳遞數據,而無需一級一級地手動傳遞 props。步驟一:建立 Context
首先,你需要建立一個 Context。Context 可以包含一個預設值,這個值將在沒有匹配的 Provider 時被使用。
// MyContext.tsx import React, { createContext, ReactNode } from 'react'; // 定義 Context 的型別 interface MyContextType { // 這裡放置你想要共享的數據 myData: string; } // 建立 Context const MyContext = createContext<MyContextType | undefined>(undefined); // Context Provider 的包裝元件 interface MyContextProviderProps { children: ReactNode; } const MyContextProvider: React.FC<MyContextProviderProps> = ({ children }) => { // 在這裡設定你想共享的數據 const myData = '這是我要共享的數據'; return <MyContext.Provider value={{ myData }}>{children}</MyContext.Provider>; }; export { MyContext, MyContextProvider };步驟二:在應用程式中使用 Provider
接著,你需要在你的應用程式中使用這個 Provider。這通常會在你的應用程式的最高層級進行。
// App.tsx import React from 'react'; import { MyContextProvider } from './MyContext'; const App: React.FC = () => { return ( <MyContextProvider> {/* 這裡放置你的應用程式內容 */} </MyContextProvider> ); }; export default App;步驟三:在子組件中使用
useContext最後,你可以在你的子組件中使用
useContext來獲取 Context 中的數據。// ChildComponent.tsx import React, { useContext } from 'react'; import { MyContext } from './MyContext'; const ChildComponent: React.FC = () => { // 使用 useContext 來獲取 Context 中的數據 const contextValue = useContext(MyContext); if (!contextValue) { throw new Error('useContext 必須在 MyContextProvider 內使用'); } return <div>{contextValue.myData}</div>; }; export default ChildComponent;現在,當
ChildComponent被渲染時,它會從MyContext中獲取myData。TypeScript 實作範例
這是一個 TypeScript 實作的範例:
// 使用 App、MyContextProvider、ChildComponent 前面提供的程式碼 // 在 App.tsx 中 import React from 'react'; import { MyContextProvider } from './MyContext'; import ChildComponent from './ChildComponent'; const App: React.FC = () => { return ( <MyContextProvider> <ChildComponent /> </MyContextProvider> ); }; export default App;這個範例中,當
ChildComponent被渲染時,它會從MyContext中獲取myData,並顯示在畫面上。希望這份教學對你有所幫助!如果有任何問題或需要進一步的說明,請隨時告訴我。
所以useContext是為了解決層層的props傳遞
在React的開發中,我們會把畫面中的功能,切成一塊一塊的Component
但若這些Component層層嵌套、又需要對同一個state進行更新或讀取
那程式碼就會變得相當複雜
我們請ChatGPT提供一個這樣的範例

當然可以,我可以給你一個範例來說明在沒有使用
useContext的情況下,需要一層層地傳遞 props 的情況。假設我們有一個包含多個組件的應用程式,它們需要共享一個名為
userData的數據。例子:使用 props 逐層傳遞數據
1. 最外層的父組件
Appimport React from 'react'; import ParentComponent from './ParentComponent'; const App: React.FC = () => { const userData = { name: 'John Doe', age: 30, }; return <ParentComponent userData={userData} />; }; export default App;2. 第一層的子組件
ParentComponentimport React from 'react'; import ChildComponent from './ChildComponent'; interface Props { userData: { name: string; age: number; }; } const ParentComponent: React.FC<Props> = ({ userData }) => { return <ChildComponent userData={userData} />; }; export default ParentComponent;3. 第二層的子組件
ChildComponentimport React from 'react'; import GrandchildComponent from './GrandchildComponent'; interface Props { userData: { name: string; age: number; }; } const ChildComponent: React.FC<Props> = ({ userData }) => { return <GrandchildComponent userData={userData} />; }; export default ChildComponent;4. 最內層的子組件
GrandchildComponentimport React from 'react'; interface Props { userData: { name: string; age: number; }; } const GrandchildComponent: React.FC<Props> = ({ userData }) => { return ( <div> <h2>User Data</h2> <p>Name: {userData.name}</p> <p>Age: {userData.age}</p> </div> ); }; export default GrandchildComponent;這個例子中,我們從最外層的
App組件開始,逐層將userData作為 props 傳遞給子組件,直到最內層的GrandchildComponent才使用這個數據。如果我們使用了
useContext,我們可以在不需要一層層傳遞的情況下,在任何地方直接獲取到userData。這樣可以減少了逐層傳遞 props 的麻煩,特別在複雜的應用程式中,這會變得非常方便。
ChatGPT給我們看的例子是,中間的Component雖然完全沒有需要那個userData
但因為他的子元件需要,所以要把資料作為Props
從Props取得資料,但又直接傳遞給子Component
請ChatGPT繪製一下兩種情形的流程圖好了
以下是使用 Mermaid 語法分別表示上述例子中,有使用
useContext和沒有使用useContext時,組件、props、以及useContext之間的資料傳遞關係的示意圖:有使用 useContext 的示意圖
graph TD subgraph useContext A(App) A --> P(ParentComponent) P --> C(ChildComponent) C --> G(GrandchildComponent) style A fill:#FFE4B5,stroke:#333 style P fill:#FFD700,stroke:#333 style C fill:#FFD700,stroke:#333 style G fill:#FFD700,stroke:#333 end subgraph ContextProvider A -->|userData| Provider(MyContextProvider) end subgraph ContextConsumer G -->|userData| Consumer(MyContext) end在這個示意圖中,
MyContextProvider提供了userData,而MyContext作為Consumer被GrandchildComponent使用。

沒有使用 useContext 的示意圖
graph TD A(App) A -->|userData| P(ParentComponent) P -->|userData| C(ChildComponent) C -->|userData| G(GrandchildComponent) style A fill:#FFE4B5,stroke:#333 style P fill:#FFD700,stroke:#333 style C fill:#FFD700,stroke:#333 style G fill:#FFD700,stroke:#333在這個示意圖中,
userData逐層地作為 props 傳遞給子組件,直到GrandchildComponent。這種傳遞方式會在組件層次多的情況下變得更加複雜和不便。

恩…總之有使用useContext的時候,可以看作是把資料存在一個地方
需要的Component只要在需要的時候,去那個地方拿取就好
但沒有使用useContext的時候,只因為子Component需要
就必須把這個userData像是接力賽一樣傳遞下去
上面的例子示範的是「讀取state」,但沒有給出要update的話要怎麼做
如果深層的子組件需要更改由
useContext取得的狀態,你需要進行以下步驟:
建立 Context 和 Provider
首先,確保你已經建立了相應的 Context 和 Provider,並在最高層級的父組件中使用了 Provider。
在深層的子組件中使用
useContext取得狀態在你的深層子組件中,使用
useContext來獲取 Context 中的狀態。import React, { useContext } from 'react'; import { MyContext } from './MyContext'; const DeepChildComponent: React.FC = () => { const { myData, setMyData } = useContext(MyContext); const handleButtonClick = () => { // 在這裡更新狀態 setMyData('新的數據'); }; return ( <div> <p>{myData}</p> <button onClick={handleButtonClick}>更改數據</button> </div> ); }; export default DeepChildComponent;注意,這裡假設
MyContext中有一個myData狀態以及一個setMyData函數用於更新這個狀態。在 Provider 中提供更新狀態的函數
在
MyContextProvider中,你需要提供一個函數來更新myData的狀態。const MyContextProvider: React.FC<MyContextProviderProps> = ({ children }) => { const [myData, setMyData] = useState('這是我要共享的數據'); return <MyContext.Provider value={{ myData, setMyData }}>{children}</MyContext.Provider>; };這樣,在子組件中就可以使用
setMyData來更新myData的狀態了。這樣,當你在深層子組件中調用
setMyData時,會觸發 Context 中的狀態更新,並且相應的組件將重新渲染以反映這個變化。希望這能幫到你!如果你有任何進一步的問題,請隨時告訴我。

是的,你可以使用多個
createContext來建立多個 Context,每個 Context 可以有自己的 Provider。這樣可以在應用程式中管理不同的狀態或數據,而且它們之間是獨立的。多個 Context 和 Provider 之間的關係是平行的,也就是它們並不相依或嵌套。每個 Provider 可以提供一個獨立的數據或狀態,而在組件中,你可以使用
useContext來選擇要使用哪個 Context。以下是一個簡單的示例,演示如何使用多個 Context 和 Provider:
import React, { createContext, useContext } from 'react'; // 第一個 Context 和 Provider const FirstContext = createContext<string | undefined>(undefined); const FirstProvider: React.FC = ({ children }) => { const firstData = '這是第一個 Context 的數據'; return <FirstContext.Provider value={firstData}>{children}</FirstContext.Provider>; }; // 第二個 Context 和 Provider const SecondContext = createContext<number | undefined>(undefined); const SecondProvider: React.FC = ({ children }) => { const secondData = 42; return <SecondContext.Provider value={secondData}>{children}</SecondContext.Provider>; }; // 範例組件 const ExampleComponent: React.FC = () => { const firstData = useContext(FirstContext); const secondData = useContext(SecondContext); return ( <div> <p>第一個 Context 的數據:{firstData}</p> <p>第二個 Context 的數據:{secondData}</p> </div> ); }; // 應用程式中使用多個 Provider const App: React.FC = () => { return ( <FirstProvider> <SecondProvider> <ExampleComponent /> </SecondProvider> </FirstProvider> ); }; export default App;在這個示例中,我們建立了兩個獨立的 Context 和 Provider,分別是
FirstContext和SecondContext。在應用程式的最高層級App中,我們將ExampleComponent包裝在這兩個 Provider 中,以便在ExampleComponent中可以訪問到相應的數據。這樣,你可以在不同的地方使用不同的 Context 和 Provider,以管理和共享不同的數據或狀態。
大概了解useContext的用法,以及複數個useContext的用法之後
來問問ChatGPT,useContext的使用細節或是規則
當使用
useContext時,有一些注意事項和規則需要遵守:
必須在
Function Component或自訂Hook中使用
useContext只能在Function Component或自訂Hook中使用。不能在類別組件或普通 JavaScript 函數中使用。必須在
Provider範圍內使用使用
useContext前,確保你的組件位於對應Provider的範圍內。如果你在Provider的外部使用useContext,將會獲得預設值或undefined。只能消費單一 Context
useContext只能消費一個 Context,也就是一個createContext創建的 Context。Context 必須有 Provider
確保你使用的 Context 必須有對應的 Provider。如果沒有 Provider,
useContext會返回undefined。Context 值的變更會引起重新渲染
當 Context 中的值發生變化時,使用這個 Context 的所有組件都會重新渲染。
避免在
render方法中使用不要在
render方法中使用useContext,這可能會導致每次重新渲染都創建一個新的 Context。總的來說,使用
useContext是一種方便且有效的方式來在 React 中獲取 Context 的值,但要確保你在正確的範圍內使用它並遵守上述規則。如果你有特定的情境或程式碼,可以提供給我,我可以幫助你確保它們符合這些注意事項和規則。
接下來換useReducer
當然,我們來提供 React 中使用
useReducerhook 的完整教學,並附上一個 TypeScript 實例。useReducer是一個有助於管理較複雜狀態的 hook,通常用於處理涉及多個相關狀態的場景。使用
useReducerHook步驟一:建立 Reducer 函數
首先,你需要建立一個 Reducer 函數,它將負責處理狀態的變化。Reducer 是一個純函數,它接收當前的狀態和一個操作 (action),然後返回新的狀態。
// reducer.ts type State = { count: number; }; type Action = | { type: 'increment' } | { type: 'decrement' } | { type: 'reset' }; const initialState: State = { count: 0, }; const reducer = (state: State, action: Action): State => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return initialState; default: return state; } }; export { initialState, reducer };在這個示例中,我們建立了一個 Reducer 函數,它處理
count狀態的變化,可以進行增加、減少和重置操作。步驟二:使用
useReducerHook現在,讓我們在你的 React 組件中使用
useReducerHook。import React, { useReducer } from 'react'; import { initialState, reducer } from './reducer'; const Counter: React.FC = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>Increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button> <button onClick={() => dispatch({ type: 'reset' })}>Reset</button> </div> ); }; export default Counter;在這個示例中,我們使用了
useReducerHook,並傳入 Reducer 函數reducer和初始狀態initialState。這將返回一個包含目前狀態 (state) 和派發操作 (dispatch) 函數的陣列。TypeScript 實例
這個示例是 TypeScript 實現的,所以你可以在 TypeScript 專案中使用它。確保在 reducer 函數和組件中明確指定型別,以確保 TypeScript 的類型檢查正確運作。
現在,當你在按鈕上點擊時,它將呼叫 reducer 函數以更新
count狀態,並且組件會根據新的狀態重新渲染。希望這份教學對你有所幫助!如果有任何問題或需要進一步的說明,請隨時告訴我。
哇,跟我們知道的react redux還真是87%像
只是他不需要處理store的連接,也不用在根目錄中掛載
在需要使用的當前目錄中使用即可
那麼問題來了,useReducer可以跨組件處理嗎?
redux中,整個專案的store (狀態) 都是一致的、共通的
useReducer也可以嗎?

useReducer的狀態和 dispatch 函數是由useReducerhook 維護的,並且它們只能在使用useReducer的那個組件內部存取。這意味著useReducer的內容不能直接跨越多個不同的組件存取。如果你想要在多個組件之間共享
useReducer的狀態和操作,有幾種方法可以實現:
使用 Context
你可以將
useReducer的狀態和 dispatch 函數存放在一個 Context 中,然後讓多個組件消費這個 Context。這樣,多個組件就可以訪問相同的狀態和操作。// 在某個上層組件中定義 Context const MyContext = createContext(); // 在提供狀態的組件中使用 useReducer const [state, dispatch] = useReducer(reducer, initialState); // 在上層組件的 value 中提供狀態和 dispatch <MyContext.Provider value={{ state, dispatch }}> {/* 子組件可以消費 MyContext 並訪問狀態和操作 */} <ChildComponent /> </MyContext.Provider>使用 Redux 或其他狀態管理庫
Redux 是一個非常流行的狀態管理庫,它允許你在整個應用程式中共享狀態。如果你的應用程式需要跨多個組件共享狀態,那麼使用 Redux 或其他類似的狀態管理庫可能是一個更好的選擇。
使用上層組件作為中介
你可以將
useReducer的狀態和操作存放在一個上層組件中,然後透過 props 傳遞給需要訪問這些資訊的子組件。這種方法適用於簡單的情境,不需要引入額外的狀態管理庫或 Context。// 上層組件中維護 useReducer 的狀態和操作 const [state, dispatch] = useReducer(reducer, initialState); // 將狀態和操作透過 props 傳遞給子組件 <ChildComponent state={state} dispatch={dispatch} />每種方法都有其適用的情境,具體取決於你的應用程式的需求和架構。選擇適合你項目的方法後,你可以實現在多個組件之間共享
useReducer的狀態和操作。
嗯,答案是不行呢。
如果在不同的組件,都使用const [state, dispatch] = useReducer(reducer, initialState);
他跟useState比較相像,兩個組件的內容是獨立分開的
沒道理共通啊 (reducer 也只是一個function而已)
所以ChatGPT跟我們說,哦~可以搭配useContext
一個負責讓 state可以直接跨組件存取,一個負責做狀態管理
合起來的功效就像是使用redux
先讓ChatGPT提供一個兩者結合的範例程式碼

當然,以下是一個結合了 Context 和
useReducer的完整範例。這個範例將演示如何在 React 中使用 Context 來共享useReducer的狀態和 dispatch 函數。步驟一:建立 Context 和 Reducer
首先,我們需要建立一個 Context 和相應的 Reducer。
// AppContext.tsx import React, { createContext, useReducer, useContext, Dispatch } from 'react'; interface State { count: number; } type Action = { type: 'increment' } | { type: 'decrement' }; const initialState: State = { count: 0, }; const AppContext = createContext<{ state: State; dispatch: Dispatch<Action> } | undefined>(undefined); const reducer = (state: State, action: Action): State => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error('Unhandled action type'); } }; const AppProvider: React.FC = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return <AppContext.Provider value={{ state, dispatch }}>{children}</AppContext.Provider>; }; const useAppContext = () => { const context = useContext(AppContext); if (context === undefined) { throw new Error('useAppContext must be used within an AppProvider'); } return context; }; export { AppProvider, useAppContext };步驟二:使用 Context Provider
在你的應用程式的最高層組件中使用 Context Provider。
// App.tsx import React from 'react'; import { AppProvider } from './AppContext'; import Counter from './Counter'; const App: React.FC = () => { return ( <AppProvider> <Counter /> </AppProvider> ); }; export default App;步驟三:在組件中使用 Context
現在,你可以在你的組件中使用
useAppContexthook 來獲取state和dispatch。// Counter.tsx import React from 'react'; import { useAppContext } from './AppContext'; const Counter: React.FC = () => { const { state, dispatch } = useAppContext(); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>Increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button> </div> ); }; export default Counter;現在,當你點擊按鈕時,它將使用 Reducer 更新
count狀態,並且相應的組件將重新渲染以反映這個變化。這是一個完整的範例,演示了如何結合 Context 和
useReducer來管理全局狀態。希望對你有所幫助!如果有任何問題或需要進一步的說明,請隨時告訴我。
useReducer + useContext 基本上就跟單用react redux很相像
請ChatGPT來幫我們分析比較一下
使用 useReducer + useContext 和用 redux + redux toolkit的差異

使用
useReducer+useContext與使用 Redux + Redux Toolkit 有一些重要的差異。以下是它們之間的比較,以及各自的優點和缺點:使用
useReducer+useContext優點:
- 輕量級:不需要引入額外的庫,這使得代碼相對較輕,並且可以更容易地掌握。
- 更靈活:可以更靈活地管理狀態和操作,不需要依賴外部庫的約定。
- 學習曲線較低:對於小型應用程式或初學者來說,學習
useReducer和useContext相對較容易。缺點:
- 需要手動管理全局狀態:你需要自己設計和管理全局狀態的結構,這可能需要更多的代碼和努力。
- 不適合大型應用程式:對於大型應用程式,手動管理全局狀態可能變得複雜和困難。
- 沒有內置的工具和中間件:相對於 Redux,缺少像 Redux DevTools 和中間件(例如 redux-thunk)等強大的工具和擴展。
使用 Redux + Redux Toolkit
優點:
- 強大的工具和生態系統:Redux 提供了大量的工具,包括 Redux DevTools,以方便開發和調試。Redux Toolkit 簡化了 Redux 的用法,減少了模板代碼。
- 適用於大型應用程式:Redux 的架構和中間件使其適合處理大型應用程式的複雜狀態管理需求。
- 社群和資源豐富:Redux 有一個龐大的社群和大量的資源,可以幫助你解決各種狀態管理相關的問題。
缺點:
- 學習曲線較陡:對於初學者來說,Redux 的概念和用法可能較難理解。
- 相對複雜:Redux 的設定和用法可能相對複雜,特別是在小型應用程式中可能會感到過度工程。
- 冗余的代碼:Redux 在某些情況下可能需要較多的代碼,因為它要求你定義多個文件(例如 actions、reducers、selectors)。
總結來說,選擇使用
useReducer+useContext還是 Redux + Redux Toolkit 取決於你的應用程式規模和需求,以及你的經驗水平。對於小型應用程式或初學者來說,useReducer+useContext可能更簡單。對於大型應用程式或需要更多工具和生態系統支持的專案來說,Redux + Redux Toolkit 可能更適合。最終,選擇取決於你的項目目標和你自己的舒適度水平。無論你選擇哪種方法,都應該根據具體需求來決定。

這次的對話內容綜合了多個方面,主要討論了React中的
useContext和useReducer的使用,以及與Redux + Redux Toolkit的比較。以下是一些重要的要點:
useContext可以用於在React應用程式中建立全局狀態並在組件之間共享。useReducer是一個用於處理較複雜狀態的hook,通常與useContext一起使用以共享全局狀態。- 使用
createContext和useReducer的組合,可以建立一個自定義的全局狀態管理系統。- 在使用
useReducer和useContext時,必須遵守相關的規則,並確保它們在適當的範圍內使用。- Redux和Redux Toolkit是強大的狀態管理庫,特別適用於大型應用程式,提供了許多工具和生態系統支持。
- 選擇使用
useReducer+useContext還是Redux + Redux Toolkit應基於應用程式的規模和需求,以及個人經驗水平進行考慮。總結來說,React提供了多種選擇來處理全局狀態,每種方法都有其優點和局限性。根據項目需求和個人偏好,你可以選擇適合你的方式。重要的是理解這些工具的使用方式,以便在需要時能夠選擇最合適的解決方案。無論你選擇哪種方法,都可以在React應用程式中有效地管理狀態並構建強大的應用程式。如果你需要進一步的幫助或資訊,請隨時提問。
本次的對話記錄在這裡:
https://chat.openai.com/share/a9dfdc60-2538-4a7e-ab78-c606827ea083
useReducer 和useContext 都可以單獨使用
也可以組合起來,變成比較輕量版的Redux
但就不像Redux可以借助Redux Toolkit 或 redux-thunk 等其他套件
來簡化程式碼以及處理異步邏輯等等
所有和redux、reducer相關的內容就這邊討論結束
下個章節稍微介紹一下react中處理style的方式
以及我個人比較偏好的套件: styled-component
我是用詠唱來學習redux的前端工程師一宵三筵,我們明天見~