使用useEffect,如何跳过对初始渲染应用效果?

utu*_*rnr 28 reactjs react-hooks

使用React的新效果钩子,如果重新渲染之间某些值没有改变,我可以告诉React跳过应用效果 - 来自React的文档示例:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
Run Code Online (Sandbox Code Playgroud)

但是上面的例子应用了初始渲染时的效果,以及后续重新渲染的位置count.如何告诉React跳过初始渲染的效果?

Sco*_*ner 57

这是一个自定义钩子,它只提供一个布尔标志来指示当前渲染是否是第一个渲染(安装组件时)。它与其他一些答案大致相同,但您可以在 auseEffect或 render 函数或您想要的组件中的任何其他地方使用该标志。也许有人可以提出一个更好的名字。

import { useRef, useEffect } from 'react';

export const useIsMount = () => {
  const isMountRef = useRef(true);
  useEffect(() => {
    isMountRef.current = false;
  }, []);
  return isMountRef.current;
};
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

import React, { useEffect } from 'react';

import { useIsMount } from './useIsMount';

const MyComponent = () => {
  const isMount = useIsMount();

  useEffect(() => {
    if (isMount) {
      console.log('First Render');
    } else {
      console.log('Subsequent Render');
    }
  });

  return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};
Run Code Online (Sandbox Code Playgroud)

如果您有兴趣,这里有一个测试:

import { renderHook } from '@testing-library/react-hooks';

import { useIsMount } from '../useIsMount';

describe('useIsMount', () => {
  it('should be true on first render and false after', () => {
    const { result, rerender } = renderHook(() => useIsMount());
    expect(result.current).toEqual(true);
    rerender();
    expect(result.current).toEqual(false);
    rerender();
    expect(result.current).toEqual(false);
  });
});
Run Code Online (Sandbox Code Playgroud)

我们的用例是隐藏动画元素,如果初始道具表明它们应该被隐藏。如果道具发生变化,在稍后的渲染中,我们确实希望元素具有动画效果。

  • 我猜我在想当前渲染是否是发生安装的渲染。是的,我同意,现在回到这个话题听起来有点奇怪。但第一次渲染时是正确的,之后是错误的,所以你的建议听起来有误导性。`isFirstRender` 可以工作。 (4认同)
  • 谢谢你的钩子!我同意@ScottyWaggoner,“isFirstRender”是一个更好的名字 (3认同)
  • 为什么选择“isMount”而不是“didMount”或“isMount”? (2认同)

Est*_*ask 52

正如指南所述,

Effect Hook,useEffect增加了从功能组件执行副作用的功能.它与React类中的componentDidMount,componentDidUpdate和componentWillUnmount具有相同的用途,但统一为单个API.

在本指南中的示例中,预期count仅在初始渲染时为0:

const [count, setCount] = useState(0);
Run Code Online (Sandbox Code Playgroud)

所以它会像componentDidUpdate额外的检查一样工作:

useEffect(() => {
  if (count)
    document.title = `You clicked ${count} times`;
}, [count]);
Run Code Online (Sandbox Code Playgroud)

这基本上是如何使用自定义钩子而不是useEffect可以工作:

function useDidUpdateEffect(fn, inputs) {
  const didMountRef = useRef(false);

  useEffect(() => {
    if (didMountRef.current)
      fn();
    else
      didMountRef.current = true;
  }, inputs);
}
Run Code Online (Sandbox Code Playgroud)

积分转到@Tholle建议useRef代替setState.

  • @JustinLang React linter 规则!=最佳实践,它们只尝试解决常见的钩子问题。ESLint 规则并不智能,可能会导致误报或漏报。只要钩子背后的逻辑是正确的,就可以使用 eslint-disable 或 eslint-disable-next 注释安全地忽略规则。在这种情况下,不应提供“fn”作为输入。请参阅解释,https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependency。如果“fn”内部的某些内容引入了依赖项,则更像是应该将它们直接作为“输入”提供。 (9认同)
  • @ rob-gordon这是在更新答案后删除的注释。原因是useState woukld导致不必要的组件更新。 (4认同)
  • `useRef` 对 `setState` 的建议在哪里?我在此页面上没有看到它,我想了解原因。 (3认同)
  • 这种方法有效,但它违反了react-hooks/exhaustive-deps linter 规则。即 deps 数组中未给出 fn 。有人有一种不违反 React 最佳实践的方法吗? (2认同)
  • **注意**:如果您使用多个 useEffects 来检查 didMountRef,请确保只有最后一个(底部)将 didMountRef 设置为 false。React 按顺序遍历 useEffects! (2认同)

