В исходном примере это дорогое начальное значение хука, а не сам хук, 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
не использует встроенные хуки внутри, его можно вызывать условно, как показывает «правильный» пример.
В случае, если компоненту необходимо условно применить сторонний хук в зависимости от флага проп, необходимо гарантировать, что условие не изменится со временем, ограничив его начальным значением проп:
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);