Как избежать проблем с негарантированными deps useCallback в deps useEffect? - PullRequest
4 голосов
/ 29 марта 2019

Я недавно начал использовать новый API React Hooks, и я нахожу его потрясающим!

Однако я столкнулся с небольшой путаницей в области зависимостей.

О чем это?

По сути, мой вариант использования довольно прост и может быть проиллюстрирован следующим псевдокодом:

import React, { useState, useCallback, useEffect } from 'react'

function Component() {
  const [state, setState] = useState()

  const doStuff = useCallback(() => {
    // Do something 
    setState(result)
  }, [setState])

  useEffect(() => {
    // Do stuff ONLY at mount time
    doStuff()
  }, [])

  return <ExpensivePureComponent doStuff={doStuff} />
}

Теперь приведенный выше код работает нормально.

Но после того, как я установил eslint-plugin-react-hooks, появляется предупреждение. Я должен объявить все зависимости, которые используются в моих эффектах, то есть здесь doStuff.

Хорошо, давайте исправим этот код:

  useEffect(() => {
    // Do stuff ONLY at mount time
    doStuff()
  }, [doStuff])

Круто, больше предупреждений нет!

Подождите, без предупреждения, но ... тоже нет ошибки?

Посмотрим, о чем говорят документы useCallback:

useCallback (fn, deps) эквивалентно useMemo (() => fn, deps)

А потом примерно useMemo:

Вы можете положиться на использование Memo в качестве оптимизации производительности, а не в качестве семантической гарантии . В будущем React может выбрать «забыть» некоторые ранее запомненные значения и пересчитать их при следующем рендере

Так, в принципе, мой обратный вызов doStuff, следовательно, мой useEffect, больше не гарантированно будет работать только во время монтирования? Разве это не проблема?

Я понимаю принципы, лежащие в основе плагина eslint, но мне кажется, что существует опасная путаница между useCalback / useMemo массивами зависимостей и useEffect единицей, или я что-то упустил?

Возможно, потому что даже доктора говорят, что мой последний код в порядке:

Если по какой-то причине вы не можете переместить функцию внутри эффекта, есть еще несколько вариантов:

● ...

● В качестве последнего средства вы можете добавить функцию для воздействия на зависимости, но включить ее определение в ловушку useCallback. Это гарантирует, что он не изменится при каждом рендере, если только его собственные зависимости также не изменятся


я: бла-бла-хукс бла

SO: Какой у вас вопрос? :)

Ну, что ты думаешь? Код безопасен? Документы говорят, что это так, но также говорят, что это не так, потому что обратный вызов не гарантированно не изменится ... это немного сбивает с толку.

Есть ли плохая практика в приведенном выше псевдокоде? Когда этого не избежать, что делать? // eslint-disable-next-line

1 Ответ

2 голосов
/ 29 марта 2019

В то время как документы говорят, что

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)

это не значит, что useCallback реализован с использованием useMemo, и, конечно, это не так. Поэтому, хотя useMemo может выбрать пересчет снова, useCallback не будет обновлять функцию, если что-то в массиве зависимостей не изменится.

Также, поскольку установщик, возвращаемый useState, не изменяется, вам не нужно передавать его в useCallback

  const doStuff = useCallback(() => {
    // Do something 
    setState(result)
  }, [])

Поскольку doStuff не изменится, useEffect больше не будет вызываться, кроме первоначального монтирования.

Однако при использовании useEffect и useCallback следует иметь в виду, что если вы измените массив зависимостей в useCallback, обратный вызов будет воссоздан и, следовательно, useEffect будет перезапущено повторно. Один из способов предотвратить такие сценарии - использовать useReducer hook вместо useState и полагаться на dispatch для обновления состояния, поскольку оно никогда не изменится в ходе взаимодействия вашего приложения в сеансе.

import React, { useReducer, useEffect } from 'react'

const initialState = [];
const reducer = (state, action) => {
    switch(action.type) {
        case 'UPDATE_STATE' : {
            return action.payload
        }
        default: return state;
    }
}
function Component() {
  const [state, dispatch] = useReducer(reducer, initialState);


  useEffect(() => {
    // Do stuff ONLY at mount time
    dispatch({type: 'UPDATE_RESULT', payload: ['xyz']})
  }, [])

  return <ExpensivePureComponent dispatch={dispatch} />
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...