React: [ESLint] Feedback para regra de lint 'exaustiva-deps'

Criado em 21 fev. 2019  ·  111Comentários  ·  Fonte: facebook/react

Respostas Comuns

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡

Analisamos os comentários neste post para fornecer algumas orientações: https://github.com/facebook/react/issues/14920#issuecomment -471070149.

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡


O que é isso

Esta é uma nova regra ESLint que verifica a lista de dependências para Ganchos como useEffect e semelhantes, protegendo contra as armadilhas de fechamento obsoleto. Na maioria dos casos, ele possui um autofix. Adicionaremos mais documentação nas próximas semanas.

autofix demo

Instalação

yarn add eslint-plugin-react-hooks<strong i="18">@next</strong>
# or
npm install eslint-plugin-react-hooks<strong i="19">@next</strong>

Configuração ESLint:

{
  "plugins": ["react-hooks"],
  // ...
  "rules": {
    "react-hooks/rules-of-hooks": 'error',
    "react-hooks/exhaustive-deps": 'warn' // <--- THIS IS THE NEW RULE
  }
}

Caso de teste simples para verificar se a regra funciona:

function Foo(props) {
  useEffect(() => {
    console.log(props.name);
  }, []); // <-- should error and offer autofix to [props.name]
}

A regra do lint reclama, mas meu código está bom!

Se esta nova regra react-hooks/exhaustive-deps lint disparar para você, mas você achar que seu código está correto , poste nesta edição.


ANTES DE PUBLICAR UM COMENTÁRIO

Por favor, inclua estas três coisas:

  1. Um CodeSandbox demonstrando um exemplo de código mínimo que ainda expressa sua intenção (não "foo bar", mas o padrão de UI real que você está implementando).
  2. Uma explicação das etapas que um usuário executa e o que você espera ver na tela.
  3. Uma explicação da API pretendida de seu Gancho / componente.

please

Mas meu caso é simples, não quero incluir essas coisas!

Pode ser simples para você - mas não é nada simples para nós. Se o seu comentário não incluir nenhum deles (por exemplo, nenhum link CodeSandbox), iremos ocultar seu comentário porque é muito difícil rastrear a discussão de outra forma. Obrigado por respeitar o tempo de todos, incluindo-os.

O objetivo final deste tópico é encontrar cenários comuns e transformá-los em documentos e avisos melhores. Isso só pode acontecer quando detalhes suficientes estão disponíveis. Comentários diretos com fragmentos de código incompletos diminuem significativamente a qualidade da discussão - a ponto de não valer a pena.

ESLint Rules Discussion

Comentários muito úteis

Passamos por cima deles com @threepointone hoje. Aqui está um resumo:

Corrigido na regra Lint

Dependências externas de useEffect

A regra não impede mais você de adicionar dependências "estranhas" a useEffect uma vez que existem cenários legítimos.

Funções no mesmo componente, mas definidas fora do efeito

O Linter não avisa para casos em que é seguro agora, mas em todos os outros casos, ele oferece sugestões melhores (como mover a função dentro do efeito ou envolvê-la com useCallback ).

Vale a pena corrigir no código do usuário

Reiniciando o estado na mudança de adereços

Isso não produz mais violações de lint, mas a maneira idiomática de redefinir o estado em resposta aos adereços é diferente . Esta solução terá um render inconsistente extra, então não tenho certeza se é desejável.

"Meu valor não funcional é constante"

Os ganchos o empurram para a correção sempre que possível. Se você especificar as dependências (que em alguns casos você pode omitir), é altamente recomendável para incluir mesmo os que você acha que não vai mudar. Sim, neste useDebounce exemplo, é improvável que o atraso mude. Mas ainda é um bug se isso acontecer, mas o Gancho não consegue lidar com isso. Isso também aparece em outros cenários. (Por exemplo, os ganchos são muito mais compatíveis com recarregamento a quente porque cada valor é tratado como dinâmico.)

Se você absolutamente insiste que um determinado valor é estático, você pode aplicá-lo.
A maneira mais segura é fazer isso explicitamente em sua API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Então, claramente não pode mudar a menos que você coloque dentro do render. (O que não seria o uso idiomático do seu Gancho.) Mas dizer que <Slider min={50} /> nunca pode mudar não é realmente válido - alguém poderia facilmente mudá-lo para <Slider min={state ? 50 : 100} /> . Na verdade, alguém poderia fazer isso:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Se alguém mudar de estado isCelsius , um componente assumindo que min nunca muda não será atualizado. Não é óbvio neste caso que Slider será o mesmo (mas será porque tem a mesma posição na árvore). Portanto, este é um tiro certeiro em termos de fazer alterações no código. Um ponto principal do React é que as atualizações são renderizadas exatamente como os estados iniciais (normalmente você não pode dizer qual é qual). Quer você renderize o valor de proposta B ou vá do valor de proposta A para B - ele deve ter a mesma aparência e comportamento.

Embora isso seja desaconselhável, em alguns casos, o mecanismo de aplicação pode ser um Gancho que avisa quando o valor muda (mas fornece o primeiro). Pelo menos então é mais provável que seja notado.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

Também pode haver um caso legítimo em que você simplesmente não consegue lidar com uma atualização. Por exemplo, se a API de nível inferior não o suportasse, como um plugin jQuery ou uma API DOM. Nesse caso, o aviso ainda é apropriado para que o consumidor do seu componente o entenda. Alternativamente, você pode fazer um componente de invólucro que redefina key em atualizações incompatíveis - forçando uma remontagem limpa com novos acessórios. Isso provavelmente é preferível para componentes folha, como controles deslizantes ou caixas de seleção.

"O valor da minha função é constante"

Em primeiro lugar, se for constante e elevado ao escopo de nível superior, o linter não reclamará. Mas isso não ajuda com coisas que vêm de adereços ou contexto.

Se ele realmente é constante, em seguida, especificando-a no deps não dói. Como o caso em que uma função setState dentro de um Gancho personalizado é retornada ao seu componente e, em seguida, você a chama a partir de um efeito. A regra do lint não é inteligente o suficiente para entender indireções como essa. Mas, por outro lado, qualquer um pode quebrar esse retorno de chamada mais tarde, antes de retornar, e possivelmente fazer referência a outro prop ou estado dentro dele. Então não será constante! E se você falhar em lidar com essas mudanças, você terá bugs de propriedade / estado obsoletos. Portanto, especificá-lo é um padrão melhor.

No entanto, é um equívoco pensar que os valores das funções são necessariamente constantes. Eles são mais frequentemente constantes nas classes devido à associação de métodos, embora isso crie sua própria gama de bugs . Mas, em geral, qualquer função que fecha sobre um valor em um componente de função não pode ser considerada constante. A regra do lint agora é mais inteligente sobre o que fazer. (Como movê-lo dentro do efeito - a correção mais simples - ou envolvê-lo com useCallback .)

Há um problema no espectro oposto disso, que é onde você obtém loops infinitos (um valor de função sempre muda). Percebemos isso na regra do lint agora, quando possível (no mesmo componente) e sugerimos uma correção. Mas é complicado se você passar algo vários níveis abaixo.

Você ainda pode envolvê-lo em useCallback para corrigir o problema. Lembre-se de que tecnicamente é válido que uma função mude , e você não pode ignorar este caso sem correr o risco de bugs. Como onChange={shouldHandle ? handleChange : null} ou renderizando foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> no mesmo local. Ou mesmo fetchComments que fecha sobre o estado do componente pai. Isso pode mudar. Com as classes, seu comportamento mudará silenciosamente, mas a referência de função permanecerá a mesma. Portanto, seu filho sentirá falta dessa atualização - você realmente não tem a opção de passar mais dados para ele. Com os componentes da função e useCallback , a própria identidade da função muda - mas apenas quando necessário. Portanto, essa é uma propriedade útil e não apenas um obstáculo a ser evitado.

Devemos adicionar uma solução melhor para detectar loops assíncronos infinitos. Isso deve atenuar o aspecto mais confuso. Podemos adicionar alguma detecção para isso no futuro. Você também pode escrever algo assim:

useWarnAboutTooFrequentChanges([deps]);

Isso não é o ideal e precisaremos pensar mais em como lidar com isso de maneira elegante. Eu concordo que casos como esse são bem desagradáveis. A correção sem quebrar a regra seria tornar rules estático, por exemplo, alterando a API para createTextInput(rules) e envolvendo register e unregister em useCallback . Melhor ainda, remova register e unregister e substitua-os por um contexto separado onde você coloca dispatch sozinho. Então você pode garantir que nunca terá uma identidade de função diferente da leitura.

Eu acrescentaria que você provavelmente deseja colocar useMemo no valor de contexto de qualquer maneira, porque o provedor faz muitos cálculos que seria triste repetir se nenhum novo componente fosse registrado, mas seu próprio pai atualizado. Portanto, este tipo de problema coloca o problema que você talvez não tenha notado de outra forma mais visível. Embora eu concorde que precisamos tornar isso ainda mais proeminente quando isso acontecer.

Ignorar as dependências de função completamente leva a erros piores com componentes de função e ganchos porque eles continuariam vendo adereços e estados obsoletos se você fizesse isso. Portanto, tente não fazê-lo, quando puder.

Reagindo às Mudanças de Valor Composto

É estranho para mim por que este exemplo usa um efeito para algo que é essencialmente um manipulador de eventos. Fazer o mesmo "log" (suponho que poderia ser um envio de formulário) em um manipulador de eventos parece mais adequado. Isso é especialmente verdadeiro se pensarmos sobre o que acontece quando o componente é desmontado. E se ele desmontar logo após o efeito ser programado? Coisas como o envio de formulários não deveriam simplesmente "não acontecer" nesse caso. Portanto, parece que o efeito pode ser uma escolha errada.

Dito isso, você ainda pode fazer o que tentou - tornando fullName setSubmittedData({firstName, lastName}) , e então [submittedData] é sua dependência, da qual você pode ler firstName e lastName .

Integrando com código imperativo / legado

Ao integrar com coisas imperativas como plug-ins jQuery ou APIs DOM brutas, algumas coisas desagradáveis ​​podem ser esperadas. Dito isso, ainda espero que você consiga consolidar os efeitos um pouco mais nesse exemplo.


Espero não ter esquecido de ninguém! Avise-me se sim ou se algo não estiver claro. Tentaremos transformar as lições disso em alguns documentos em breve.

Todos 111 comentários

Este exemplo tem uma resposta: https://github.com/facebook/react/issues/14920#issuecomment -466145690

CodeSandbox

Este é um componente de caixa de seleção não controlado que usa um defaultIndeterminate prop para definir o status indeterminado na renderização inicial (o que só pode ser feito em JS usando um ref porque não há indeterminate atributo de elemento). Este prop pretende se comportar como defaultValue , onde seu valor é usado apenas na renderização inicial.

