目前這個系列已挑選ChatGPT、前端&React相關章節出版成書!
感謝大家的閱讀與支持!
《用 ChatGPT 詠唱來點亮 React & 前端技能樹》
歷經重新整理、校正、編排、與修改後,讓章節內容之間的銜接更平順,除了補充上更多的解說之外,甚至有部分章節為重新撰寫的內容,讓理解變得更輕鬆!
目前已在天瓏書局、博客來、金石堂等通路都買得到了! 也有電子書的選擇!
歡迎各位點擊連結選購,謝謝大家~
在【Day19】ChatGPT請教教我:React入門(五)- 更多Hook!useMemo!useCallback!自定義Hook!
這個篇章的時候,ChatGPT就有提到useContext 和 useRedecer 這兩個hook
終於讓我講完redux可以來探討這兩個hook了
為什麼這兩個會被跟redux一起討論? 這兩個hook的作用和目的又是什麼?
先來請ChatGPT講解 useContext
先了解這兩個hook作用是什麼,再來討論和Redux的差異
當然可以!首先,我們來詳細地介紹 React 的
useContext
hook,並提供一個完整的 TypeScript 實作範例。使用
useContext
Hook
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. 最外層的父組件
App
import 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. 第一層的子組件
ParentComponent
import 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. 第二層的子組件
ChildComponent
import 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. 最內層的子組件
GrandchildComponent
import 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 中使用
useReducer
hook 的完整教學,並附上一個 TypeScript 實例。useReducer
是一個有助於管理較複雜狀態的 hook,通常用於處理涉及多個相關狀態的場景。使用
useReducer
Hook步驟一:建立 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
狀態的變化,可以進行增加、減少和重置操作。步驟二:使用
useReducer
Hook現在,讓我們在你的 React 組件中使用
useReducer
Hook。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;
在這個示例中,我們使用了
useReducer
Hook,並傳入 Reducer 函數reducer
和初始狀態initialState
。這將返回一個包含目前狀態 (state
) 和派發操作 (dispatch
) 函數的陣列。TypeScript 實例
這個示例是 TypeScript 實現的,所以你可以在 TypeScript 專案中使用它。確保在 reducer 函數和組件中明確指定型別,以確保 TypeScript 的類型檢查正確運作。
現在,當你在按鈕上點擊時,它將呼叫 reducer 函數以更新
count
狀態,並且組件會根據新的狀態重新渲染。希望這份教學對你有所幫助!如果有任何問題或需要進一步的說明,請隨時告訴我。
哇,跟我們知道的react redux還真是87%像
只是他不需要處理store的連接,也不用在根目錄中掛載
在需要使用的當前目錄中使用即可
那麼問題來了,useReducer可以跨組件處理嗎?
redux中,整個專案的store (狀態) 都是一致的、共通的
useReducer也可以嗎?
useReducer
的狀態和 dispatch 函數是由useReducer
hook 維護的,並且它們只能在使用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
現在,你可以在你的組件中使用
useAppContext
hook 來獲取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的前端工程師一宵三筵,我們明天見~