Как обойти дорогие кастомные крючки? - PullRequest
0 голосов
/ 21 февраля 2019

Как мы знаем, правило таково:

Только вызовы на верхнем уровне.Не вызывайте Hooks внутри циклов, условий или вложенных функций.

Итак, мои вопросы: как использовать и спроектировать пользовательский крючок, который стоит дорого?

Учитывая этот хук:

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>;
}

Решение, которое я придумал, состоит в том, чтобы позволитьhook знает, что он должен выручить, вот так, используя флаг:

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>;
}

Передача флага дорогим хукам - правильный путь кобрабатывать условные крючки?Какие есть другие варианты?

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

Аргумент 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;
};
0 голосов
/ 21 февраля 2019

В исходном примере это дорогое начальное значение хука, а не сам хук, 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);
...