A regra reclama que defaultIndeterminate está faltando na matriz de dependência, mas adicioná-lo faria com que o componente sobrescrevesse incorretamente o estado não controlado se um novo valor fosse passado. A matriz de dependência não pode ser removida inteiramente porque faria com que o status indeterminado fosse totalmente controlado pelo prop.

Não vejo nenhuma maneira de distinguir entre isso e o tipo de caso que a regra _pretende_ capturar, mas seria ótimo se a documentação da regra final pudesse incluir uma solução alternativa sugerida. 🙂

Re: https://github.com/facebook/react/issues/14920#issuecomment -466144466

@billyjanitsch Isso funcionaria em vez disso? https://codesandbox.io/s/jpx1pmy7ry

Eu adicionei useState para indeterminate que é inicializado para defaultIndeterminate . O efeito então aceita [indeterminate] como argumento. No momento, você não o altera - mas se o fizesse mais tarde, acho que também funcionaria? Portanto, o código antecipa um possível caso de uso futuro um pouco melhor.

Então, eu tenho este seguinte caso (extremo?) Onde passo um pouco de html e uso-o com dangerouslySetInnerHtml para atualizar meu componente (algum conteúdo editorial).
Não estou usando o html prop, mas o ref onde posso usar ref.current.querySelectorAll para fazer mágica.
Agora preciso adicionar html às minhas dependências em useEffect , embora não o esteja usando explicitamente. Este é um caso de uso em que eu realmente devo desabilitar a regra?

A ideia é interceptar todos os cliques em links do conteúdo editorial e rastrear algumas análises ou fazer outras coisas relevantes.

Este é um exemplo diluído de um componente real:
https://codesandbox.io/s/8njp0pm8v2

Estou usando o react-redux, portanto, ao passar um criador de ação nos adereços de mapDispatchToProps e usar esse criador de ação em um gancho, recebo um aviso exhaustive-deps .

Obviamente, posso adicionar a ação redux ao array de dependência, mas como a ação redux é uma função e nunca muda, isso é desnecessário, certo?

const onSubmit = React.useCallback(
  () => {
    props.onSubmit(emails);
  },
  [emails, props]
);

Espero que o lint conserte os dep em [emails, props.onSubmit] , mas agora ele sempre conserta os dep em [emails, props] .

  1. Um CodeSandbox demonstrando um exemplo de código mínimo que ainda expressa sua intenção (não "foo bar", mas o padrão de UI real que você está implementando).

https://codesandbox.io/s/xpr69pllmz

  1. Uma explicação das etapas que um usuário executa e o que você espera ver na tela.

Um usuário adicionará um e-mail e os convidará para o aplicativo. Eu intencionalmente removo o resto da IU para apenas button porque eles são irrelevantes para o meu problema.

  1. Uma explicação da API pretendida de seu Gancho / componente.

Meu componente tem um único objeto, onSubmit: (emails: string[]) => void . Ele será chamado com o estado emails quando um usuário enviar o formulário.


EDIT: Respondido em https://github.com/facebook/react/issues/14920#issuecomment -467494468

Isso ocorre porque tecnicamente props.foo() passa props si mesmo como this para foo chamada. Portanto, foo pode depender implicitamente de props . Precisamos de uma mensagem melhor para este caso. A melhor prática é sempre desestruturante.

CodeSandbox

Ele não considera que a montagem e a atualização podem ser distintamente diferentes durante a integração quando libs de terceiros. O efeito de atualização não pode ser incluído na montagem (e remover o array por completo), porque a instância não deve ser destruída em cada renderização

Oi,

Não tenho certeza do que há de errado com meu código aqui:

const [client, setClient] = useState(0);

useEffect(() => {
    getClient().then(client => setClient(client));
  }, ['client']);

Recebi React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

@joelmoss @sylvainbaronnet Agradeço seus comentários, mas escondi seus comentários porque não incluíam as informações solicitadas no início da edição. Isso torna essa discussão desnecessariamente difícil para todos porque falta contexto. Terei prazer em continuar a conversa se você postar novamente e incluir todas as informações relevantes (veja a postagem principal). Obrigado pela compreensão.

  1. CodeSandbox
  2. O usuário pode selecionar um nome e isso força a alteração do sobrenome. O usuário pode selecionar um sobrenome e isso não força a alteração do nome.
  3. Há um gancho personalizado que retorna um pedaço de estado, um componente de seleção que atualiza esse estado e o método de atualização do gancho de estado que atualiza o estado programaticamente. Conforme demonstrado, nem sempre desejo usar a função de atualização, então deixei-a como o último item na matriz retornada.

Acredito que o código como está não deve apresentar erros. Ele funciona agora na linha 35, dizendo que setLastName deve ser incluído no array. Alguma ideia do que fazer a respeito? Estou fazendo algo inesperado?

Eu entendo totalmente, e normalmente faria tudo isso por você, mas no meu caso específico, nenhum código é exclusivo para mim. É simplesmente uma questão de saber se o uso de uma função definida fora do gancho (ou seja, um criador de ação redux) e usada dentro de um gancho deve exigir que essa função seja adicionada como um depósito de gancho.

Fico feliz em criar uma caixa de códigos se você ainda precisar de mais informações. valeu

@joelmoss Acho que meu representante cobre o seu caso também.

Sim, um CodeSandbox ainda ajudaria. Imagine como é alternar o contexto entre os trechos de código das pessoas o dia todo. É um grande tributo mental. Nenhum deles parece o mesmo. Quando você precisa se lembrar de como as pessoas usam criadores de ação no Redux ou algum outro conceito externo ao React, é ainda mais difícil. O problema pode parecer óbvio para você, mas não é nada óbvio para mim o que você quis dizer.

@gaearon Eu entendo que faz sentido, na verdade funcionou para mim porque o que eu queria alcançar era:

Se você deseja executar um efeito e limpá-lo apenas uma vez (na montagem e desmontagem), você pode passar um array vazio ([]) como um segundo argumento.

Muito obrigado pelo esclarecimento. (e desculpe pelo tópico fora)

Aqui está a versão do código do que estou implementando. Não parece muito, mas o padrão é 100% idêntico ao meu código real.

https://codesandbox.io/s/2x4q9rzwmp?fontsize=14

  • Uma explicação das etapas que um usuário executa e o que você espera ver na tela.

Tudo funciona muito bem neste exemplo! Exceto a questão do linter.

  • Uma explicação da API pretendida de seu Gancho / componente.

Tenho vários arquivos como este, onde estou executando uma função no efeito, mas só quero que seja executado sempre que alguma condição for atendida - por exemplo, alterar o Id da série. Não quero incluir a função na matriz.

Isso é o que eu obtenho do linter ao executá-lo no código da sandbox:

25:5   warning  React Hook useEffect has a missing dependency: 'fetchPodcastsFn'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Minha pergunta é: é assim que ele deve se comportar (a regra de linter ou a regra de matriz de gancho)? Existe uma maneira mais idiomática de descrever esse efeito?

@svenanders , estou curioso para saber por que você não deseja incluir fetchPodcastsFn ? É porque você acha que ele muda em cada renderização? Se for, você provavelmente deseja memorizar essa função ou torná-la estática (no caso de não ter nenhum parâmetro)

Por um lado - trata-se de clareza. Quando vejo a função, quero entender facilmente quando ela deve ser acionada. Se eu vir _one_ id na matriz, é claro como cristal. Se eu vejo esse id e um monte de funções, fica mais confuso. Preciso gastar tempo e esforço, possivelmente até mesmo depurando funções, para entender o que está acontecendo.
Minhas funções não mudam em tempo de execução, então não sei se memoizing-los faria diferença (este em particular é uma ação de despacho que dispara um épico, eventualmente resultando em uma mudança de estado).

https://codesandbox.io/s/4xym4yn9kx

  • Passos

O usuário acessa uma rota na página, mas não é um superusuário, portanto, queremos redirecioná-lo para fora da página. O props.navigate é injetado por meio de uma biblioteca do roteador, portanto, não queremos usar window.location.assign para evitar o recarregamento da página.

Tudo funciona!

  • API pretendida

Eu coloquei as dependências corretamente como na caixa de proteção do código, mas o linter está me dizendo que a lista de dependências deve ter props vez de props.navigate . Por que é que?

Um tweet com imagens! https://twitter.com/ferdaber/status/1098966716187582464

EDITAR: um motivo válido para o bug é se navigate() é um método que depende de um limite this , caso em que, tecnicamente, se algo dentro de props muda, então as coisas dentro this também mudará.

CodeSandbox: https://codesandbox.io/s/711r1zmq50

API pretendida:

Este gancho permite eliminar qualquer valor de mudança rápida. O valor depurado refletirá apenas o valor mais recente quando o gancho useDebounce não tiver sido chamado para o período de tempo especificado. Quando usado em conjunto com useEffect, como fazemos na receita, você pode facilmente garantir que operações caras, como chamadas de API, não sejam executadas com muita frequência.

Passos:

O exemplo permite que você pesquise a API do Marvel Comic e usa useDebounce para evitar que chamadas de API sejam disparadas a cada pressionamento de tecla.

IMO adicionar "tudo" que usamos na matriz de dependências não é eficiente.

Por exemplo, considere o gancho useDebounce . Em usos do mundo real (pelo menos no nosso), o delay não mudará após a primeira renderização, mas estamos verificando se ele mudou ou não em cada nova renderização. Portanto, neste gancho, o segundo argumento de useEffect é melhor ser [value] vez de [value, delay] .

Por favor, não pense que verificações de igualdade superficiais são extremamente baratas. Eles podem ajudar seu aplicativo quando colocados estrategicamente, mas apenas tornar cada componente puro pode realmente tornar seu aplicativo mais lento. Tradeoffs.

Acho que adicionar tudo na matriz de dependências tem o mesmo (ou até pior) problema de desempenho que usar componentes puros em todos os lugares. Uma vez que existem muitos cenários em que sabemos que alguns dos valores que estamos usando não mudarão, então não devemos adicioná-los na matriz de dependências, porque como @gaearon disse, verificações de igualdade superficiais não são muito baratas e isso pode tornar nosso aplicativo mais lento.

Tenho alguns comentários sobre como ativar esta regra para funcionar automaticamente em ganchos personalizados por convenção.

Posso ver no código-fonte que há alguma intenção de permitir que as pessoas especifiquem um regex para capturar ganchos personalizados por nome:

https://github.com/facebook/react/blob/ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L490 -L492

O que a equipe do React pensaria sobre uma convenção de nomenclatura adicional para ganchos personalizados com matrizes de dependência? Os ganchos já seguem a convenção de serem prefixados por use para serem detectados como um gancho por este plugin. A convenção que eu proporia para detectar ganchos customizados que dependem de certas dependências seria algum tipo de postfix, algo como WithDeps , significando que um nome de gancho customizado completo poderia ser algo como useCustomHookWithDeps . O postfix WithDeps diria ao plugin que o argumento final da matriz é uma das dependências.

