© Bohua Xu

Context Module Functions

Aug 26, 2022 · 8min

    Context Module Functions允许您将一组复杂的状态更改封装到一个实用函数中,该实用函数可以进行树形抖动和延迟加载。(tree-shaken and lazily loaded.)

    一个简单Context和 reducer 组合的示例:

    import React from "react";
    
    const CounterContext = React.createContext()
    
    const actionTypes = {                //防止写错,
        increment: 'increment',
        decrement: 'decrement',
      }
    function CounterProvider({step=1,initialCount=0,...props}){
        const [state,dispatch] = React.useReducer(
            (state,action) =>{
                const change = action.step??step
                switch (action.type) {
                    case actionTypes.increment:
                        return {...state,count:state.count+change}
                    case actionTypes.decrement:
                        return {...state,count:state.count-change}
                    default:
                        throw new Error(`没有这个${action.type}`)
                }
            },
            {count:initialCount}
        )
        const value =[state,dispatch]
        return <CounterContext.Provider value={value} {...props} />
    }
    
    function useCounter(){
        const context = React.useContext(CounterContext)
        if(context===undefined){
            throw new Error(`useCounter must be used within a CounterProvider`)
        }
        return context
    }
    
    export {CounterProvider,useCounter}
    // src/screens/counter.js
    import {useCounter} from 'context/counter'
    
    function Counter() {
      const [state, dispatch] = useCounter()
      const increment = () => dispatch({type: 'increment'})
      const decrement = () => dispatch({type: 'decrement'})
      return (
        <div>
          <div>Current Count: {state.count}</div>
          <button onClick={decrement}>-</button>
          <button onClick={increment}>+</button>
        </div>
      )
    }
    // src/index.js
    import {CounterProvider} from 'context/counter'
    
    function App() {
      return (
        <CounterProvider>
          <Counter />
        </CounterProvider>
      )
    }
    
    import React from "react";
    
    const CounterContext = React.createContext()
    
    const actionTypes = {                //防止写错,
        increment: 'increment',
        decrement: 'decrement',
      }
    function CounterProvider({step=1,initialCount=0,...props}){
        const [state,dispatch] = React.useReducer(
            (state,action) =>{
                const change = action.step??step
                switch (action.type) {
                    case actionTypes.increment:
                        return {...state,count:state.count+change}
                    case actionTypes.decrement:
                        return {...state,count:state.count-change}
                    default:
                        throw new Error(`没有这个${action.type}`)
                }
            },
            {count:initialCount}
        )
        const value =[state,dispatch]
        return <CounterContext.Provider value={value} {...props} />
    }
    
    function useCounter(){
        const context = React.useContext(CounterContext)
        if(context===undefined){
            throw new Error(`useCounter must be used within a CounterProvider`)
        }
        return context
    }
    
    export {CounterProvider,useCounter}
    // src/screens/counter.js
    import {useCounter} from 'context/counter'
    
    function Counter() {
      const [state, dispatch] = useCounter()
      const increment = () => dispatch({type: 'increment'})
      const decrement = () => dispatch({type: 'decrement'})
      return (
        <div>
          <div>Current Count: {state.count}</div>
          <button onClick={decrement}>-</button>
          <button onClick={increment}>+</button>
        </div>
      )
    }
    // src/index.js
    import {CounterProvider} from 'context/counter'
    
    function App() {
      return (
        <CounterProvider>
          <Counter />
        </CounterProvider>
      )
    }
    

    我想专注于我们的 reducer(Counter组件)的用户。他们必须创建自己的incrementdecrement调用dispatch. 这不是一个好的的 API。dispatch当您有一系列需要调用的函数时(就像您将在我们的练习中看到的那样),它变得更加烦人。

    第一个倾向是创建“辅助”函数并将它们包含在上下文中。让我们这样做。你会注意到我们必须把它放进去, React.useCallback这样我们才能在依赖列表中列出我们的“帮助”函数):

    const increment = React.useCallback(
    () => dispatch({type: 'increment'}),
    [dispatch],
    )
    const decrement = React.useCallback(
    () => dispatch({type: 'decrement'}),
    [dispatch],
    )
    const value = {state, increment, decrement}
    return <CounterContext.Provider value={value} {...props} />
    
    // now users can consume it like this:
    
    const {state, increment, decrement} = useCounter()
    
    const increment = React.useCallback(
    () => dispatch({type: 'increment'}),
    [dispatch],
    )
    const decrement = React.useCallback(
    () => dispatch({type: 'decrement'}),
    [dispatch],
    )
    const value = {state, increment, decrement}
    return <CounterContext.Provider value={value} {...props} />
    
    // now users can consume it like this:
    
    const {state, increment, decrement} = useCounter()
    

    这个方案不是不好的

    辅助方法是对象垃圾,我们需要重新创建和比较它们,除了表面上看起来更好看的语法之外没有其他目的。

    (Helper methods are object junk that we need to recreate and compare for no purpose other than superficially nicer looking syntax.)

    推荐的(以及 Facebook 所做的)是传递调度。为了解决我们最初试图解决的烦恼,他们使用了接受的可导入“助手” dispatch。让我们看一下它的外观:

    // src/context/counter.js
    const CounterContext = React.createContext()
    
    function CounterProvider({step = 1, initialCount = 0, ...props}) {
      const [state, dispatch] = React.useReducer(
        (state, action) => {
          const change = action.step ?? step
          switch (action.type) {
            case 'increment': {
              return {...state, count: state.count + change}
            }
            case 'decrement': {
              return {...state, count: state.count - change}
            }
            default: {
              throw new Error(`Unhandled action type: ${action.type}`)
            }
          }
        },
        {count: initialCount},
      )
    	//也可以直接放在这里
      const value = [state, dispatch]
    
      return <CounterContext.Provider value={value} {...props} />
    }
    
    function useCounter() {
      const context = React.useContext(CounterContext)
      if (context === undefined) {
        throw new Error(`useCounter must be used within a CounterProvider`)
      }
      return context
    }
    
    const increment = dispatch => dispatch({type: 'increment'})
    const decrement = dispatch => dispatch({type: 'decrement'})
    
    export {CounterProvider, useCounter, increment, decrement}
    // src/screens/counter.js
    import {useCounter, increment, decrement} from 'context/counter'
    
    function Counter() {
      const [state, dispatch] = useCounter()
      return (
        <div>
          <div>Current Count: {state.count}</div>
          <button onClick={() => decrement(dispatch)}>-</button>
          <button onClick={() => increment(dispatch)}>+</button>
        </div>
      )
    }
    
    // src/context/counter.js
    const CounterContext = React.createContext()
    
    function CounterProvider({step = 1, initialCount = 0, ...props}) {
      const [state, dispatch] = React.useReducer(
        (state, action) => {
          const change = action.step ?? step
          switch (action.type) {
            case 'increment': {
              return {...state, count: state.count + change}
            }
            case 'decrement': {
              return {...state, count: state.count - change}
            }
            default: {
              throw new Error(`Unhandled action type: ${action.type}`)
            }
          }
        },
        {count: initialCount},
      )
    	//也可以直接放在这里
      const value = [state, dispatch]
    
      return <CounterContext.Provider value={value} {...props} />
    }
    
    function useCounter() {
      const context = React.useContext(CounterContext)
      if (context === undefined) {
        throw new Error(`useCounter must be used within a CounterProvider`)
      }
      return context
    }
    
    const increment = dispatch => dispatch({type: 'increment'})
    const decrement = dispatch => dispatch({type: 'decrement'})
    
    export {CounterProvider, useCounter, increment, decrement}
    // src/screens/counter.js
    import {useCounter, increment, decrement} from 'context/counter'
    
    function Counter() {
      const [state, dispatch] = useCounter()
      return (
        <div>
          <div>Current Count: {state.count}</div>
          <button onClick={() => decrement(dispatch)}>-</button>
          <button onClick={() => increment(dispatch)}>+</button>
        </div>
      )
    }
    

    **可能看起来有点矫枉过正,而且确实如此。**但是,在某些情况下,这种模式不仅可以帮助您减少重复,还 可以帮助提高性能 并帮助您避免依赖项列表中的错误。

    不会一直推荐这个,但有时它会有所帮助!

    📜 如果需要查看上下文 API,以下是文档:

    🦉 React DevTools–区分显示

    const MyContext = React.createContext()
    MyContext.displayName = 'MyContext'
    
    const MyContext = React.createContext()
    MyContext.displayName = 'MyContext'
    
    CC BY-NC-SA 4.0 2021-PRESENT © Bohua Xu