如何通过useReducer和useContext调度自定义挂钩中的动作?

时间:2020-06-28 08:51:06

标签: javascript reactjs react-hooks use-reducer use-context

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

这是通过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>
  );
}

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

(我尝试过,如果有效负载未处于处理状态(例如某些硬代码的东西),那么它会起作用,因此此时应该触发分派)

有人可以帮我吗?感激!!!

2 个答案:

答案 0 :(得分:1)

好吧,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 实际上是应该使用上下文的方式,尽管通常情况下,您将需要添加一个额外的备注步骤(取决于用例),因为在每个渲染上都分配了一个新对象{ {1}}即使{state,dispatch}可能相同也总是会导致渲染。

请参阅此类备忘录用例示例。

Edit black-smoke-6lz6k

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

如果许多组件访问 上下文,然后记忆是一个好主意,当具有 提供者出于改变上下文以外的其他原因而重新渲染。

答案 1 :(得分:1)

您是正确的,切换必须是一个函数,但是您正在分派动作类型UPDATE,而reducer对该动作没有任何作用。

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

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

这是您的代码的有效示例,其中包含我所做的更改:

@Controller
public class indexController {

// Tomcat Jasper to return jsp files
@RequestMapping("index")
public String getIndex() {
    System.out.println("test1");
    return "index.jsp";
}
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'));