O plug-in ainda poderia oferecer suporte adicional a regexes, mas acho que veria um grande benefício em permitir que os autores da biblioteca simplesmente exportassem seus ganchos personalizados pós-fixados com WithDeps vez de forçar os consumidores da biblioteca a configurar explicitamente o plug-in para todo e qualquer conecta terceiros ou não.

Ele avisa e remove automaticamente a verificação de igualdade personalizada.

const myEqualityCheck = a => a.includes('@');

useCallback(
  () => {
    // ...
  },
  [myEqualityCheck(a)]
);

Para useEffect, não acho que o aviso de "dependência desnecessária" deva aparecer porque essas "dependências" mudarão a forma como o efeito é acionado.

Digamos que eu tenha dois contadores, pai e filho:

  • Os contadores podem ser aumentados / diminuídos independentemente.
  • Quero zerar o contador filho para zero quando o contador pai mudar.

Implementação:

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);

  useEffect(
    () => {
      setChild(0);
    },
    [parent] // "unnecessary dependency: 'parent'"
  );

A regra exaustiva-deps dá o aviso React Hook useEffect has an unnecessary dependency: 'parent'. Either exclude it or remove the dependency array.

screen shot 2019-02-25 at 11 06 40 am

Não acho que o linter deva fazer essa sugestão, pois isso muda o comportamento do app.

  1. CodeSandbox: https://codesandbox.io/s/ol6r9plkv5
  2. Passos: Quando parent muda, child zera.
  3. Intenção: em vez de sinalizar a dependência como desnecessária, o linter deve reconhecer que parent muda o comportamento de useEffect e não deve me aconselhar a removê-lo.

[EDITAR]

Como alternativa, posso escrever algo como

const [parent, setParent] = useState(0)
const [child, setChild] = useState(0)
const previousParent = useRef(parent)

useEffect(() => {
  if (parent !== previousParent.current) {
    setChild(0)
  }

  previousParent.current = parent
}, [parent])

Mas há uma "poesia" no trecho original de que gosto.

Acho que a questão se resume a se devemos interpretar a matriz de dependência apenas como um mecanismo para otimizações de desempenho versus o comportamento real do componente. O React Hooks FAQ usa desempenho como um exemplo de por que você o usaria, mas eu não interpretei o exemplo para significar que você deve usar a matriz de dependência para melhorar o desempenho, em vez disso, vi isso como uma maneira conveniente de pular as chamadas de efeito em geral.

Na verdade, é um padrão que usei algumas vezes para invalidar algum cache interno:

useEffect(() => {
  if (props.invalidateCache) {
    cache.clear()
  }
}, [props.invalidateCache])

Se eu não deveria usá-lo dessa forma, sinta-se à vontade para me informar e desconsiderar este comentário.

@MrLeebo
A respeito

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);
  const updateChild = React.useCallback(() => setChild(0), [parent]);

  useEffect(
    () => {
      updateChild();
    },
    [updateChild] 
  );

Eu também não entenderia esse trecho de você. A dependência só pode ser assumida com base em seu código, mas pode ser um bug. Embora minha proposta não resolva necessariamente isso, ela o torna pelo menos mais explícito.

Parece que isso foi resolvido anteriormente por meio de getDerivedStateFromProps ? Como faço para implementar getDerivedStateFromProps? ajuda?

@nghiepit Eu escondi seu comentário porque você ignorou a lista de verificação exigida no primeiro post (por exemplo, CodeSandbox). Siga a lista de verificação e poste novamente. Obrigado.

@ eps1lon Você teria exatamente o mesmo aviso em useCallback , e eu discordo que esse padrão em geral seja uma melhoria em relação ao original, mas não quero atrapalhar o assunto para falar sobre isso. Para esclarecer, acho que a regra de dependência desnecessária deve ser relaxada para useEffect ou useLayoutEffect especificamente, porque eles podem conter lógica eficaz, mas a regra deve permanecer em vigor para useCallback , useMemo , etc.

Estou encontrando alguns dos mesmos problemas / questões de modelo mental que @MrLeebo descreveu aqui. Minha intuição é que a regra de dependência useEffect não pode ser tão estrita. Eu tenho um exemplo incrivelmente inventado no qual estava trabalhando para uma ideia básica de prova de conceito. Sei que esse código é uma merda e a ideia não é particularmente útil, mas acho que é uma boa ilustração do problema em questão. Acho que @MrLeebo expressou muito bem a minha pergunta:

Acho que a questão se resume a se devemos interpretar a matriz de dependência apenas como um mecanismo para otimizações de desempenho versus o comportamento real do componente. O React Hooks FAQ usa desempenho como um exemplo de por que você o usaria, mas eu não interpretei o exemplo para significar que você deve usar a matriz de dependência para melhorar o desempenho, em vez disso, vi isso como uma maneira conveniente de pular as chamadas de efeito em geral.

https://codesandbox.io/s/5v9w81j244

Eu olharia especificamente para o gancho useEffect em useDelayedItems . Neste momento, incluir a propriedade items na matriz de dependência causará um erro de linting, mas remover essa propriedade ou a matriz inteiramente fornece um comportamento que você não deseja.

A ideia básica do gancho useDelayedItems é dada uma matriz de itens e um objeto de configuração (atraso em ms, tamanho da página inicial), inicialmente receberei um subconjunto de itens dependendo do tamanho da minha página configurada. Depois de config.delay passar, os itens serão agora o conjunto completo de itens. Quando é entregue um novo conjunto de itens, o gancho deve executar novamente essa lógica de "atraso". A ideia é uma versão muito crua e estúpida de renderização atrasada de grandes listas.

Obrigado por todo o trabalho nessas regras, elas têm sido muito úteis no desenvolvimento. Mesmo que meu exemplo seja de qualidade questionável, espero que ele forneça algumas dicas sobre como esses operadores podem ser ab / usados.

EDIT : Eu adicionei alguns comentários esclarecedores dentro do codesandbox em torno da intenção de comportamento. Espero que as informações sejam suficientes, mas deixe-me saber se algo ainda estiver confuso.

Que tal um único valor que combina outros valores?

Exemplo: fullName é derivado de firstName e lastName . Queremos acionar o efeito apenas quando fullName muda (como quando o usuário clica em "Salvar"), mas também queremos acessar os valores que ele compõe no efeito

Demo

Adicionar firstName ou lastName às dependências quebraria as coisas, pois só queremos executar o efeito depois que fullName mudou.

@aweary Não tenho certeza de qual valor você está obtendo com a indireção de alteração de useEffect prop. Parece que seu onClick deve estar lidando com esse "efeito".

https://codesandbox.io/s/0m4p3klpyw

No que diz respeito a valores únicos que combinam outros valores, useMemo provavelmente será o que você deseja. A natureza atrasada do cálculo em seu exemplo significa que ele não mapeia exatamente 1: 1 com seu comportamento vinculado.

Vou criar links codesandbox para esses exemplos e editar este post.

Eu tenho uma regra extremamente simples: se a guia atual mudou, vá para o topo:
https://codesandbox.io/s/m4nzvryrxj

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

E eu ganhei React Hook useEffect has an unnecessary dependency: 'activeTab'. Either exclude it or remove the dependency array

Além disso, quando estava migrando alguns componentes, quero replicar o comportamento do componentDidMount:

useEffect(() => {
    ...
  }, []);

esta regra está reclamando muito sobre isso. Hesito em adicionar todas as dependências ao array useEffect.

Finalmente, se você declarar uma nova função embutida antes de useEffect, como este:
https://codesandbox.io/s/nr7wz8qp7l

const foo = () => {...};
useEffect(() => {
    foo();
  }, []);

Você recebe: React Hook useEffect has a missing dependency: 'foo'. Either include it or remove the dependency array
Não tenho certeza de como me sinto sobre a adição de foo à lista de dependências. Em cada renderização, foo é uma nova função e useEffect sempre seria executado?

@ ksaldana1 colocar o efeito colateral dentro do manipulador de eventos não é suficiente. Isso faria com que o efeito colateral ocorresse antes da atualização real ser confirmada, o que pode fazer com que o efeito colateral seja acionado com mais freqüência do que você deseja.

Também não funcionará ao usar useReducer porque o estado atualizado não estará disponível dentro do manipulador de eventos.

No que diz respeito a valores únicos que combinam outros valores, useMemo provavelmente será o que você deseja.

Se este exemplo usasse useMemo ele quebraria porque useMemo derivaria um novo fullName toda vez que firstName ou lastName mudasse. O comportamento aqui é que fullName não é atualizado até que o botão Salvar seja clicado.

@aweary Algo assim funciona? https://codesandbox.io/s/lxjm02j50m
Estou muito curioso para ver qual é a implementação recomendada.

Também estou curioso para saber mais sobre algumas coisas que você declarou. Você pode me apontar mais informações sobre isso?

Disparo e efeito no manipulador:

isso faria com que o efeito colateral ocorresse antes da atualização real ser confirmada, o que pode fazer com que o efeito colateral seja disparado com mais freqüência do que você deseja.

Usando o estado useReducer no manipulador de eventos:

o estado atualizado não estará disponível.

Obrigado!

@bugzpodder meio que não relacionado, mas a chamada de rolagem deve estar dentro de useLayoutEffect vez de useEffect . Atualmente, há uma oscilação perceptível ao navegar para a nova rota

Não tenho certeza se isso é intencional ou não:

Quando você chama uma função a partir de adereços, o linter sugere adicionar todo o objeto adereços como uma dependência.

Versão curta:

useEffect(function() {
  props.setLoading(true)
}, [props.setLoading])

// ⚠️ React Hook useEffect has a missing dependency: 'props'.

Versão completa: Codesandbox

Tentando novamente ... mas mais simples desta vez;)

Ao passar uma função nos adereços, ou mesmo de qualquer lugar, e usar essa função dentro de um gancho, recebo um aviso exhaustive-deps .

Obviamente, posso adicionar a função ao array de dependência, mas como é uma função e nunca muda, isso é desnecessário, certo?

-> Sandbox

Espero que seja tudo de que você precisa, mas eu simplesmente bifurquei a sandbox de

valeu.

Obviamente, posso adicionar a função ao array de dependência, mas como é uma função e nunca muda, isso é desnecessário, certo?

Isso não está correto; as funções podem mudar o tempo todo quando o pai é renderizado novamente.

@siddharthkp

Quando você chama uma função a partir de adereços, o linter sugere adicionar todo o objeto adereços como uma dependência.

Isso ocorre porque, tecnicamente, props.foo() passa props si mesmo como this para foo chamada. Portanto, foo pode depender implicitamente de props . Precisamos de uma mensagem melhor para este caso. A melhor prática é sempre desestruturante.

as funções podem mudar o tempo todo quando o pai é renderizado novamente.

Claro, mas se a função fosse definida em outro lugar e não a partir de um componente pai, ela nunca mudaria.

