使用 useEffect,如何跳过在初始渲染时应用效果?
With useEffect, how can I skip applying an effect upon the initial render?
使用 React 的新 Effect Hooks,如果某些值在重新渲染之间没有改变,我可以告诉 React 跳过应用效果 - 来自 React 文档的示例:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
但上面的示例在初始渲染时应用效果,并在 count
已更改的后续重新渲染时应用效果。我如何告诉 React 跳过初始渲染的效果?
如指南所述,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
在本指南的示例中,预期 count
仅在初始渲染时为 0:
const [count, setCount] = useState(0);
因此它将作为 componentDidUpdate
进行额外检查:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
这基本上就是可以用来代替 useEffect
的自定义挂钩的工作方式:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
return fn();
}
didMountRef.current = true;
}, inputs);
}
感谢@Tholle 建议 useRef
而不是 setState
。
我使用常规状态变量而不是引用。
// 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])
我们可以像这样将 didMount 逻辑重构为自定义挂钩。
function useDidMount() {
const [didMount, setDidMount] = useState(false)
useEffect(() => { setDidMount(true) }, [])
return didMount
}
最后,我们可以像这样在我们的组件中使用它了。
const didMount = useDidMount()
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
更新 使用 useRef 钩子避免额外的重新渲染(感谢@TomEsterez 的建议)
这次我们的自定义挂钩 returns 一个函数返回我们 ref 的当前值。你也可以直接使用 ref,但我更喜欢这个。
function useDidMount() {
const mountRef = useRef(false);
useEffect(() => { mountRef.current = true }, []);
return () => mountRef.current;
}
用法
const MyComponent = () => {
const didMount = useDidMount();
useEffect(() => {
if (didMount()) // do something
else // do something else
})
return (
<div>something</div>
);
}
附带说明一下,我从来没有用过这个钩子,可能有更好的方法来处理这个问题,这将更符合 React 编程模型。
这是一个自定义挂钩,它只提供一个布尔标志来指示当前渲染是否是第一个渲染(当组件被挂载时)。它与其他一些答案大致相同,但您可以在 useEffect
或渲染函数或您想要的组件中的任何其他地方使用标志。也许有人可以提出一个更好的名字。
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
您可以像这样使用它:
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>;
};
如果您有兴趣,这里有一个测试:
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);
});
});
我们的用例是在初始道具指示应隐藏动画元素时隐藏它们。如果道具发生变化,在以后的渲染中,我们确实希望元素动画化。
我找到了一个更简单的解决方案,不需要使用另一个钩子,但它有缺点。
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
这只是一个示例,如果您的情况非常简单,还有其他方法可以做到这一点。
这样做的缺点是不能起到清理作用,只有在依赖数组第二次变化时才会执行
不建议使用,您应该使用其他答案所说的内容,但我只是在此处添加此内容,以便人们知道有不止一种方法可以做到这一点。
编辑:
只是为了说得更清楚,你不应该使用这种方法来解决问题中的问题(跳过初始渲染),这仅用于教学目的表明你可以用不同的方式做同样的事情。
如果您需要跳过初始渲染,请使用其他答案中的方法。
一个 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)
}
下面的解决方案与上面的类似,只是我更喜欢更简洁的方式。
const [isMount, setIsMount] = useState(true);
useEffect(()=>{
if(isMount){
setIsMount(false);
return;
}
//Do anything here for 2nd render onwards
}, [args])
这是我基于 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);
}
现场演示
下面的现场演示演示了 useEffect
和 useDidUpdateEffect
钩子之间的区别
我本来打算对当前接受的答案发表评论,但 运行 出 space!
首先,重要的是在使用功能组件时不要考虑生命周期事件。考虑 prop/state 变化。我有一个类似的情况,我只希望在特定道具(parentValue
在我的例子中)从其初始状态发生变化时触发特定的 useEffect
函数。所以,我创建了一个基于其初始值的 ref:
const parentValueRef = useRef(parentValue);
然后在 useEffect
fn 的开头包含以下内容:
if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;
(基本不用运行如果parentValue
没变的效果,有变化就更新ref,准备下一次检查,继续运行效果)
因此,尽管建议的其他解决方案将解决您提供的特定用例,但从长远来看,它将有助于 运行 改变您对功能组件的看法。
将它们视为主要基于某些道具渲染组件。
如果您确实需要一些本地状态,那么 useState
会提供,但不要假设您的问题将通过存储本地状态来解决。
如果你有一些代码会在渲染过程中改变你的道具,这个 'side-effect' 需要包装在 useEffect
中,但这样做的目的是有一个干净的渲染在渲染时受到某些变化的影响。渲染完成后 useEffect
挂钩将是 运行,正如您所指出的,每次渲染都是 运行 - 除非第二个参数用于提供 运行 列表=42=] 来确定哪些更改的项目将导致它成为 运行 随后的时间。
祝你在函数式组件/Hooks 之旅中好运!有时有必要忘记一些东西来掌握一种新的做事方式:)
这是一本优秀的入门书:https://overreacted.io/a-complete-guide-to-useeffect/
给大家介绍一下react-use.
npm install react-use
想要运行:
仅在第一次渲染后? ------> useUpdateEffect
只有一次? ------> useEffectOnce
检查是否先挂载? ------> useFirstMountState
想要运行效果与深度比较、浅层比较或节流?还有更多 here.
不想安装库?检查 code 并复制。 (也许 star
也适合那里的好人)
最好的事情是 您需要维护的东西少了一件。
使用 React 的新 Effect Hooks,如果某些值在重新渲染之间没有改变,我可以告诉 React 跳过应用效果 - 来自 React 文档的示例:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
但上面的示例在初始渲染时应用效果,并在 count
已更改的后续重新渲染时应用效果。我如何告诉 React 跳过初始渲染的效果?
如指南所述,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
在本指南的示例中,预期 count
仅在初始渲染时为 0:
const [count, setCount] = useState(0);
因此它将作为 componentDidUpdate
进行额外检查:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
这基本上就是可以用来代替 useEffect
的自定义挂钩的工作方式:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
return fn();
}
didMountRef.current = true;
}, inputs);
}
感谢@Tholle 建议 useRef
而不是 setState
。
我使用常规状态变量而不是引用。
// 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])
我们可以像这样将 didMount 逻辑重构为自定义挂钩。
function useDidMount() {
const [didMount, setDidMount] = useState(false)
useEffect(() => { setDidMount(true) }, [])
return didMount
}
最后,我们可以像这样在我们的组件中使用它了。
const didMount = useDidMount()
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
更新 使用 useRef 钩子避免额外的重新渲染(感谢@TomEsterez 的建议)
这次我们的自定义挂钩 returns 一个函数返回我们 ref 的当前值。你也可以直接使用 ref,但我更喜欢这个。
function useDidMount() {
const mountRef = useRef(false);
useEffect(() => { mountRef.current = true }, []);
return () => mountRef.current;
}
用法
const MyComponent = () => {
const didMount = useDidMount();
useEffect(() => {
if (didMount()) // do something
else // do something else
})
return (
<div>something</div>
);
}
附带说明一下,我从来没有用过这个钩子,可能有更好的方法来处理这个问题,这将更符合 React 编程模型。
这是一个自定义挂钩,它只提供一个布尔标志来指示当前渲染是否是第一个渲染(当组件被挂载时)。它与其他一些答案大致相同,但您可以在 useEffect
或渲染函数或您想要的组件中的任何其他地方使用标志。也许有人可以提出一个更好的名字。
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
您可以像这样使用它:
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>;
};
如果您有兴趣,这里有一个测试:
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);
});
});
我们的用例是在初始道具指示应隐藏动画元素时隐藏它们。如果道具发生变化,在以后的渲染中,我们确实希望元素动画化。
我找到了一个更简单的解决方案,不需要使用另一个钩子,但它有缺点。
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
这只是一个示例,如果您的情况非常简单,还有其他方法可以做到这一点。
这样做的缺点是不能起到清理作用,只有在依赖数组第二次变化时才会执行
不建议使用,您应该使用其他答案所说的内容,但我只是在此处添加此内容,以便人们知道有不止一种方法可以做到这一点。
编辑:
只是为了说得更清楚,你不应该使用这种方法来解决问题中的问题(跳过初始渲染),这仅用于教学目的表明你可以用不同的方式做同样的事情。 如果您需要跳过初始渲染,请使用其他答案中的方法。
一个 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)
}
下面的解决方案与上面的类似,只是我更喜欢更简洁的方式。
const [isMount, setIsMount] = useState(true);
useEffect(()=>{
if(isMount){
setIsMount(false);
return;
}
//Do anything here for 2nd render onwards
}, [args])
这是我基于 Estus Flask 的
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);
}
现场演示
下面的现场演示演示了 useEffect
和 useDidUpdateEffect
钩子之间的区别
我本来打算对当前接受的答案发表评论,但 运行 出 space!
首先,重要的是在使用功能组件时不要考虑生命周期事件。考虑 prop/state 变化。我有一个类似的情况,我只希望在特定道具(parentValue
在我的例子中)从其初始状态发生变化时触发特定的 useEffect
函数。所以,我创建了一个基于其初始值的 ref:
const parentValueRef = useRef(parentValue);
然后在 useEffect
fn 的开头包含以下内容:
if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;
(基本不用运行如果parentValue
没变的效果,有变化就更新ref,准备下一次检查,继续运行效果)
因此,尽管建议的其他解决方案将解决您提供的特定用例,但从长远来看,它将有助于 运行 改变您对功能组件的看法。
将它们视为主要基于某些道具渲染组件。
如果您确实需要一些本地状态,那么 useState
会提供,但不要假设您的问题将通过存储本地状态来解决。
如果你有一些代码会在渲染过程中改变你的道具,这个 'side-effect' 需要包装在 useEffect
中,但这样做的目的是有一个干净的渲染在渲染时受到某些变化的影响。渲染完成后 useEffect
挂钩将是 运行,正如您所指出的,每次渲染都是 运行 - 除非第二个参数用于提供 运行 列表=42=] 来确定哪些更改的项目将导致它成为 运行 随后的时间。
祝你在函数式组件/Hooks 之旅中好运!有时有必要忘记一些东西来掌握一种新的做事方式:) 这是一本优秀的入门书:https://overreacted.io/a-complete-guide-to-useeffect/
给大家介绍一下react-use.
npm install react-use
想要运行:
仅在第一次渲染后? ------> useUpdateEffect
只有一次? ------> useEffectOnce
检查是否先挂载? ------> useFirstMountState
想要运行效果与深度比较、浅层比较或节流?还有更多 here.
不想安装库?检查 code 并复制。 (也许 star
也适合那里的好人)
最好的事情是 您需要维护的东西少了一件。