我正在尝试创建一个钩子,该钩子允许组件订阅全局状态更改的一部分。例如,假设我的状态是这样的
{
products: []
userForm: {
name: 'John Smith',
dateOfBirth: '07/10/1991'
}
}
仅当dateOfBirth字段更改时,才应重新呈现控制userForm中dateOfBirth
字段的组件。
说我使用React上下文创建了一些全局状态。这是我尝试订阅该组件所关心的全局状态的领域
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];
}
演示https://codesandbox.io/s/dawn-fog-ieqxs?file=/src/App.js:326-612
以上代码使使用useField
钩子的所有组件重新呈现。
所需的行为是,仅当该字段更改时才应重新渲染组件。
答案 0 :(得分:1)
它可以工作,但不能与Context API一起使用,因为目前Context API无法救助无用的渲染器。
换句话说:订阅上下文提供程序的组件将始终在提供程序值更改时呈现。
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>
);
};
也就是说,使用每个现代状态管理解决方案都具有救助功能,可以解决此问题:
// 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 */);