Claro, mas se a função fosse definida em outro lugar e não a partir de um componente pai, ela nunca mudaria.

Se você importá-lo diretamente, a regra do lint não solicitará que você o adicione às dependências de efeito. Somente se estiver no escopo de renderização. Se estiver no escopo de renderização, a regra lint não sabe de onde vem. Mesmo que não seja dinâmico hoje, pode ser amanhã, quando alguém alterar o componente pai. Portanto, especificá-lo é o padrão correto. Não faz mal especificá-lo se for estático de qualquer maneira.

thx @gaearon

Isso ocorre porque, tecnicamente, props.foo() passa props si mesmo como this para foo chamada. Portanto, foo pode depender implicitamente de props . Precisamos de uma mensagem melhor para este caso. A melhor prática é sempre desestruturante.

Isso responde minha pergunta também. Obrigado! 😄

https://codesandbox.io/s/98z62jkyro

Portanto, estou criando uma biblioteca para lidar com a validação de entrada, registrando todas as entradas usando uma API exposta em um contexto para que cada entrada se registre. Criei um gancho personalizado chamado useRegister.

Ex:

 useEffect(
    () => {
      register(props.name, props.rules || []);
      console.log("register");
      return function cleanup() {
        console.log("cleanup");
        unregister(props.name);
      };
    },
    [props.name, props.rules, register, unregister]
  );

Ao forçar props.rules a fazer parte das dependências, parece que termina em um loop de renderização infinito. Props.rules é uma matriz de funções a serem registradas como validadores. Só detectei esse problema ao fornecer matrizes como dependências. Em minha caixa de códigos, você pode ver que ele faz um loop ao abrir o console.

Apenas ter props.name como dependência faz com que funcione conforme o esperado. Reforçar as dependências irá, como apontado anteriormente, alterar o comportamento do aplicativo e, nesta ocasião, os efeitos colaterais são graves.

@bugzpodder

Re: https://github.com/facebook/react/issues/14920#issuecomment -467212561

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Este parece ser um caso legítimo. Vou relaxar o aviso para permitir dependências estranhas apenas para efeitos. (Mas não para useMemo ou useCallback .)

Além disso, quando estava migrando alguns componentes, quero replicar o comportamento do componentDidMount:
esta regra está reclamando muito sobre isso. Hesito em adicionar todas as dependências ao array useEffect.

Desculpe, você não adicionou nenhum exemplo para isso, então perdemos a oportunidade de discutir isso. É exatamente por isso que o post do OP pede para especificar um exemplo concreto de IU . Você fez para o primeiro ponto, mas não este. Fico feliz em discutir isso quando você adiciona um exemplo concreto para isso. Os detalhes realmente dependem disso.

Finalmente, se você declarar uma nova função inline antes de useEffect, como este: https://codesandbox.io/s/nr7wz8qp7l

Nesse caso, a solução fácil é mover doSomething para o efeito. Então você não precisa declará-lo. Alternativamente, você pode useCallback torno de doSomething . Estou aberto a relaxar a regra para permitir a omissão de funções que usam apenas dependências declaradas. Mas isso pode ficar confuso se você tiver uma função chamando outra função e adicionar um prop ou estado a uma dessas funções. De repente, todos os departamentos de efeito usando-o transitivamente precisam ser atualizados. Isso pode ficar confuso.

Não sei se esta é uma solicitação de recurso para uma nova regra ou algo que poderia ser melhorado em exhaustive-deps

import React from 'react'
import emitter from './thing'

export const Foo = () => {
  const onChange = () => {
    console.log('Thing changed')
  }

  React.useEffect(() => {
    emitter.on('change', onChange)
    return () => emitter.off('change', onChange)
  }, [onChange])

  return <div>Whatever</div>
}

Como a função onChange é criada a cada renderização, o argumento useEffect hook [onChange] é redundante e também pode ser removido:

   React.useEffect(() => {
     emitter.on('change', onChange)
     return () => emitter.off('change', onChange)
-  }, [onChange])
+  })

O linter pode detectar isso e aconselhá-lo a excluir o argumento da matriz.

Houve situações em que mantive uma lista de itens de array, apenas para perceber que um ou mais deles estavam sendo criados e invalidando o gancho a cada renderização de qualquer maneira.

Acabei de lançar [email protected] com algumas correções e mensagens melhores para esta regra. Nada inovador, mas alguns casos devem ser resolvidos. Estarei vendo o resto na próxima semana.

Eu também postei a primeira etapa possível para omitir as dependências da função "segura" aqui: https://github.com/facebook/react/pull/14996. (Consulte os testes.) Se você tiver idéias sobre heurísticas úteis e como orientar as pessoas para as correções corretas, comente sobre o PR.

@gaearon Excelente ideia. Isso definitivamente será útil para ter um estilo melhor ao usar ganchos 🙏

@gaearon Ainda não funciona caso a ação venha do prop. Considere este exemplo:

image

Em que setScrollTop é uma ação redux.

Neste exemplo no componente Slider , estou usando useEffect para esperar até que o DOM esteja disponível para que eu possa montar o componente noUiSlider. Portanto, passo [sliderElement] para garantir que o ref esteja disponível no DOM quando o efeito for executado. Servimos para renderizar nossos componentes também, então isso também garante que o DOM esteja disponível antes da renderização. Os outros adereços que uso em useEffect (ou seja, min, max, onUpdate, etc) são constantes e, portanto, não vejo necessidade de serem passados ​​para o efeito.

screen shot 2019-03-02 at 5 17 09 pm


Este é o efeito visto em codesandbox:

const { min, max, step } = props;
const sliderElement = useRef();

useEffect(() => {
  if (!sliderElement.current) {
    return;
  }

  const slider = sliderElement.current;
  noUiSlider.create(slider, {
    connect: true,
    start: [min, max],
    step,
    range: {
      min: [min],
      max: [max],
    },
  });

  if (props.onUpdate) {
    slider.noUiSlider.on('update', props.onUpdate);
  }

  if (props.onChange) {
    slider.noUiSlider.on('change', props.onChange);
  }
}, [sliderElement]);

@ WebDeg-Brian Não posso ajudá-lo sem uma demonstração completa do CodeSandbox. Desculpa. Veja a postagem principal.

Eu postei um pouco sobre o equívoco comum de "funções nunca mudam":

https://overreacted.io/how-are-function-components-different-from-classes/

Não é exatamente o mesmo tópico, mas é relevante para esta regra.

Olá @gaearon , Aqui está o exemplo que você me pediu para postar aqui (do tweeter) :)

Basicamente, estou tentando converter minha biblioteca react-trap em ganchos.
Esta é apenas uma armadilha para eventos, fora / dentro de um elemento.

Meu problema é que, se useEffect não depender do valor do estado ( trapped ), ele às vezes fica obsoleto.
Escrevi alguns comentários e logs para demonstrar. Observe o arquivo useTrap.js , os comentários e logs estão nas funções useEffect e preventDefaultHelper .

Que eu saiba, se um valor não estiver dentro de useEffect então ele não deve fazer parte de suas dependências (corrija-me se eu estiver errado).

  1. Um CodeSandbox
  2. Passos:
    Um usuário clica dentro da caixa para torná-la ativa, e fora para torná-la inativa, o usuário também pode clicar com o botão direito do mouse, embora para o primeiro clique não deva acionar o menu de contexto ( e.preventDefault )
    Quando digo "primeiro clique", quero dizer o primeiro clique que muda o estado.
    Dada uma caixa ativa, um clique com o botão direito fora dela mudará o estado para "não ativo" e impedirá o menu de contexto. outro clique externo não afetará o estado, portanto, o menu de contexto deve aparecer.

Espero estar sendo claro aqui e que o caso de uso seja compreensível. Avise-me se precisar fornecer mais informações. Obrigado!

Olá @gaearon , estou adicionando aqui meu gancho personalizado, conforme você me sugeriu no Twitter! Estou lutando para encontrar a forma certa que não ignore dependências.

É elaborado como um exemplo, espero poder explicá-lo de uma maneira clara e compreensível.

Este é o estado atual dele: react-async-utils / src / hooks / useAsyncData.ts

Visão geral da funcionalidade
Ajudar o usuário a lidar com chamadas assíncronas, dados resultantes e estado deles ao longo do processo.

triggerAsyncData atualiza o estado asyncData forma assíncrona de acordo com uma função getData que retorna Promise . triggerAsyncData pode ser invocado como um efeito ou "manualmente" pelo usuário do gancho.

Desafios

  1. As dependências do efeito que invoca triggerAsyncData são as intrincadas. triggerAsyncData é uma dependência do efeito, mas é criado em cada renderização. Linha de pensamento até agora:

    1. Basta adicioná-lo como uma dependência => Mas então o efeito é executado em cada renderização.

    2. Adicione-o como uma dependência, mas use useMemo / useCallback com triggerAsyncData => useMemo / useCallback só deve ser usado para otimizações de desempenho ATÉ ONDE SEI.

    3. Faça o escopo dentro do efeito => Então, não posso devolvê-lo ao usuário.

    4. Em vez de usar triggerAsyncData como dependência, use as dependências de triggerAsyncData como dependências => Melhor opção que encontrei até agora. Mas quebra a regra de "depuração exaustiva".

  2. Cada parâmetro de entrada do gancho personalizado é / se torna uma dependência de nosso efeito interno. Portanto, funções embutidas e objetos literais como parâmetros fazem com que o efeito seja executado com muita frequência.

    1. Deixe a responsabilidade para o usuário. Eles fornecerão valores apropriados, usando useMemo / useCallback se necessário => Receio que muitas vezes não o façam. E se o fizerem, é bastante prolixo.

    2. Permita um argumento extra para o gancho customizado para fornecer as dependências das entradas e use-o em vez das próprias entradas => Legal, menos detalhado, mais controle para o usuário. Eu estou usando isso. Mas quebra a regra de "depuração exaustiva". (Na verdade, usando I am this e voltando às dependências regulares se o argumento extra não for fornecido. Acho que é um padrão poderoso).

  3. Dependências mal gerenciadas para o gancho personalizado geram um loop assíncrono infinito devido ao efeito interno. Usei a programação defensiva para evitar isso, mas adiciona um "fake"? dependência ( asyncData ). Isso quebra a regra "exaustiva-deps" novamente.

Explicação mais longa do que eu gostaria ... mas acho que reflete minha luta para usar ganchos de maneira adequada. Informe-me se houver algo mais que eu possa fazer para esclarecer essas dificuldades.

Obrigado por todo o trabalho duro aqui!

Olá @gaearon, obrigado por todo o seu trabalho árduo.

  1. Um exemplo de busca de dados assíncronos mínimos, exemplo de CodeSandbox .

  2. Espera-se que o usuário veja 5 strings de título lorem ipsum obtidas da API json .

  3. Criei um gancho personalizado para a busca de dados com a API pretendida:

