useReducer 和 useContext Dispatch 在 onClick 函数中不起作用
useReducer and useContext Dispatch doesn't work in onClick function
我会为您提供更广泛的背景,因为它非常简单。使用 React hooks,这里的第一个调度会自动触发并且工作正常,但第二个不会。 Context
是从另一个文件导入的,所以我认为这是一个(小写)上下文问题,但我不知道如何解决它?
const Component = props => {
const [, dispatch] = useContext(Context);
useEffect(() => {
document.title = state.title;
// eslint-disable-next-line react-hooks/exhaustive-deps
});
// this works
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
function onAddClick() {
// this doesn't work
dispatch({ type: "UPDATE_TITLE", payload: "CLICKED!" });
}
return (
<div>
<AddButton onClick={onAddClick} />
</div>
);
};
这是父级。
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
state["title"] = action.payload;
return state;
default:
return state;
}
};
const initialState = {
title: "My Title"
};
export const Context = createContext(initialState);
const App = () => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
<Component />
</Context.Provider>
);
};
export default App;
控制台日志在两种情况下都在正确的 reducer 情况下触发,但只有标记为 'this works' 的一个会正确更新状态,另一个会静默失败。
固定:https://codesandbox.io/s/cranky-wescoff-9epf9?file=/src/App.js
您似乎正试图直接改变状态。相反,请尝试 return 一个新对象,该对象是应用于旧状态的操作更改的结果。
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
return {
...state,
page: {
...state.page,
title: action.payload
}
};
default:
return state;
}
};
或者,使用 produce from immerjs 让您能够以这种 mutable 风格编写 reducer。
import produce from "immer";
const Reducer = produce((state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
state["title"] = action.payload;
break;
default:
return;
}
});
我不知道你想通过将 dispatch
放在 controlled
环境之外来实现什么(比如 event
或 useEffect
):-
// this works
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
// but it will run in infinite loop tho (no changes can be made then)
所以 fixes 应该是:-
- 在
Reducer
中,确保不要完全 mutate
你的 state
:-
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
// Don't do this
// let newState = state;
// newState["page"]["title"] = action.payload;
// console.log("updating", newState, newState.page.title);
// return newState;
// Do this instead
return {
...state,
page: {
...state.page,
title: action.payload
}
};
default:
return state;
}
};
- 将
dispatch
for not Click!
放在 event
或 function
中,或者在这种情况下更好,useEffect
因为你想应用它 once
component
渲染。
const Demo = props => {
const [state, dispatch] = useContext(Context);
useEffect(() => {
document.title = state.title;
// eslint-disable-next-line react-hooks/exhaustive-deps
// this works (should be here)
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
}, []); // run it once when render
// this works but, (should not be here tho - cause it will run non-stop)
// dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
function onAddClick() {
// this will work now
dispatch({ type: "UPDATE_TITLE", payload: "CLICKED!" });
}
return (
<div>
<button onClick={onAddClick}>Click Me!</button>
<p>State now: {state.title}</p>
</div>
);
};
您可以尝试参考这个 sandbox 看看它是如何工作的。
已编辑和更新sandbox
我会为您提供更广泛的背景,因为它非常简单。使用 React hooks,这里的第一个调度会自动触发并且工作正常,但第二个不会。 Context
是从另一个文件导入的,所以我认为这是一个(小写)上下文问题,但我不知道如何解决它?
const Component = props => {
const [, dispatch] = useContext(Context);
useEffect(() => {
document.title = state.title;
// eslint-disable-next-line react-hooks/exhaustive-deps
});
// this works
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
function onAddClick() {
// this doesn't work
dispatch({ type: "UPDATE_TITLE", payload: "CLICKED!" });
}
return (
<div>
<AddButton onClick={onAddClick} />
</div>
);
};
这是父级。
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
state["title"] = action.payload;
return state;
default:
return state;
}
};
const initialState = {
title: "My Title"
};
export const Context = createContext(initialState);
const App = () => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
<Component />
</Context.Provider>
);
};
export default App;
控制台日志在两种情况下都在正确的 reducer 情况下触发,但只有标记为 'this works' 的一个会正确更新状态,另一个会静默失败。
固定:https://codesandbox.io/s/cranky-wescoff-9epf9?file=/src/App.js
您似乎正试图直接改变状态。相反,请尝试 return 一个新对象,该对象是应用于旧状态的操作更改的结果。
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
return {
...state,
page: {
...state.page,
title: action.payload
}
};
default:
return state;
}
};
或者,使用 produce from immerjs 让您能够以这种 mutable 风格编写 reducer。
import produce from "immer";
const Reducer = produce((state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
state["title"] = action.payload;
break;
default:
return;
}
});
我不知道你想通过将 dispatch
放在 controlled
环境之外来实现什么(比如 event
或 useEffect
):-
// this works
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
// but it will run in infinite loop tho (no changes can be made then)
所以 fixes 应该是:-
- 在
Reducer
中,确保不要完全mutate
你的state
:-
const Reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TITLE":
// Don't do this
// let newState = state;
// newState["page"]["title"] = action.payload;
// console.log("updating", newState, newState.page.title);
// return newState;
// Do this instead
return {
...state,
page: {
...state.page,
title: action.payload
}
};
default:
return state;
}
};
- 将
dispatch
fornot Click!
放在event
或function
中,或者在这种情况下更好,useEffect
因为你想应用它once
component
渲染。
const Demo = props => {
const [state, dispatch] = useContext(Context);
useEffect(() => {
document.title = state.title;
// eslint-disable-next-line react-hooks/exhaustive-deps
// this works (should be here)
dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
}, []); // run it once when render
// this works but, (should not be here tho - cause it will run non-stop)
// dispatch({ type: "UPDATE_TITLE", payload: "Not Click!" });
function onAddClick() {
// this will work now
dispatch({ type: "UPDATE_TITLE", payload: "CLICKED!" });
}
return (
<div>
<button onClick={onAddClick}>Click Me!</button>
<p>State now: {state.title}</p>
</div>
);
};
您可以尝试参考这个 sandbox 看看它是如何工作的。
已编辑和更新sandbox