如何解决昂贵的自定义挂钩?
How to work around expensive custom hooks?
据我们所知,规则是:
Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions.
所以我的问题是如何使用和设计昂贵的自定义挂钩?
给出这个钩子:
const useExpensiveHook = () => {
// some code that uses other built-in hooks...
const value = computeExpensiveValue();
// some more code....
return value;
}
如果该规则不存在,我的客户端代码将是:
const myComponent = ({isSuperFeatureEnabled}) => {
let value;
if (isSuperFeatureEnabled){
value = useExpensiveHook();
}
return <div>{value}</div>;
}
我想到的解决方案是让钩子知道它应该退出,就像这样,使用一个标志:
const useExpensiveHook = ({enabled}) => {
// some code that uses other built-in hooks...
let value;
if(enabled) {
value = computeExpensiveValue();
}
else {
value = undefined;
}
// some more code....
return value;
};
和客户端代码:
const myComponent = ({isSuperFeatureEnabled}) => {
const value = useExpensiveHook({enabled : isSuperFeatureEnabled});
return <div>{value}</div>;
}
将标志传递给昂贵的挂钩是处理条件挂钩的正确方法吗?还有哪些选择?
在原来的例子中是钩子初始值是昂贵的,而不是钩子本身,computeExpensiveValue
可以有条件地调用:
const [value, setValue] = useState(enabled ? computeExpensiveValue() : undefined);
在当前列出的示例中,useExpensiveHook
不是一个钩子,而是一些函数;它不使用 React 钩子。
引用规则的目的是让内置的钩子无条件调用,因为钩子的状态是由它们被调用的顺序决定的:
if (flipCoin())
var [foo] = useState('foo');
var [bar] = useState('bar');
如果 useState('foo')
在下一个组件渲染时没有被调用,useState('bar')
成为第一个 useState
挂钩被调用并被视为 foo
状态,而第二个 useState
丢失,这种不一致会在渲染器中触发错误。
如果保证钩子调用的顺序不变,使用条件是可以接受的,但这在实践中很少可行。即使存在像 if (process.env.NODE_ENV === 'development')
这样看似恒定的条件,它也可能在运行时的某些情况下发生变化,并导致难以调试的上述问题。
正确:
useEffect(() => {
if (varyingCondition)
computeExpensiveValue();
});
不正确:
if (varyingCondition)
useEffect(() => {
computeExpensiveValue();
});
此规则仅适用于内置挂钩和直接或间接调用它们的函数(所谓的自定义挂钩)。只要 computeExpensiveValue
内部不使用内置钩子,就可以有条件地调用它,如 'correct' 示例所示。
如果组件需要根据 prop 标志有条件地应用第三方 hook,应通过将其限制为初始 prop 值来保证条件不会随时间变化:
const Component = ({ expensive, optionalValue }) => {
const isExpensive = useMemo(() => expensive, []);
if (isExpensive)
optionalValue = useExpensiveHook();
return ...
}
这样 <Component expensive={flipCoin()} />
不会违反规则,只会滥用组件。
由于在使用<Component expensive/>
时应该知道是否需要昂贵的钩子,更简洁的方法是将此功能组合在高阶组件中,并根据需要使用不同的组件:
const withExpensive = Comp => props => {
const optionalValue = useExpensiveHook();
return <Comp optionalValue={optionalValue} ...props />;
}
const Component = ({ optionalValue }) => {
return ...
}
const ExpensiveComponent = withExpensive(Component);
useState
的参数仅使用一次,因此如果您最初将 enabled
作为 false 传递给它,它永远不会执行 computeExpensiveValue
。因此,您还需要添加一个 useEffect
调用。你可以改为设计你的钩子
const useExpensiveHook = ({enabled}) => {
const [value, setValue] = useState(enabled ? computeExpensiveValue : undefined);
useEffect(()=> {
if(enabled) {
const value = computeExpensiveValue();
setValue(value);
}
}, [enabled]);
// some more code....
return value;
};
据我们所知,规则是:
Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions.
所以我的问题是如何使用和设计昂贵的自定义挂钩?
给出这个钩子:
const useExpensiveHook = () => {
// some code that uses other built-in hooks...
const value = computeExpensiveValue();
// some more code....
return value;
}
如果该规则不存在,我的客户端代码将是:
const myComponent = ({isSuperFeatureEnabled}) => {
let value;
if (isSuperFeatureEnabled){
value = useExpensiveHook();
}
return <div>{value}</div>;
}
我想到的解决方案是让钩子知道它应该退出,就像这样,使用一个标志:
const useExpensiveHook = ({enabled}) => {
// some code that uses other built-in hooks...
let value;
if(enabled) {
value = computeExpensiveValue();
}
else {
value = undefined;
}
// some more code....
return value;
};
和客户端代码:
const myComponent = ({isSuperFeatureEnabled}) => {
const value = useExpensiveHook({enabled : isSuperFeatureEnabled});
return <div>{value}</div>;
}
将标志传递给昂贵的挂钩是处理条件挂钩的正确方法吗?还有哪些选择?
在原来的例子中是钩子初始值是昂贵的,而不是钩子本身,computeExpensiveValue
可以有条件地调用:
const [value, setValue] = useState(enabled ? computeExpensiveValue() : undefined);
在当前列出的示例中,useExpensiveHook
不是一个钩子,而是一些函数;它不使用 React 钩子。
引用规则的目的是让内置的钩子无条件调用,因为钩子的状态是由它们被调用的顺序决定的:
if (flipCoin())
var [foo] = useState('foo');
var [bar] = useState('bar');
如果 useState('foo')
在下一个组件渲染时没有被调用,useState('bar')
成为第一个 useState
挂钩被调用并被视为 foo
状态,而第二个 useState
丢失,这种不一致会在渲染器中触发错误。
如果保证钩子调用的顺序不变,使用条件是可以接受的,但这在实践中很少可行。即使存在像 if (process.env.NODE_ENV === 'development')
这样看似恒定的条件,它也可能在运行时的某些情况下发生变化,并导致难以调试的上述问题。
正确:
useEffect(() => {
if (varyingCondition)
computeExpensiveValue();
});
不正确:
if (varyingCondition)
useEffect(() => {
computeExpensiveValue();
});
此规则仅适用于内置挂钩和直接或间接调用它们的函数(所谓的自定义挂钩)。只要 computeExpensiveValue
内部不使用内置钩子,就可以有条件地调用它,如 'correct' 示例所示。
如果组件需要根据 prop 标志有条件地应用第三方 hook,应通过将其限制为初始 prop 值来保证条件不会随时间变化:
const Component = ({ expensive, optionalValue }) => {
const isExpensive = useMemo(() => expensive, []);
if (isExpensive)
optionalValue = useExpensiveHook();
return ...
}
这样 <Component expensive={flipCoin()} />
不会违反规则,只会滥用组件。
由于在使用<Component expensive/>
时应该知道是否需要昂贵的钩子,更简洁的方法是将此功能组合在高阶组件中,并根据需要使用不同的组件:
const withExpensive = Comp => props => {
const optionalValue = useExpensiveHook();
return <Comp optionalValue={optionalValue} ...props />;
}
const Component = ({ optionalValue }) => {
return ...
}
const ExpensiveComponent = withExpensive(Component);
useState
的参数仅使用一次,因此如果您最初将 enabled
作为 false 传递给它,它永远不会执行 computeExpensiveValue
。因此,您还需要添加一个 useEffect
调用。你可以改为设计你的钩子
const useExpensiveHook = ({enabled}) => {
const [value, setValue] = useState(enabled ? computeExpensiveValue : undefined);
useEffect(()=> {
if(enabled) {
const value = computeExpensiveValue();
setValue(value);
}
}, [enabled]);
// some more code....
return value;
};