const { data, isLoading, isError } = useDataApi('https://jsonplaceholder.typicode.com/posts')

Recursos internos do gancho useDataApi :

...
  const fetchData = async () => {
    let response;
    setIsError(false);
    setIsLoading(true);

    try {
      response = await fetch(url).then(response => response.json());

      setData(response);
    } catch (error) {
      setIsError(true);
    }

    setIsLoading(false);
  };

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );
...

O problema é este código

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );

onde react-hooks/exhaustive-deps dispara um aviso de que devo adicionar fetchData à minha matriz de dependência e url deve ser removido.

Se eu mudar este gancho para

  useEffect(
    () => {
      fetchData();
    },
    [fetchData]
  );

então, ele dispara solicitações continuamente e nunca para, o que é um grande problema. Não tenho certeza se meu código está cheio de erros ou react-hooks/exhaustive-deps está disparando um falso positivo.

Qualquer ajuda apreciada. Muito obrigado.

PS: Eu li seu comentário sobre useEffect não adequado para busca de dados , no entanto, os documentos react afirmam que Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects que me deu a confiança de que os dados useEffect são ótimos para busca de dados. Então agora estou um pouco confuso 😕

@ jan-stehlik você deve envolver fetchData com useCallback. bifurquei seu codesandbox com as alterações necessárias aqui https://codesandbox.io/s/pjmjxprp0m

Muito útil, muito obrigado @viankakrisna !

Pelo que sei, se um valor não estiver dentro de useEffect, ele não deve fazer parte de suas dependências (corrija-me se estiver errado).

Eu acho que você está errado aqui. Ou melhor, um pouco confuso. Você está usando handleEvent dentro do seu efeito. Mas você não declara isso. É por isso que os valores que lê são obsoletos.

Interessante.

Você está usando o handleEvent dentro do seu efeito. Mas você não declara isso. É por isso que os valores que lê são obsoletos.

O que você quer dizer com: _ "Mas você não declara isso" _?
É declarado abaixo do efeito (igual a todos os outros manipuladores).

Ou você quer dizer que porque o manipulador está usando o valor de estado e o efeito é anexar o manipulador, isso significa que o efeito depende desse valor de estado?

Ou você quer dizer que porque o manipulador está usando o valor de estado e o efeito é anexar o manipulador, isso significa que o efeito depende desse valor de estado?

Sim, se você usar uma função, você deve declará- la em deps (e nesse caso envolvê-la com useCallback para evitar recriá-la), ou tudo que a função usa.

Ok, isso é novidade para mim! Obrigado por esta entrada @gaearon :)
Só quero que fique bem claro para mim (e para os outros?) ...

Se um efeito estiver invocando, passando ou fazendo qualquer coisa com uma função, precisamos passar para sua matriz deps:
A própria função OU As variáveis ​​que esta função usa.
Se a função for declarada dentro do componente de função / gancho personalizado, é aconselhável envolvê-la com useCallback para que ela não seja recriada toda vez que nosso componente ou gancho personalizado estiver em execução.

Devo dizer que não vi nos documentos .
Você acha que pode adicioná-lo à seção _Nota_?

Observação
A matriz de entradas não é passada como argumentos para a função de efeito. Conceitualmente, porém, é isso que eles representam: cada valor referenciado dentro da função de efeito também deve aparecer no array de entradas. No futuro, um compilador suficientemente avançado poderia criar esse array automaticamente.

Editar
Mais uma coisa, em meu exemplo, quais são as dependências de useCallback quando envolve handleEvent (ou qualquer outro manipulador por esse motivo). é o próprio event ?

Devo dizer que não vi nos documentos.

A documentação diz "cada valor referenciado dentro da função de efeito também deve aparecer na matriz de entradas". Funções também são valores. Concordo que precisamos documentar isso melhor - que é sobre o que este tópico trata :-) Estou coletando casos de uso para uma nova página de documentação dedicada a isso.

Mais uma coisa, no meu exemplo, quais são as dependências de useCallback quando envolve o handleEvent (ou qualquer outro manipulador por esse motivo). é o próprio evento?

Não tenho certeza do que você quer dizer. São quaisquer valores referenciados pela função fora dela. Assim como em useEffect .

Acho que não pensei sobre isso com funções como dependências. meu modelo mental estava errado, eu estava pensando em passar uma função como uma dependência apenas se ela vier como suporte ou argumento para meu componente / gancho. Obrigado por esclarecer isso para mim.

Quanto ao useCallback , usei-o assim:

const memoHandleEvent = useCallback(
    handleEvent
);

e, claro, passou memoHandleEvent como uma dependência de useEffect também para addEventListener da função handleEvent real. parece funcionar, espero que essa seja a maneira adequada e idiomática de fazê-lo.

Nota useCallback sem o segundo argumento não faz nada.

Novamente, uma sandbox completa seria necessária. Não posso dizer se sua correção está correta por uma descrição incompleta como esta.

Observe que useCallback sem o segundo argumento não faz nada.

Argg! : fazendo careta: lol

Novamente, uma sandbox completa seria necessária. Não posso dizer se sua correção está correta por uma descrição incompleta como esta.

Ah, este é o mesmo link de cima. acabei de atualizá-lo :)

Não atualize os links do CodeSandbox: PI precisa deles como eram originalmente - caso contrário, não posso testar a regra de lint neles. Você poderia criar duas caixas de proteção separadas se tiver duas soluções diferentes? Para que eu possa verificar cada um.

OPA, desculpe! : PI excedeu o número de sandboxes em minha conta. deixe-me deletar alguns e vou criar outro (e reverter as alterações no original).

@gaearon, este é o segundo link para a solução com useCallback

Tenho um cenário que acredito estar bem, mas o linter reclama. Minha amostra:

CodeSandbox

O que o exemplo está tentando representar é que quando um usuário clica em um botão, uma solicitação é feita para solicitar novos dados com base em um ID fornecido anteriormente e uma função. Se o Id for alterado, ele não deve solicitar novos dados; apenas uma nova solicitação de recarregamento deve acionar uma nova solicitação de dados.

O exemplo aqui é um pouco artificial. Em meu aplicativo real, o React reside em um DIV que é uma pequena parte de um aplicativo da web muito maior. A função que é passada é via Redux e mapDispatchToProps, onde uma criação de ação pega o Id e faz uma solicitação ajax para buscar dados e atualizar a loja. O prop refreshRequest é passado por meio de React.createElement. Em minha implementação original, eu tinha um código parecido com este no componente de classe:

componentDidUpdate (prevProps) { const { getData, someId, refreshRequest} = this.props; if (prevProps.refreshRequest!== this.props.refreshRequest) { getData(someId); } }

Estou tentando implementar o mesmo comportamento com um gancho de efeito. Mas, conforme está escrito na amostra, o linter reclama:

aviso React Hook useEffect tem dependências ausentes: 'getData' e 'someId'. Inclua-os ou remova a matriz de dependência

Se eu adicionar tudo o que o linter deseja, se o usuário clicar em um dos botões da amostra, useEffect será acionado. Mas eu só quero que seja acionado quando o botão Solicitar novos dados for pressionado.

Espero que faça sentido. Eu ficaria feliz em esclarecer mais, se algo não estiver claro. Obrigado!

Acabei de publicar [email protected] que tem um suporte experimental para detectar dependências de funções nuas (que tendem a não ser muito úteis sem useCallback ). Aqui está um gif:

demo

Adoraria se vocês pudessem tentar em seus projetos e ver como é! (Comente sobre esse fluxo específico em https://github.com/facebook/react/pull/15026.)

Vou tentar em exemplos deste tópico amanhã.

Ainda não experimentei, mas me pergunto se trata de içamento. Este é apenas um "exemplo mínimo" que criei na hora, então não é útil para nada, mas eu uso muito as declarações içadas para tornar mais fácil ver a instrução return .

function Component() {
  useEffect(() => {
    handleChange
  }, [handleChange])

  return null

  function handleChange() {}
}

Seria bom se a regra tivesse uma opção para configurar outra função para se comportar como useEffect que diz respeito ao linter. Por exemplo, adicionei isso para poder usar facilmente funções assíncronas para efeitos que fazem uma chamada AJAX - no entanto, assim, eu perco todos os exhaustive-deps benefícios do linting:

const useAsyncEffect = (fn, ...args) => {
    /* eslint-disable react-hooks/exhaustive-deps */
    useEffect(() => {
        fn();
    }, ...args);
};

Edit: Nevermind, acabei de notar que isso já é possível usando a opção additionalHooks da regra.

Olá a todos,
Eu tenho um exemplo https://codesandbox.io/s/znnmwxol7l

Abaixo está o que eu quero:

function App() {
  const [currentTime, setCurrentTime] = React.useState(moment());

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime.format("MMMM")] // <= this proplem [currentTime]
  );

  return (
    <div className="App">
      <h1>Current month: {currentMonth}</h1>
      <div>
        <button
          onClick={() => setCurrentTime(currentTime.clone().add(1, "days"))}
        >
          + 1 day
        </button>
      </div>
      <div>{currentTime.toString()}</div>
    </div>
  );
}

Mas é isso que eu recebo:

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime] // <= this proplem
  );

Sempre que currentTime mudar, então currentMonth recompute desnecessário

Ou de alguma forma eu posso fazer isso abaixo:

  const currentMonth = React.useMemo(
    () => {
      return currentTime.format("MMMM");
    },
    [myEqualityCheck(currentTime)]
  );

Desculpe se isso já foi respondido, não consegui ver no tópico acima:
A maneira de executar o gancho useEffect apenas na montagem é definir uma matriz de entradas vazia como o segundo parâmetro. No entanto, ao fazer isso, as queixas exaustivas deps sobre a inclusão desses argumentos de entrada, o que mudaria o efeito de também ser executado na atualização.
Qual é a abordagem para executar useEffect apenas na montagem com o exaustivo-deps habilitado?

@einarq Eu acho que você precisa se certificar de que todos os valores referidos em sua montagem useEffect nunca serão alterados. Isso pode ser conseguido usando outros ganchos como useMemo . Depois disso, independentemente se esta regra ESlint adiciona todas as referências no array (com autofix) ou não, o código será executado apenas uma vez.

@einarq Conforme afirmado em outro lugar no tópico, não podemos ajudá-lo sem um CodeSandbox. Porque a resposta realmente depende do seu código.

@nghiepit Seu exemplo realmente não faz sentido para mim. Se sua entrada for currentTime.format("MMMM") então useMemo não otimiza nada para você porque você já calculou . Então você está computando duas vezes desnecessariamente.

É possível especificar qual índice de argumento é o retorno de chamada na opção additionalHooks ? Vejo que estamos assumindo agora no código que seria o primeiro https://github.com/facebook/react/blob/9b7e1d1389e080d19e71680bbbe979ec58fa7389/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.51 --js L1081

