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.