On the first code
To create the Context
// ./src/context/GlobalContext.tsx
/** * defines the type interface to store */
export interface GlobalFace {
userName: string
age: number
}
/** * Initial value */
export const globalDataInit: GlobalFace = {
userName: "Zhang".age: 0,}/** * GlobalReducer interface */
export interface GlobalReducerProps {
globalState: GlobalFace
setGlobalState: Dispatch<Partial<GlobalFace>>
}
export const GlobalContext = createContext<GlobalReducerProps>({
globalState: globalDataInit,
setGlobalState: () = > {
throw new Error("GlobalContext not defined")}})export const GlobalReducer = (prevState: GlobalFace, updatedProperty: Partial<GlobalFace>): GlobalFace= >({... prevState, ... updatedProperty })Copy the code
Used by the current hierarchy component
The correct way to use the current component
// ./src/App.tsx
const App: FC = () = > {
const [globalState, setGlobalState] = useReducer(GlobalReducer, globalDataInit)
return (
<GlobalContext.Provider value={{ globalState.setGlobalState}} >
<div>
<p>Use the current component correctly</p>
<p>Name: {globalState. UserName}</p>
<p>Age: {globalState. Age}</p>
<button onClick={()= >{setGlobalState({userName: 'lI'})}} > Set the name to Li</button>
</div>
<hr />
<HomePage />
</GlobalContext.Provider>)}export default App
Copy the code
The wrong way to use the current component
// bad
const AppV2: FC = () = > {
const [globalState, setGlobalState] = useReducer(GlobalReducer, globalDataInit)
const globalContext = useContext(GlobalContext)
return (
<GlobalContext.Provider value={{ globalState.setGlobalState}} >
<div>
<p>Incorrect usage of the current component</p>
<p>Name: {globalContext. GlobalState. UserName}</p>
<p>Age: {globalContext. GlobalState. Age}</p>
<button onClick={()= >{globalContext. SetGlobalState ({the userName: 'bill'})}} > set the name to li si</button>
</div>
</GlobalContext.Provider>)}export default AppV2
Copy the code
This also shows why we created createContext and setGlobalState with a default value that throws an error. The default value is used only when there is no matching Provider in the component tree. Normally, the default value is overridden. Throwing errors helps us troubleshoot bugs.
Ps: In reality, many programmers are very reluctant to throw exceptions. This is not a good habit. Error is also a part of the code.
Use of sub-components
// ./src/page/HomePage.tsx
const HomePage: FC = () = > {
const { globalState, setGlobalState } = useContext(GlobalContext)
return (
<div>
<p>Used in child components</p>
<p>{globalState.userName}</p>
<p>Age: {globalState. Age}</p>
<div>
<button onClick={()= >{setGlobalState({userName: 'wang5'})}} > Set name to wang5</button>
<button onClick={()= >{setGlobalState({age: 18})}} > Set age to 18</button>
</div>
</div>)}export default HomePage
Copy the code
Ts friendly useReducer method
export const GlobalReducer = (prevState: GlobalFace, updatedProperty: Partial<GlobalFace>): GlobalFace= >({... prevState, ... updatedProperty })const [globalState, setGlobalState] = useReducer(GlobalReducer, globalDataInit)
Copy the code
Partial
is a type provided by ts to make all incoming interface attributes optional
The method is easy to understand. Decompose the old and new states, then overwrite the old with the new state, and return
use
const { setGlobalState } = useContext(GlobalContext)
setGlobalState({
age:12
})
Copy the code
Compare with the official demo method
interface StateType {
count: number
}
interface actionType {
type: 'increment'|'decrement'|'customize'
data: any
}
function reducer (state: StateType, action: actionType) :StateType {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
case 'customize':
return { count: action.data }
default:
throw new Error()}}Copy the code
By contrast, to define a type field, even if we define the values allowed for type in advance, would be inelegant to abandon TS type checking when StateType contains attribute types and many data fields are set to type any.
What if there are too many stored states
The useReducer + Context scheme can also be cumbersome when there are a lot of states to store.
A: Most of the people who can ask this question are programmers who abuse Redux, as the saying goes: Redux is a basket. As you can see, most of the state we store in Redux is retrieved from the server side, and if we remove this state, we actually save very little state.
A useful library for server-side state management, React Query, provides out-of-the-box data fetching, caching, updating, and rolling replies.
React Query is used to manage interface status, and useReducer + Context is used to manage local status. This combination allows you to do robust state management with minimal code and simple logic.