[英]How to render component conditionally when leaf value of global state changes using react hooks
I am attempting to create a hook which allows a component to subscribe to a part of the global state changing.我正在尝试创建一个钩子,它允许组件订阅全局状态更改的一部分。 For example, imagine my state looks like this
例如,想象我的状态看起来像这样
{
products: []
userForm: {
name: 'John Smith',
dateOfBirth: '07/10/1991'
}
}
The component which controls the dateOfBirth
field in the userForm should only re-render if the dateOfBirth field changes.控制 userForm 中
dateOfBirth
字段的组件应该只在 dateOfBirth 字段更改时重新呈现。
Say I have some global state created using React context.假设我使用 React 上下文创建了一些全局状态。 Here is my attempt at subscribing to the field of the global state that that component cares about
这是我订阅该组件关心的全局状态字段的尝试
function useField(field) {
const [globalState, setGlobalState] = useContext(GlobalState);
const value = globalState[field] || "initial";
const setValue = useCallback(
(value) => {
setGlobalState((state) => ({
...state,
[field]: value
}));
},
[setGlobalState, field]
);
return [value, setValue];
}
Demo https://codesandbox.io/s/dawn-fog-ieqxs?file=/src/App.js:326-612演示https://codesandbox.io/s/dawn-fog-ieqxs?file=/src/App.js:326-612
The above code causes any component which uses the useField
hook to rerender.上面的代码会导致任何使用
useField
钩子的组件重新渲染。
The desired behaviour is that the component should only rerender when that field changes.所需的行为是组件应该只在该字段更改时重新渲染。
It can work, but not with Context API, as for now Context API can't bailout of useless renders.它可以工作,但不能与 Context API 一起使用,因为现在 Context API 无法摆脱无用的渲染。
In other words: components subscribed to context provider will always render on provider value change.换句话说:订阅上下文提供程序的组件将始终在提供程序值更改时呈现。
An example of Context API known problem: Context API 已知问题示例:
const GlobalContext = React.createContext(null);
const InnerComponent = () => {
/* eslint-disable no-unused-vars */
const { uselessState } = useContext(GlobalContext);
console.log(`Inner rendered`);
return <></>;
};
const InnerMemo = React.memo(InnerComponent);
const InnerComponentUsingContext = () => {
const { counter, dispatch } = useContext(GlobalContext);
console.log(`Inner Using Context rendered`);
return (
<>
<div>{counter}</div>
<button onClick={() => dispatch()}>Dispatch</button>
</>
);
};
const InnerComponentUsingContextMemo = React.memo(InnerComponentUsingContext);
const App = () => {
const [counter, dispatch] = useReducer((p) => p + 1, 0);
const [uselessState] = useState(null);
return (
<GlobalContext.Provider value={{ counter, uselessState, dispatch }}>
<InnerMemo />
<InnerComponentUsingContextMemo />
</GlobalContext.Provider>
);
};
That said, using every modern state management solution has a bailout function which will resolve this issue:也就是说,使用每个现代状态管理解决方案都有一个救助功能可以解决这个问题:
// Always renders
const [globalState, setGlobalState] = useContext(GlobalState);
const value = globalState[field] || "initial";
// Bailout, for example with redux
const value = useReducer(globalState => globalState[field], /* Can add bailout function here if necessary */);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.