如何通过 useReducer 和 useContext 在 Custom Hooks 中调度 action?

How to dispatch action in Custom Hooks by useReducer and useContext?

我创建了一个按钮切换示例。

这是由 useContext(存储数据)和 useReducer(处理数据)完成的。它工作正常。

这里是CodeSandBox Link它是如何工作的。

version 1 只是在点击按钮时调度。

然后我创建了一个 version 2 的切换。基本上只是将调度放在自定义挂钩中。但不知何故,它不起作用。

// context
export const initialState = { status: false }

export const AppContext = createContext({
  state: initialState,
  dispatch: React.dispatch
})

// reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'TOGGLE':
      return {
        ...state,
        status: action.payload
      }
    default:
      return state
  }
}

//custom hook
const useDispatch = () => {
  const {state, dispatch} = useContext(AppContext)
  return {
    toggle: dispatch({type: 'UPDATE', payload: !state.status})
    // I tried to do toggle: () => dispatch(...) as well
  }
}

// component to display and interact
const Panel = () => {
  const {state, dispatch} = useContext(AppContext) 
  // use custom hook
  const { toggle } = useDispatch()
  const handleChange1 = () => dispatch({type: 'TOGGLE', payload: !state.status})
  const handleChange2 = toggle // ERROR!!!
  // and I tried handleChange2 = () => toggle, or, handleChange2 = () => toggle(), or handleChange2 = toggle()
  return (
    <div>
      <p>{ state.status ? 'On' : 'Off' }</p>
      <button onClick={handleChange1}>change version 1</button>
      <button onClick={handleChange2}>change version 2</button>
    </div>
  )
}

// root 
export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <AppContext.Provider value={{state, dispatch}}>
      <div className="App">
        <Panel />
      </div>
    </AppContext.Provider>
  );
}

不确定那里发生了什么。但我认为调度状态有问题。

(我试过如果有效载荷没有处理状态,就像一些硬代码一样,它可以工作,所以此时应该触发调度)

有人可以帮帮我吗?欣赏!!!

好吧,没有这样的东西React.dispatch。它的值为undefined

export const AppContext = createContext({
  state: initialState,
  // useless
  // dispatch: undefined
  dispatch: React.dispatch
});

// dispatch function won't trigger anything.
const {state, dispatch} = useContext(AppContext);

版本 1 实际上是应该如何使用上下文,尽管通常情况下,您会想要添加一个额外的记忆步骤(取决于用例),因为在每次渲染时您分配一个新对象 {state,dispatch},它总是会导致渲染,即使 state 可能相同。

查看此类记忆用例示例。

如果我的观点不清楚,请参阅HMR评论:

Strategic useMemo should be used, if many components access the context then memoizing is a good idea when the component with the provider re-renders for reasons other than changing the context.

你是正确的,toggle 需要是一个函数,但你正在调度操作类型 UPDATE 并且 reducer 不对该操作执行任何操作。

Dennis 是正确的,您提供上下文的初始值毫无意义,最好将其留空,因为提供者将提供该值。

Dennis 的 useMemo 建议不会优化您的示例,因为 App 在状态更改时重新呈现,因此永远不会使用记忆值。

这是您的代码的工作示例,其中包含我更改的注释:

const { createContext, useReducer, useContext } = React;

const initialState = { status: false };
//no point in setting initial context value
const AppContext = createContext();

const reducer = (state, action) => {
  switch (action.type) {
    case 'TOGGLE':
      return {
        ...state,
        status: action.payload,
      };
    default:
      return state;
  }
};

const useDispatch = () => {
  const { state, dispatch } = useContext(AppContext);
  return {
    //you were correct here, toggle
    //  has to be a function
    toggle: () =>
      dispatch({
        //you dispatch UPDATE but reducer
        //  is not doing anything with that
        type: 'TOGGLE',
        payload: !state.status,
      }),
  };
};

const Panel = () => {
  const { state, dispatch } = useContext(AppContext);
  const { toggle } = useDispatch();
  const handleChange1 = () =>
    dispatch({ type: 'TOGGLE', payload: !state.status });
  const handleChange2 = toggle; // ERROR!!!
  return (
    <div>
      <p>{state.status ? 'On' : 'Off'}</p>
      <button onClick={handleChange1}>
        change version 1
      </button>
      <button onClick={handleChange2}>
        change version 2
      </button>
    </div>
  );
};

function App() {
  const [state, dispatch] = useReducer(
    reducer,
    initialState
  );
  return (
    <AppContext.Provider value={{ state, dispatch }}>
      <div className="App">
        <Panel />
      </div>
    </AppContext.Provider>
  );
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>