Atualmente não é possível. Nossa orientação geral é que os Ganchos personalizados devem preferir não ter um argumento deps porque se torna muito difícil pensar sobre como os departamentos compõem. Se você usar uma função, pode pedir aos usuários que usem useCallback .

@CarlosGines Pode fazer um CodeSandbox, conforme solicitado no post do OP. Estou perguntando por um motivo - caso contrário, será difícil para mim verificar as alterações. Especialmente quando é escrito em TypeScript.

Acabei de publicar [email protected] com mensagens, espero, mais úteis e heurísticas mais inteligentes. Experimente em seus projetos antes de ficar estável.

Espero que, com a maioria dos exemplos neste tópico, ele forneça conselhos razoáveis ​​que devem ajudá-lo a corrigir os problemas.

deveria ser [email protected] ?

sim.

Na verdade, acaba de publicar [email protected] com algumas pequenas alterações.

@gaearon Você conseguiu fazer eslint funcionar dentro da caixa de códigos por acaso?

@einarq talvez algo assim funcionasse?

const useDidMount = fn => {
  const callbackFn = useCallback(fn)
  useEffect(() => {
    callbackFn()
  }, [callbackFn])
}

Na verdade, estou envergonhado de dizer que não consigo fazer o plug-in funcionar em um aplicativo CRA simples (com base no meu snippet ).
aqui está o repositório bifurcado do codesandbox.

Eu vi esta observação nos documentos :

Observação: se você estiver usando o aplicativo Create React, aguarde uma versão correspondente dos scripts react que incluem esta regra em vez de adicioná-la diretamente.

Mas não há como testá-lo com o CRA no momento? 👂

Sim, você teria que ejetar e adicioná-lo à configuração ESLint até que o adicionemos lá. Você pode ejetar em um galho e não fundi-lo. :-)

Oi,

antes de tudo: muito obrigado por trabalhar tão de perto com a comunidade e pelo trabalho incrível que você está fazendo em geral!

Temos um problema com um gancho personalizado que construímos em torno da API Fetch . Eu criei um Codesandbox para demonstrar o problema.

Exemplo em Codesandbox

https://codesandbox.io/s/kn0km7mzv

Observação: o problema (veja abaixo) resulta em um loop infinito e não quero fazer o DDoS jsonplaceholder.typicode.com que estou usando para demonstrar o problema. Portanto, incluí um limitador de solicitação simples usando um contador. Isso não é necessário para demonstrar o problema, mas parecia errado disparar uma quantidade ilimitada de solicitações para este grande projeto.

Explicação