cYe*_*Yee 29

让我向您介绍一下react-use

npm install react-use

想跑:

仅在第一次渲染后?-------->useUpdateEffect

只有一次?--------> useEffectOnce

检查它是第一次安装吗?-------->useFirstMountState

想要运行深比较浅比较节流效果吗?还有更多这里

不想安装库?检查代码并复制。(也许star也适合那里的好人)

最好的事情就是少一件你需要维护的事情。

  • 看起来真的是一个很好的包 (4认同)
  • 很棒的资源,很高兴我找到了这个! (2认同)

Ven*_*sky 26

我找到了一个更简单的解决方案,不需要使用另一个钩子,但它有缺点。

useEffect(() => {
  // skip initial render
  return () => {
    // do something with dependency
  }
}, [dependency])
Run Code Online (Sandbox Code Playgroud)

这只是一个例子,如果您的案例非常简单,还有其他方法可以做到。

这样做的缺点是不能有清理效果,只有在依赖数组第二次改变时才会执行。

不建议使用此方法,您应该使用其他答案所说的内容,但我只在此处添加了此内容,以便人们知道这样做的方法不止一种。

编辑:

只是为了更清楚,您不应该使用这种方法来解决问题中的问题(跳过初始渲染),这仅用于教学目的,表明您可以用不同的方式做同样的事情。如果您需要跳过初始渲染,请使用其他答案的方法。

  • 我刚刚学到了一些东西。我认为这行不通,然后我尝试了,结果确实行得通。谢谢你! (2认同)
  • React 应该为此提供更好的方法,但这个问题在 Github 上开放,建议是自己编写一个自定义解决方案(这完全是无意义的)。 (2认同)

Los*_*Don 8

一个 TypeScript 和 CRA 友好的钩子,用 替换它useEffect,这个钩子的工作原理类似,useEffect但不会在第一次渲染发生时被触发。

import * as React from 'react'

export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
  const initializeRef = React.useRef<boolean>(false)

  React.useEffect((...args) => {
    if (initializeRef.current) {
      cb(...args)
    } else {
      initializeRef.current = true
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, dep)
}
Run Code Online (Sandbox Code Playgroud)


Ami*_*k88 7

我使用常规状态变量而不是引用。

// Initializing didMount as false
const [didMount, setDidMount] = useState(false)

// Setting didMount to true upon mounting
useEffect(() => setDidMount(true), [])

// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
  if (didMount) document.title = `You clicked ${count} times`
}, [count])
Run Code Online (Sandbox Code Playgroud)

我们可以像这样将didMount逻辑重构为自定义钩子。

function useDidMount() {
  const [didMount, setDidMount] = useState(false)
  useEffect(() => setDidMount(true), [])

  return didMount
}
Run Code Online (Sandbox Code Playgroud)

最后,我们可以像这样在组件中使用它。

const didMount = useDidMount()

const [count, setCount] = useState(0)
useEffect(() => {
  if (didMount) document.title = `You clicked ${count} times`
}, [count])
Run Code Online (Sandbox Code Playgroud)

  • `useRef`更适合于此,因为`useState`将导致该组件的额外且无用的呈现:https://codesandbox.io/embed/youthful-goldberg-pz3cx (2认同)

Nea*_*arl 5

这是我的实现,基于 Estus Flask用 Typescript 编写的答案。它还支持清理回调。

import { DependencyList, EffectCallback, useEffect, useRef } from 'react';

export function useDidUpdateEffect(
  effect: EffectCallback,
  deps?: DependencyList
) {
  // a flag to check if the component did mount (first render's passed)
  // it's unrelated to the rendering process so we don't useState here
  const didMountRef = useRef(false);

  // effect callback runs when the dependency array changes, it also runs
  // after the component mounted for the first time.
  useEffect(() => {
    // if so, mark the component as mounted and skip the first effect call
    if (!didMountRef.current) {
      didMountRef.current = true;
    } else {
      // subsequent useEffect callback invocations will execute the effect as normal
      return effect();
    }
  }, deps);
}
Run Code Online (Sandbox Code Playgroud)

现场演示

useEffect下面的现场演示演示了 hooks和useDidUpdateEffecthooks之间的区别

编辑53179075/with-useeffect-how-can-i-skip-applying-an-effect-upon-the-initial-render