Как отправить действие в пользовательских хуках с помощью useReducer и useContext?

Я создал образец для кнопки-переключателя.

Это делается useContext (сохранение данных) и useReducer (обработка данных). и он работает нормально.

Ниже приведена ссылка на CodeSandBox о том, как это работает.

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>
  );
}

Не уверен, что там происходит. но я думаю, что что-то не так с отправленным состоянием.

(Я пробовал, это работает, если полезная нагрузка не обрабатывает состояние, например, какой-то жесткий код, поэтому отправка должна быть запущена в этот момент)

Может ли кто-нибудь дать мне руку? Ценить!!!


person SPG    schedule 28.06.2020    source источник


Ответы (2)


Вы правы, что переключатель должен быть функцией, но вы отправляете тип действия UPDATE, а редуктор ничего не делает с этим действием.

Деннис прав в том, что нет смысла в начальном значении, которое вы даете контексту, и вы можете оставить его пустым, поскольку поставщик предоставит значение.

Предложение Денниса по useMemo не оптимизирует ваш пример, так как приложение выполняет повторную визуализацию при изменении состояния, поэтому мемоизированное значение никогда не будет использоваться.

Вот рабочий пример вашего кода с комментариями, что я изменил:

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>

person HMR    schedule 28.06.2020

Ну нет такого 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 может быть таким же.

См. пример такого варианта использования мемоизации.

Редактировать черный дым-6lz6k

Если моя точка зрения непонятна, см. комментарий HMR:

Стратегическое использование useMemo следует использовать, если многие компоненты обращаются к контексту, тогда запоминание является хорошей идеей, когда компонент с провайдером выполняет повторную визуализацию по причинам, отличным от изменения контекста.

person Dennis Vash    schedule 28.06.2020
comment
useMemo бессмысленно, если приложение является корневым компонентом, то оно повторно отображается только при изменении значения контекста, поэтому мемоизированное значение никогда не будет использоваться. Если у приложения есть другие причины для повторного рендеринга, тогда оно оптимизирует вещи, но, поскольку приложение обычно является корневым компонентом, оно, вероятно, не будет повторно отображаться по причинам, отличным от изменения значения контекста. - person HMR; 28.06.2020
comment
Это не бессмысленно, и, как я уже упоминал, в зависимости от варианта использования, вот пример для моей точки зрения: codesandbox.io/s/black-smoke-6lz6k?file=/index.js - person Dennis Vash; 28.06.2020
comment
В вашем примере контекст никогда не может измениться, и вы также можете использовать <AppContext.Provider value={setState}> вместо useMemo. Я согласен, что бывают ситуации, когда useMemo полезен, но, как я уже говорил ранее; компонент OP перерисовывается только при изменении контекста, из-за чего useMemo никогда ничего не запоминает. - person HMR; 28.06.2020
comment
Это был не комментарий для решения вопроса OP, а рекомендация, хотя обычно, в зависимости от варианта использования - person Dennis Vash; 28.06.2020
comment
Да, вы правы в том, что стратегическое useMemo следует использовать, если многие компоненты обращаются к контексту, а компонент с провайдером повторно отображается по причинам, отличным от изменения контекста. - person HMR; 28.06.2020