A ideia é tornar mais fácil lidar com os 3 estados possíveis de uma solicitação de API: Carregando, Sucesso e Erro. Assim, construímos um gancho personalizado useFetch() que retorna 3 propriedades isLoading , response e error. It makes sure that either response or error is set and updates API . As the name implies, it uses the Fetch`.

Para fazer isso, ele usa 3 ganchos useState :

  const [isLoading, setIsLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

e um gancho useEffect :

useEffect(
    () => {
      fetch(url, fetchConfig)
        .then(/* Handle fetch response... See Codesandbox for the actual implementation */)
    },
    [url]
  );

Ele funciona bem desde que o array de dependências de useEffect contenha apenas [url] . Se adicionarmos fetchConfig (= [url, fetchConfig] ), resulta em um loop infinito. Para o nosso caso de uso particular, seria suficiente apenas reexecutar o efeito quando o url mudar, mas o linter não permitir o uso apenas de [url] (testado com v1.4.0 e v1.5.0-beta.1 ).

No final do gancho personalizado, as 3 variáveis ​​de estado são retornadas como um objeto:

  return {
    error,
    isLoading,
    response
  };

Não tenho certeza se este é o lugar certo para pedir orientação sobre esse assunto, pois a sugestão de linting faz sentido, eu acho. Desculpe se não for.

E se fetchConfig mudar? Por que você espera que seja estático?

Tudo bem. Eu lancei [email protected] com as correções e melhorias da semana passada.
Este tópico tem sido extremamente útil para encontrar diferentes padrões que causam problemas.

Muito obrigado a todos vocês. (Especialmente aqueles que forneceram sandboxes. :-)


Não vou responder a todos pessoalmente em detalhes porque são muitos casos.

Em vez disso, escreveremos receitas e dicas comuns nos próximos dias, e criaremos um link para elas nos documentos. Vou me certificar de que todos os padrões comuns neste tópico sejam abordados (ou pedirei para fazer um acompanhamento em uma edição separada para padrões raros).

Assim que os exemplos forem publicados, comentarei aqui e editarei todos os comentários contendo um CodeSandbox anexando um link para a resposta / exemplo relevante que demonstra a correção correta. Espero que isso ajude você e futuros leitores.

Saúde!

❤️

@timkraut você deve ser capaz de adicionar fetchConfig nas dependências, e o componente deve envolvê-lo com memo para que a referência permaneça.
Um exemplo: https://codesandbox.io/s/9l015v2x4w


Meu problema com isso é que agora o componente precisa estar ciente dos detalhes de implementação do gancho ...

Não tenho certeza se esse tópico ainda está aberto para discussão de exemplo, mas ainda estou pensando nas melhores práticas. Minha pergunta é sobre como controlar quando o gancho useEffect é acionado com a matriz de dependência e usar os valores naquele momento em vez de exigir a declaração de um ref separado para contornar a regra de lint.

Exemplo

https://codesandbox.io/s/40v54jnkyw

Neste exemplo, estou tentando salvar automaticamente os valores de entrada periodicamente.

Explicação

A cada 5 segundos, o código tenta salvar automaticamente os valores atuais (por enquanto, apenas imprima-os na tela). O linter exigiria que todos os valores de entrada fossem incluídos na matriz de dependência, o que mudaria a frequência com que o efeito seria disparado.

  useEffect(
    () => {
      if (autoSaveTick % 5 === 0) {
        setAutosaveValue(
          `${input1Value.value}, ${input2Value.value}, ${input3Value.value}`
        );
      }
    },
    [autoSaveTick]
  );

A alternativa que vejo é ter uma implementação semelhante aos ganchos useTick e useInterval definidos no exemplo, onde há um retorno de chamada atribuído a um ref. Parece que isso causaria redefinições de função desnecessárias apenas por uma questão de alinhamento com a regra do lint.

Estou procurando as melhores práticas para escrever um efeito que é executado quando uma variável muda (Variável A) que usa outros valores de variáveis ​​(Variável B e Variável C) no momento da mudança de variável anterior (Variável A).

E se fetchConfig mudar? Por que você espera que seja estático?

Eu ficaria bem em adicioná-lo à matriz de dependências. Em nosso caso específico de negócios, isso nunca pode acontecer, mas acho que é uma boa ideia adicioná-lo de qualquer maneira.

Meu problema com isso é que agora o componente precisa estar ciente dos detalhes de implementação do gancho ...

Esse é exatamente o problema que temos também: / Em nossa implementação, até usamos uma propriedade adicional para facilitar a configuração do corpo, de modo que teríamos que usar 2 useMemo() chamadas sempre que usarmos esse gancho. Não tenho certeza de como superar essa limitação, ainda. Se você tiver uma ideia, me avise!

Como você pode executar um useEffect que tem dependências apenas uma vez sem a regra reclamando ao passar um array vazio?

Diga, algo como:

useEffect(() => { init({ dsn, environment}) }, [])

Meu problema com isso é que agora o componente precisa estar ciente dos detalhes de implementação do gancho ...

Eu não diria que esses são detalhes de implementação. É a sua API. Se um objeto for passado, presumimos que ele pode mudar a qualquer momento.

Se na prática for sempre estático, você pode torná-lo um argumento para uma fábrica de ganchos. Como createFetch(config) que retorna useFetch() . Você liga para a fábrica no nível superior.

Eu entendo que é um pouco estranho. Temos um problema semelhante com o useSubscription qual estamos trabalhando. Mas, como é um problema comum, isso pode significar que useMemo é realmente uma resposta legítima e as pessoas deveriam se acostumar a fazê-lo nesses casos.

Na prática, examinaremos mais isso no futuro. Mas você não deve tratar um objeto potencialmente dinâmico como estático, porque o usuário não poderá alterá-lo.

@asylejmani Você não forneceu detalhes sobre seus casos de uso, conforme solicitado na postagem superior. Por que você espera que alguém possa dar uma resposta à sua pergunta?

O objetivo da regra é dizer a você que environment e dsn podem mudar com o tempo e seu componente deve lidar com isso. Portanto, ou seu componente está cheio de erros (porque não lida com alterações nesses valores) ou você tem algum caso único em que isso não importa (nesse caso, você deve adicionar uma regra de lint para ignorar o comentário explicando o porquê).

Em ambos os casos, não está claro o que você está perguntando. A regra reclama que as coisas podem mudar e você não lida com essa mudança. Sem um exemplo completo, só você pode responder por que acha que não é necessário lidar com isso.

@gaearon, desculpe por não ter sido mais claro (sempre parece claro na cabeça) :) minha pergunta é mais como você obteria o componentDidMount com useEffect.

Fiquei com a impressão de que um array vazio faz isso, mas a regra está me dizendo para sempre incluir dependências no array.

@asylejmani Acho que a maior pegadinha com métodos de ciclo de vida de classe como componentDidMount é que tendemos a pensar nisso como um método isolado, mas na verdade é parte de um fluxo.
Se você referenciar algo em componentDidMount , provavelmente precisará lidar com isso em componentDidUpdate também, ou seu componente pode ficar cheio de erros.
Isso é o que a regra está tentando corrigir, você precisa lidar com os valores ao longo do tempo.

  1. componente montado, faça algo com um suporte
  2. componente atualizado (por exemplo: o valor da proposta mudou), faça algo com o novo valor da proposta

O número 1 é onde você coloca a lógica em componentDidMount / useEffect body
O número 2 é onde você coloca a lógica em componentDidUpdate / useEffect deps

A regra é reclamar de que você não está cumprindo a parte número 2 do fluxo

@gaearon, desculpe por não ter sido mais claro (sempre parece claro na cabeça) :) minha pergunta é mais como você obteria o componentDidMount com useEffect.

Fiquei com a impressão de que um array vazio faz isso, mas a regra está me dizendo para sempre incluir dependências no array.

Acho que eu e @asylejmani estamos na mesma página aqui, mas acho que o que você está dizendo é que @gaearon é que provavelmente estamos errados em apenas executar o efeito na montagem se realmente tivermos dependências.
Essa é uma declaração justa? Acho que estou pensando que fornecer um array vazio é como dizer "Eu sei o que estou fazendo", mas entendo por que você ainda deseja manter a regra em vigor.

Desculpe por não fornecer uma sandbox ainda. Comecei outra noite com um exemplo Create React App, não consegui descobrir como executar eslint na sandbox e perdi a sandbox ao recarregar o navegador sem salvar primeiro (assumiu CodeSandbox temp armazenado, meu problema).
Então eu tive que ir para a cama e não tive tempo desde então.

Em qualquer caso, entendi o que você está dizendo e que, em cenários normais, provavelmente é melhor incluir essas dependências e não presumir que seja o suficiente para executar apenas na montagem.

Provavelmente, existem casos de uso válidos para isso também, mas é um pouco difícil de explicar ou apresentar um bom exemplo, portanto, conviverei com a desativação da regra em linha quando necessário.

@asylejmani é seu caso de uso semelhante a https://github.com/facebook/react/issues/14920#issuecomment -466378650? Não acho que seja possível que a regra entenda o cenário neste caso, então só precisaremos desabilitá-la manualmente para esse tipo de código. Em todos os outros casos, a regra funciona como deveria.

Não tenho certeza se isso faz sentido, mas um cenário bastante comum para mim é algo assim:

useEffect(() => {
    if(!dataIsLoaded) { // flag from redux
        loadMyData(); // redux action creator
    }
}, []);

Ambas as dependências vêm do redux. Os dados (neste caso) devem ser carregados apenas uma vez, e o criador da ação é sempre o mesmo.

Isso é específico para redux, e sua regra eslint não pode saber disso, então entendo por que deveria avisar. Ainda está se perguntando se fornecer um array vazio talvez deva apenas desabilitar a regra? Eu gosto que a regra me diga sobre dependências ausentes se eu forneci algumas, mas não todas, ou se eu não forneci nenhuma. Array vazio significa algo diferente para mim. Mas pode ser apenas eu :)

Obrigado por todo o seu trabalho árduo! E por tornar nossas vidas como desenvolvedores melhores :)

Meu caso de uso é muito mais simples e posso, é claro, adicionar todas as dependências e ainda funcionará da mesma forma, no entanto, fiquei com a impressão de que a regra "avisará" quando você tiver algumas dependências, mas não tiver outras.

O caso de uso de @einarq é algo que usei algumas vezes, por exemplo, em "componentDidMount" carregue os dados se não houver nenhum (de redux ou qualquer outro).

Também concordo que, nesses casos, desabilitar a regra in-line é a melhor escolha. Nesse caso, você sabe exatamente o que está fazendo.

Acredito que toda a minha confusão foi [] vs [alguns] e, claro, obrigado @gaearon pelo trabalho incrível :)

Acho que eu e @asylejmani estamos na mesma página aqui, mas acho que o que você está dizendo é que @gaearon é que provavelmente estamos errados em apenas executar o efeito na montagem se realmente tivermos dependências. Essa é uma declaração justa?

sim. Se o seu componente não lida com atualizações para um prop, geralmente é bugado. O design de useEffect força você a enfrentá-lo. É claro que você pode contornar isso, mas o padrão é cutucá-lo para lidar com esses casos. Este comentário explica bem: https://github.com/facebook/react/issues/14920#issuecomment -470913287.

Ambas as dependências vêm do redux. Os dados (neste caso) devem ser carregados apenas uma vez, e o criador da ação é sempre o mesmo.

Se for o mesmo, incluí-lo nas dependências não afetará você. Quero enfatizar isso - se você tem certeza de que suas dependências nunca mudam, não há mal nenhum em listá-las . No entanto, se mais tarde acontecer que eles mudem (por exemplo, se um componente pai passar uma função diferente dependendo do estado), seu componente irá lidar com isso corretamente.

Ainda está se perguntando se fornecer um array vazio talvez deva apenas desabilitar a regra?

Não. Fornecer um array vazio e depois se perguntar por que alguns adereços ou estado estão obsoletos é literalmente o erro mais comum.

>

Faz muito sentido, obrigado

Em 8 de março de 2019, às 15:27, Dan Abramov [email protected] escreveu:

Acho que eu e @asylejmani estamos na mesma página aqui, mas acho que o que você está dizendo é que @gaearon é que provavelmente estamos errados em apenas executar o efeito na montagem se realmente tivermos dependências. Essa é uma declaração justa?

sim. Se o seu componente não lida com atualizações para um prop, geralmente é bugado. O design de useEffect força você a enfrentá-lo. É claro que você pode contornar isso, mas o padrão é cutucá-lo para lidar com esses casos. Este comentário explica bem: # 14920 (comentário).

Ambas as dependências vêm do redux. Os dados (neste caso) devem ser carregados apenas uma vez, e o criador da ação é sempre o mesmo.

Se for o mesmo, incluí-lo nas dependências não afetará você. Quero enfatizar isso - se você tem certeza de que suas dependências nunca mudam, não há mal nenhum em listá-las. No entanto, se mais tarde acontecer que eles mudem (por exemplo, se um componente pai passar uma função diferente dependendo do estado), seu componente irá lidar com isso corretamente.

Ainda está se perguntando se fornecer um array vazio talvez deva apenas desabilitar a regra?

Não. Fornecer um array vazio e depois se perguntar por que alguns adereços ou estado estão obsoletos é literalmente o erro mais comum.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub ou ignore a conversa.

@aweary

Isso faria com que o efeito colateral ocorresse antes da atualização real ser confirmada, o que pode fazer com que o efeito colateral seja acionado com mais freqüência do que você deseja.

Não tenho certeza do que isso significa. você pode dar um exemplo?

Passamos por cima deles com @threepointone hoje. Aqui está um resumo:

Corrigido na regra Lint

Dependências externas de useEffect

A regra não impede mais você de adicionar dependências "estranhas" a useEffect uma vez que existem cenários legítimos.

Funções no mesmo componente, mas definidas fora do efeito

O Linter não avisa para casos em que é seguro agora, mas em todos os outros casos, ele oferece sugestões melhores (como mover a função dentro do efeito ou envolvê-la com useCallback ).

Vale a pena corrigir no código do usuário

Reiniciando o estado na mudança de adereços

Isso não produz mais violações de lint, mas a maneira idiomática de redefinir o estado em resposta aos adereços é diferente . Esta solução terá um render inconsistente extra, então não tenho certeza se é desejável.

"Meu valor não funcional é constante"

Os ganchos o empurram para a correção sempre que possível. Se você especificar as dependências (que em alguns casos você pode omitir), é altamente recomendável para incluir mesmo os que você acha que não vai mudar. Sim, neste useDebounce exemplo, é improvável que o atraso mude. Mas ainda é um bug se isso acontecer, mas o Gancho não consegue lidar com isso. Isso também aparece em outros cenários. (Por exemplo, os ganchos são muito mais compatíveis com recarregamento a quente porque cada valor é tratado como dinâmico.)

Se você absolutamente insiste que um determinado valor é estático, você pode aplicá-lo.
A maneira mais segura é fazer isso explicitamente em sua API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Então, claramente não pode mudar a menos que você coloque dentro do render. (O que não seria o uso idiomático do seu Gancho.) Mas dizer que <Slider min={50} /> nunca pode mudar não é realmente válido - alguém poderia facilmente mudá-lo para <Slider min={state ? 50 : 100} /> . Na verdade, alguém poderia fazer isso:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Se alguém mudar de estado isCelsius , um componente assumindo que min nunca muda não será atualizado. Não é óbvio neste caso que Slider será o mesmo (mas será porque tem a mesma posição na árvore). Portanto, este é um tiro certeiro em termos de fazer alterações no código. Um ponto principal do React é que as atualizações são renderizadas exatamente como os estados iniciais (normalmente você não pode dizer qual é qual). Quer você renderize o valor de proposta B ou vá do valor de proposta A para B - ele deve ter a mesma aparência e comportamento.

Embora isso seja desaconselhável, em alguns casos, o mecanismo de aplicação pode ser um Gancho que avisa quando o valor muda (mas fornece o primeiro). Pelo menos então é mais provável que seja notado.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

Também pode haver um caso legítimo em que você simplesmente não consegue lidar com uma atualização. Por exemplo, se a API de nível inferior não o suportasse, como um plugin jQuery ou uma API DOM. Nesse caso, o aviso ainda é apropriado para que o consumidor do seu componente o entenda. Alternativamente, você pode fazer um componente de invólucro que redefina key em atualizações incompatíveis - forçando uma remontagem limpa com novos acessórios. Isso provavelmente é preferível para componentes folha, como controles deslizantes ou caixas de seleção.

"O valor da minha função é constante"

Em primeiro lugar, se for constante e elevado ao escopo de nível superior, o linter não reclamará. Mas isso não ajuda com coisas que vêm de adereços ou contexto.

Se ele realmente é constante, em seguida, especificando-a no deps não dói. Como o caso em que uma função setState dentro de um Gancho personalizado é retornada ao seu componente e, em seguida, você a chama a partir de um efeito. A regra do lint não é inteligente o suficiente para entender indireções como essa. Mas, por outro lado, qualquer um pode quebrar esse retorno de chamada mais tarde, antes de retornar, e possivelmente fazer referência a outro prop ou estado dentro dele. Então não será constante! E se você falhar em lidar com essas mudanças, você terá bugs de propriedade / estado obsoletos. Portanto, especificá-lo é um padrão melhor.

No entanto, é um equívoco pensar que os valores das funções são necessariamente constantes. Eles são mais frequentemente constantes nas classes devido à associação de métodos, embora isso crie sua própria gama de bugs . Mas, em geral, qualquer função que fecha sobre um valor em um componente de função não pode ser considerada constante. A regra do lint agora é mais inteligente sobre o que fazer. (Como movê-lo dentro do efeito - a correção mais simples - ou envolvê-lo com useCallback .)

Há um problema no espectro oposto disso, que é onde você obtém loops infinitos (um valor de função sempre muda). Percebemos isso na regra do lint agora, quando possível (no mesmo componente) e sugerimos uma correção. Mas é complicado se você passar algo vários níveis abaixo.

Você ainda pode envolvê-lo em useCallback para corrigir o problema. Lembre-se de que tecnicamente é válido que uma função mude , e você não pode ignorar este caso sem correr o risco de bugs. Como onChange={shouldHandle ? handleChange : null} ou renderizando foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> no mesmo local. Ou mesmo fetchComments que fecha sobre o estado do componente pai. Isso pode mudar. Com as classes, seu comportamento mudará silenciosamente, mas a referência de função permanecerá a mesma. Portanto, seu filho sentirá falta dessa atualização - você realmente não tem a opção de passar mais dados para ele. Com os componentes da função e useCallback , a própria identidade da função muda - mas apenas quando necessário. Portanto, essa é uma propriedade útil e não apenas um obstáculo a ser evitado.

Devemos adicionar uma solução melhor para detectar loops assíncronos infinitos. Isso deve atenuar o aspecto mais confuso. Podemos adicionar alguma detecção para isso no futuro. Você também pode escrever algo assim:

useWarnAboutTooFrequentChanges([deps]);

Isso não é o ideal e precisaremos pensar mais em como lidar com isso de maneira elegante. Eu concordo que casos como esse são bem desagradáveis. A correção sem quebrar a regra seria tornar rules estático, por exemplo, alterando a API para createTextInput(rules) e envolvendo register e unregister em useCallback . Melhor ainda, remova register e unregister e substitua-os por um contexto separado onde você coloca dispatch sozinho. Então você pode garantir que nunca terá uma identidade de função diferente da leitura.

Eu acrescentaria que você provavelmente deseja colocar useMemo no valor de contexto de qualquer maneira, porque o provedor faz muitos cálculos que seria triste repetir se nenhum novo componente fosse registrado, mas seu próprio pai atualizado. Portanto, este tipo de problema coloca o problema que você talvez não tenha notado de outra forma mais visível. Embora eu concorde que precisamos tornar isso ainda mais proeminente quando isso acontecer.

Ignorar as dependências de função completamente leva a erros piores com componentes de função e ganchos porque eles continuariam vendo adereços e estados obsoletos se você fizesse isso. Portanto, tente não fazê-lo, quando puder.

Reagindo às Mudanças de Valor Composto

É estranho para mim por que este exemplo usa um efeito para algo que é essencialmente um manipulador de eventos. Fazer o mesmo "log" (suponho que poderia ser um envio de formulário) em um manipulador de eventos parece mais adequado. Isso é especialmente verdadeiro se pensarmos sobre o que acontece quando o componente é desmontado. E se ele desmontar logo após o efeito ser programado? Coisas como o envio de formulários não deveriam simplesmente "não acontecer" nesse caso. Portanto, parece que o efeito pode ser uma escolha errada.

Dito isso, você ainda pode fazer o que tentou - tornando fullName setSubmittedData({firstName, lastName}) , e então [submittedData] é sua dependência, da qual você pode ler firstName e lastName .

Integrando com código imperativo / legado

Ao integrar com coisas imperativas como plug-ins jQuery ou APIs DOM brutas, algumas coisas desagradáveis ​​podem ser esperadas. Dito isso, ainda espero que você consiga consolidar os efeitos um pouco mais nesse exemplo.


Espero não ter esquecido de ninguém! Avise-me se sim ou se algo não estiver claro. Tentaremos transformar as lições disso em alguns documentos em breve.

@gaearon , obrigado por (# 14920 (comentário) por @trevorgithub) em seu comentário resumido de fechamento. (Eu certamente aprecio que tenha havido muito feedback de muitas pessoas; acho que meu comentário original se perdeu na seção de itens ocultos em algum lugar no meio dos comentários sobre o problema).

Estou assumindo que minha amostra se enquadraria em 'Integrando com código imperativo / legado', embora talvez também haja outras categorias?

No caso de problemas de 'Integração com código imperativo / legado', parece que pode não haver muito que possa ser feito. Nesses casos, como ignorar esse aviso? Eu acho:
// eslint-disable-line react-hooks/exhaustive-deps

Desculpe, eu perdi este.

Se você receber alguns dados por meio de adereços, mas não quiser usar esses adereços até alguma mudança explícita, parece que uma maneira correta de modelá-lo seria ter um estado derivado.

Você está pensando nisso como “Eu quero ignorar uma mudança em um suporte até outro suporte”. Mas você também pode pensar nisso como “Meu componente tem função de busca no estado. É atualizado a partir de um adereço quando outro adereço muda. ”

O estado geralmente derivado não é recomendado, mas aqui parece o que você deseja. A maneira mais simples de implementar isso seria algo como:

const [currentGetData, setCurrentGetData] = useState(getData);
const [prevRefreshRequest, setPrevRefreshRequest] = useState(refreshRequest);

if (prevRefreshRequest !== refreshRequest) {
  setPrevRefreshRequest(refreshRequest);
  setCurrentGetData(getData);
}

useEffect(() => {
  currentGetData(someId);
}, [currentGetData, someId]);

Você também precisaria colocar useCallback torno de getData que está passando.

Observe que, em geral, o padrão de transmissão de funções assíncronas para busca parece obscuro para mim. Eu acho que no seu caso você usa Redux, então isso faz sentido. Mas se a função assíncrona foi definida em um componente pai, seria suspeito porque você provavelmente teria condições de corrida. Seus efeitos não têm uma limpeza, então como você pode saber quando o usuário escolhe uma ID diferente? Existe o risco de as solicitações chegarem fora de serviço e configurarem o estado incorreto. Então, isso é apenas algo para se manter em mente. Se a busca de dados estiver no próprio componente, você pode ter uma função de limpeza de efeito que define um sinalizador “ignorar” para evitar um setState da resposta. (Claro que mover dados para um cache externo costuma ser uma solução melhor - é assim que o Suspense também funcionará.)

Fazer o mesmo "log" (suponho que poderia ser um envio de formulário) em um manipulador de eventos parece mais adequado.

@gaearon, o problema que vejo é que fazer isso no manipulador de eventos significa que o efeito colateral ocorre antes que a atualização seja confirmada. Não há garantia estrita de que o componente será renderizado novamente com êxito como resultado desse evento, portanto, fazê-lo no manipulador de eventos pode ser prematuro.

Por exemplo, se eu quiser registrar que o usuário enviou com êxito uma nova consulta de pesquisa e está visualizando os resultados. Se algo der errado e o componente for acionado, eu não gostaria que esse evento de log ocorresse.

Então, há o caso em que esse efeito colateral pode ser assíncrono, então usar useEffect fornece a função de limpeza.

Também há o problema de useReducer , em que o valor que devo registrar não estará disponível no manipulador de eventos. Mas acho que isso já está no radar de vocês 🙂

Em qualquer um dos casos, a abordagem que você recomenda é provavelmente suficiente. Armazene o estado composto em um formulário onde você ainda possa acessar os valores individuais que ele compõe.

Eu tenho um gancho prático para agrupar funções com parâmetros extras e transmiti-los. Se parece com isso:

function useBoundCallback(fn, ...bound) {
  return useCallback((...args) => fn(...bound, ...args), [fn, ...bound]);
}

(na verdade é um pouco mais complicado porque tem alguns recursos extras, mas o acima ainda mostra o problema relevante).

O caso de uso é quando um componente precisa passar uma propriedade para um filho e deseja que o filho tenha uma maneira de modificar essa propriedade específica. Por exemplo, considere uma lista onde cada item tem uma opção de edição. O objeto pai passa um valor para cada filho e uma função de retorno de chamada para invocar se desejar editar o valor. O filho não sabe qual ID do item está mostrando, portanto, o pai deve modificar o retorno de chamada para incluir este parâmetro. Isso pode ser aninhado em uma profundidade arbitrária.

Um exemplo simplificado desse fluxo é mostrado aqui: https://codesandbox.io/s/vvv36834k5 (clicar em "Ir!" Mostra um log do console que inclui o caminho dos componentes)


O problema é que recebo 2 erros de linter com esta regra:

O React Hook (X) tem uma dependência ausente: 'ligado'. Inclua ou remova a matriz de dependência

React Hook (X) possui um elemento spread em sua matriz de dependência. Isso significa que não podemos verificar estaticamente se você passou nas dependências corretas

Alterá-lo para usar uma lista de parâmetros (ou seja, não usar o operador de propagação) destruiria o memoisation, porque a lista é criada com cada invocação, mesmo se os parâmetros forem idênticos.


Pensamentos:

  • vale a pena mostrar o primeiro erro quando o segundo também se aplica?
  • Existe alguma maneira de desabilitar essa regra especificamente para locais onde o operador de propagação é usado?
  • se o único uso de uma variável dentro de uma função for com o operador spread, é seguro usar o operador spread nas dependências e, como a regra já está detectando isso, certamente deve permitir isso. Sei que há casos mais complexos que são mais difíceis de resolver com a análise estática, mas isso parece uma vitória fácil para um uso relativamente comum de spread.

Olá @gaearon , Acabei de receber este aviso, que não encontrei discutido em nenhum lugar:

Accessing 'myRef.current' during the effect cleanup will likely read a different ref value because by this time React has already updated the ref. If this ref is managed by React, store 'myRef.current' in a variable inside the effect itself and refer to that variable from the cleanup function.

Acho esta mensagem um pouco confusa. Eu acho que ele tenta me avisar que o valor atual ref na limpeza pode ser diferente do valor no corpo do efeito. Direito?
Se for esse o caso, e estando ciente disso, é seguro / legítimo ignorar este aviso?

Meu caso, se interessante: CodeSandbox
Contexto: algum gancho de busca de dados personalizados. Eu uso um contador em um ref para evitar condições de corrida e atualizações em componentes não montados.

Acho que poderia contornar esse aviso ocultando o ref lido dentro de uma função ou criando outro ref booleano para o caso de limpeza. Mas acho desnecessariamente prolixo se puder simplesmente ignorar esse aviso.

@aweary

Por exemplo, se eu quiser registrar que o usuário enviou com êxito uma nova consulta de pesquisa e está visualizando os resultados. Se algo der errado e o componente for acionado, eu não gostaria que esse evento de log ocorresse.

Sim, parece um caso de uso de nicho. Acho que quando a maioria das pessoas deseja "efeitos colaterais", eles se referem ao envio do formulário em si - não ao fato de você ter visualizado um formulário enviado. Nesse caso, a solução que forneci parece boa.

@ davidje13

vale a pena mostrar o primeiro erro quando o segundo também se aplica?

Registre um novo problema para propostas de alteração da regra de lint.

Existe alguma maneira de desabilitar essa regra especificamente para locais onde o operador de propagação é usado?

Você sempre pode // eslint-disable-next-line react-hooks/exhaustive-deps se achar que sabe o que está fazendo.

se o único uso de uma variável dentro de uma função for com o operador spread, é seguro usar o operador spread nas dependências e, como a regra já está detectando isso, certamente deve permitir isso.

Arquive um novo problema, por favor.

@CarlosGines

Acho esta mensagem um pouco confusa. Eu acho que ele tenta me avisar que o valor atual ref na limpeza pode ser diferente do valor no corpo do efeito. Direito?

sim.

Se for esse o caso, e estando ciente disso, é seguro / legítimo ignorar este aviso?

Ummm ... não se isso levar a um bug. 🙂

Contexto: algum gancho de busca de dados personalizados. Eu uso um contador em um ref para evitar condições de corrida e atualizações em componentes não montados.

Sim, talvez este caso de uso seja legítimo. Arquive um novo problema para discutir, por favor?

Vou bloquear esse problema, pois temos feedback suficiente e ele foi incorporado.

Perguntas e respostas comuns: https://github.com/facebook/react/issues/14920#issuecomment -471070149

Se você quiser um mergulho mais profundo em useEffect e dependências, está aqui: https://overreacted.io/a-complete-guide-to-useeffect/

Estaremos adicionando mais coisas aos documentos em breve.

Se você quiser alterar algo na regra ou não tiver certeza de que seu caso é legítimo, registre uma nova questão.

Esta página foi útil?
0 / 5 - 0 avaliações