useContext
- 用法
- 使用
- 以非侵入的方式使用 Context
- 使用 useContext 重構 useReducer 案例
用法
實現多層組件的數據傳遞
- 在全局創建 Context 對象
- 在父組件中使用 Context.Provider 提供數據
- 在子組件中使用 useContext 使用數據
import React, { useContext } from 'react'
// 全局
const MyContext = React.createContext(初始數據)
// 父組件
const Father = () => {return <MyContext.Provider value={{name: 'escook', age: 22}}></MyContext.Provider>
}
// 子組件
const Son = () => {const myCtx = useContext(MyContext)return <div><p>姓名:{myCtx.name}</p><p>年齡:{MyCtx.age}</p></div>
}
使用
// 聲明 TS 類型
type ContextType = { count: number; setCount: React.Dispatch<React.SetStateAction<number>> }
// 1. 創建 Context 對象
const AppContext = React.createContext<ContextType>({} as ContextType)
export const LevelA: React.FC = () => {// 定義狀態const [count, setCount] = useState(0)return (<div style={{ padding: 30, backgroundColor: 'lightblue', width: '50vw' }}><p>count值是:{count}</p><button onClick={() => setCount((prev) => prev + 1)}>+1</button>{/* 2. 使用 Context.Provider 向下傳遞數據 */}<AppContext.Provider value={{ count, setCount }}> {/* 使用子組件 */}<LevelB /></AppContext.Provider> </div>)
}
export const LevelB: React.FC = () => {return (<div style={{ padding: 30, backgroundColor: 'lightgreen' }}>{/* 使用子組件 */}<LevelC /></div>)
}
export const LevelC: React.FC = () => {// 3. 使用 useContext 接收數據const ctx = useContext(AppContext) return (<div style={{ padding: 30, backgroundColor: 'lightsalmon' }}>{/* 4. 使用 ctx 中的數據和方法 */}<p>count值是:{ctx.count}</p> <button onClick={() => ctx.setCount((prev) => prev + 1)}>+1</button><button onClick={() => ctx.setCount(0)}>重置</button> </div>)
}
以非侵入的方式使用 Context
保證父組件中代碼的單一性和Provider的通用性,解決父組件中侵入了 <AppContext.Provider> 的問題,
Context.Provider 封裝到獨立的 Wrapper 函數式組件中。
核心思路:每個 Context 都創建一個對應的 Wrapper 組件,在 Wrapper 組件中使用 Provider 向 children 注入數據。
// 聲明 TS 類型
type ContextType = { count: number; setCount: React.Dispatch<React.SetStateAction<number>> }
// 創建 Context 對象
const AppContext = React.createContext<ContextType>({} as ContextType)
// 定義獨立的 Wrapper 組件,被 Wrapper 嵌套的子組件會被 Provider 注入數據
export const AppContextWrapper: React.FC<React.PropsWithChildren> = (props) => {// 1. 定義要共享的數據const [count, setCount] = useState(0)// 2. 使用 AppContext.Provider 向下共享數據return <AppContext.Provider value={{ count, setCount }}>{props.children}</AppContext.Provider>
}
import React from 'react'
import { AppContextWrapper, LevelA } from '@/components/use_context/01.base.tsx'const App: React.FC = () => {return (<AppContextWrapper><!-- AppContextWrapper 中嵌套使用了 LevelA 組件,形成了父子關系 --><!-- LevelA 組件會被當做 children 渲染到 Wrapper 預留的插槽中 --><LevelA /></AppContextWrapper>)
}
export default App
組件樹的嵌套關系為:App => Wrapper => LevelA => LevelB => LevelC。因此在 LevelA、LevelB 和 LevelC 組件中,都可以使用 context 中的數據。
export const LevelA: React.FC = () => {// 使用 useContext 接收數據const ctx = useContext(AppContext)return (<div style={{ padding: 30, backgroundColor: 'lightblue', width: '50vw' }}>{/* 使用 ctx 中的數據和方法 */}<p>count值是:{ctx.count}</p><button onClick={() => ctx.setCount((prev) => prev + 1)}>+1</button><LevelB /></div>)
}
export const LevelC: React.FC = () => {// 使用 useContext 接收數據const ctx = useContext(AppContext)return (<div style={{ padding: 30, backgroundColor: 'lightsalmon' }}>{/* 使用 ctx 中的數據和方法 */}<p>count值是:{ctx.count}</p><button onClick={() => ctx.setCount((prev) => prev + 1)}>+1</button><button onClick={() => ctx.setCount(0)}>重置</button></div>)
}
使用 useContext 重構 useReducer 案例
// 1. 定義 Context 的 TS 類型
// 在這一步,我們必須先明確要向子組件注入的數據都有哪些
type UserInfoContextType = { user: UserType; dispatch: React.Dispatch<ActionType> }
// 2. 創建 Context 對象
const UserInfoContext = React.createContext<UserInfoContextType>({} as UserInfoContextType)
// 3. 創建 ContextWrapper 組件
export const UserInfoContextWrapper: React.FC<React.PropsWithChildren> = ({ children }) => {const [state, dispatch] = useImmerReducer(reducer, defaultState, initAction)return <UserInfoContext.Provider value={{ user: state, dispatch }}>{children}</UserInfoContext.Provider>
}
import React from 'react'
import { UserInfoContextWrapper, Father } from '@/components/use_reducer/01.base.tsx'const App: React.FC = () => {return (<UserInfoContextWrapper><Father /></UserInfoContextWrapper>)
}export default App
export const Father: React.FC = () => {// 4. 調用 useContext 導入需要的數據const { user: state, dispatch } = useContext(UserInfoContext)const changeUserName = () => dispatch({ type: 'UPDATE_NAME', payload: '劉龍彬' })return (<div><button onClick={changeUserName}>修改用戶名</button><p>{JSON.stringify(state)}</p><div className="father">{/* 5. 這里沒有必要再往子組件傳遞 props 了 */}{/* <Son1 {...state} dispatch={dispatch} /><Son2 {...state} dispatch={dispatch} /> */}<Son1 /><Son2 /></div></div>)
}
const Son1: React.FC = () => {// 6. 把 props 替換為 useContext() 的調用const { dispatch, user } = useContext(UserInfoContext)const add = () => dispatch({ type: 'INCREMENT', payload: 1 })return (<div className="son1"><p>{JSON.stringify(user)}</p><button onClick={add}>年齡+1</button></div>)
}
const Son2: React.FC = () => {// 7. 把 props 替換為 useContext() 的調用const { dispatch, user } = useContext(UserInfoContext)const sub = () => dispatch({ type: 'DECREMENT', payload: 5 })return (<div className="son2"><p>{JSON.stringify(user)}</p><button onClick={sub}>年齡-5</button><hr /><GrandSon /></div>)
}
const GrandSon: React.FC = () => {// 8. 把 props 替換為 useContext() 的調用const { dispatch } = useContext(UserInfoContext)const reset = () => dispatch({ type: 'RESET' })return (<><h3>這是 GrandSon 組件</h3><button onClick={reset}>重置</button></>)
}