即使存在依赖关系,如何运行一次 useEffect ?为啥 ESLint 抱怨它?
Posted
技术标签:
【中文标题】即使存在依赖关系,如何运行一次 useEffect ?为啥 ESLint 抱怨它?【英文标题】:How to run useEffect once even if there're dependencies? And why ESLint is complaining about it?即使存在依赖关系,如何运行一次 useEffect ?为什么 ESLint 抱怨它? 【发布时间】:2020-11-06 14:55:09 【问题描述】:考虑以下示例:
const userRole = sessionStorage.getItem('role');
const data, setData, type, setTableType = useTable([]);
useEffect(() =>
const getData = async () =>
// fetch some data from API
const fetchedData = await axios('..');
if (userRole === 'admin')
setData([...fetchedData, orders: [] ]);
else
setData(fetchedData);
if (type === '1')
setTableType('normal');
;
getData();
, []);
我只想在安装时运行此效果,仅此而已,我不在乎 userRole
或 setData
是否已更改!
所以,现在我的问题是:
-
为什么 ESLint 抱怨
userRole
在
依赖数组?它甚至不是一个状态!
为什么是 ESLint
抱怨依赖数组中缺少 setData
是不是总是相同的引用还是会改变?
如何在不破坏 linter 的情况下实现我想要的?还是我应该直接打旧的// eslint-disable-line react-hooks/exhaustive-deps
线?或者这绝对是野蛮的?
为什么这表明我的代码中有错误?我只想运行这个效果
用最初可用的任何东西一次。
编辑:
让我重新表述这个问题,如果这些变量发生变化而我不关心它们的新值怎么办?特别是当type
在同一文件的不同位置更新时。我只想读取初始值并运行 useEffect 一次如何实现?
react-hooks/exhaustive-deps 警告
【问题讨论】:
如果userRole
发生变化,那数据会不会不正确?
@RossAllen userRole
存储在 sessionStorage 中,除非用户注销并使用不同的帐户类型登录,否则它将始终相同
如果这些事情不随时间改变,那么useEffect
只会被调用一次。如果这些事情确实发生了变化,那么您的代码在逻辑上将是不正确的,并会导致细微的错误。我建议遵循 lint 规则;它在这里为您提供帮助并防止潜在的潜在错误。
您已经在 3. 中回答了这个问题,对吧? “// eslint-disable-line react-hooks/exhaustive-deps”。这些规则正确地识别出您的代码中的逻辑是不正确的。忽略此警告意味着您将来可能会忽略它,并且当这些值更改时,您的代码将不正确。您知道它们不会改变的事实很好;那么useEffect
只会像你想要的那样运行一次。如果您将 vals 添加到数组中,那么您的代码在逻辑上也将是正确的。
@RossAllen 没错!即使这些变量中的任何一个发生变化,我也有意只在安装时运行这段代码一次,我用另一个变量type
更新了问题以帮助表达我的观点。每当用户单击按钮转到下一个表时,此type
就会发生变化。在这种情况下,如果我将 type
添加到依赖数组,它将被触发,这是我不想要的!能不能最后看一下帖子,告诉我你的看法?
【参考方案1】:
-
用户角色在
useEffect
中,因此它是一个依赖项(如果它会改变 - useEffect
无效)
useEffect
不知道它是否相同,这就是它要求依赖的原因
通常按照 linter 的要求执行操作,并将这两个添加到依赖项数组中。
const userRole = sessionStorage.getItem('role');
const data, setData = useTable([]);
useEffect(() =>
const getData = async () =>
// fetch some data from API
const fetchedData = await axios('..');
if (userRole === 'admin')
setData([...fetchedData, orders: [] ]);
else
setData(fetchedData);
;
getData();
, [userRole, setData]);
-
here's Dan Abramov's take on this
“但我只想在 mount 上运行它!”,你会说。现在,请记住:如果您指定 deps,则效果使用的组件内部的所有值都必须存在。包括道具、状态、函数——组件中的任何东西。
【讨论】:
感谢您的回答,但让我重新提出我的问题:如果他们确实发生了变化而我不在乎怎么办?我只想读取初始值并运行 useEffect 一次如何实现?userRole
是否在应用程序的其他任何地方使用过?
@Mohamed 将 const userRole = sessionStorage.getItem('role');
移到组件主体之外,或者做 const [userRole] = useState(sessionStorage.getItem('role'));
前者将允许您从依赖项列表中省略 userRole
,而后者仍然需要您将其包含在依赖项列表,但如果 sessionStorage 在组件的渲染之间更新,useEffect()
将不会重新运行。
@Mohamed 抱歉,误会了,你在这个组件的任何地方都使用它吗?如果没有,你可以把它放在useEffect
钩子里,就是这样。
@PatrickRoberts 实际上这将解决 userRole 问题,让我添加另一个变量来表达我的观点。【参考方案2】:
编辑:更新 React 后,Eslint 似乎开始抱怨以下解决方案,所以我可能会使用 // eslint-disable-line
如果您希望效果仅在组件挂载时运行一次,则给 useEffect
一个空的依赖项数组是正确的。 ESLint 警告您的问题是效果可能会使用陈旧数据执行,因为它引用了外部状态属性,但正如您所注意到的,为数组提供它要求的依赖项会导致效果在任何时候运行它们也会发生变化。
幸运的是,有一个令我惊讶的简单解决方案尚未被提及 - 将效果包装在 useCallback
中。您可以安全地将依赖项提供给useCallback
,而无需再次执行。
// Some state value
const [state, setState] = useState();
const init = useCallback(
() =>
// Do something which references the state
if (state === null)
,
// Pass the dependency to the array as normal
[state]
);
// Now do the effect without any dependencies
useEffect(init, []);
现在init
会在依赖发生变化时重新记忆,但除非你在其他地方调用它,否则它只会在下面的useEffect
中真正被调用执行。
回答您的具体问题:
-
ESLint 抱怨
userRole
,因为 React 组件在每次渲染时都会重新运行,这意味着 userRole
将来可能会有不同的值。您可以通过将 userRole
移到您的函数之外来避免这种情况。
与之前的情况相同,尽管setData
可能实际上始终相同,但它存在更改的可能性,这就是 ESLint 希望您将其作为依赖项包含在内的原因。由于它是自定义钩子的一部分,因此不能将它移到您的函数之外,您可能应该将它包含在依赖项数组中。
查看我的主要答案
正如我可能已经在前 2 中解释的那样,ESLint 会怀疑这是一个错误,因为这些值有可能发生变化,即使它们实际上并没有这样做。可能只是 ESLint 没有检查“安装时”效果的特殊情况,因此如果它像任何其他可能触发多次的效果一样检查该效果,这显然会成为一个现实的错误。一般来说,如果你的效果只运行一次并且你知道当时数据是正确的,我认为你不需要太担心依赖警告。
【讨论】:
【参考方案3】:Linting 是分析代码中潜在错误的过程。现在,当我们讨论为什么会出现 lint 错误时,我们需要了解规则是通过牢记特定功能的理想用例来设置的。
这里有一个 useEffect 钩子,一般的概念是,如果我们有一个可能改变或可能导致逻辑流改变的值,所有这些都应该进入依赖项数组。
因此,数据是第一个进入的候选对象。userRole 也是如此,因为它被用于控制逻辑流,而不仅仅是作为一个值。
我建议使用 linter 建议忽略错误。
【讨论】:
感谢您的回答!但是,只要依赖数组中的任何变量发生变化,只要符合 linter 的要求,就会触发 useEffect 运行!这是我不想要的! 如果我错了,请纠正我,但你想知道一种方法来解决 linter 引发的所有标志,而不是通过注释掉它,但我使用依赖数组但希望它只运行一次? 是的!我想将依赖数组留空以使 useEffect 只运行一次而不会使 linter 生气 由于您使用的是自定义钩子,我不确定如何让 linter 快乐,但只运行一次。但是,存在一种骇人听闻的方式(useRef)来保持数组非空并运行一次,从而使 linter 保持快乐。虽然我建议反对它。 我正在寻找正确的方法,因为我很困惑并且文档没有提到这种情况!【参考方案4】:警告:我不建议实际使用它。这是实现不正确的逻辑,并且当值更改时保证是错误的。 linter 试图提供帮助,因为下面的代码引入了许多微妙的错误。
如果你想这样做,你可以使用ref
来存储一次性函数:
const userRole = sessionStorage.getItem('role');
const data, setData, type, setTableType = useTable([]);
const _dangerousOnMount = useRef(async () =>
// fetch some data from API
const fetchedData = await axios('..');
if (userRole === 'admin')
setData([...fetchedData, orders: [] ]);
else
setData(fetchedData);
if (type === '1')
setTableType('normal');
);
useEffect(() =>
_dangerousOnMount.current();
, []);
【讨论】:
嗯,这对我来说似乎有点 hacky 我一直在寻找一种更清晰的方法,但你给了我一个非常好的建议“如果这些事情不随着时间的推移而改变,那么 useEffect 只会得到调用一次。如果这些事情真的发生了变化,你的代码将在逻辑上不正确,并会导致细微的错误。我建议遵循 lint 规则;它在这里可以帮助你并防止细微的潜在错误“我真的很感激。【参考方案5】:另一种解决方案是创建一个用于初始化的挂钩(运行一次)。
import useState from 'react';
export const useInit = initCallback =>
const [initialized, setInitialized] = useState(false);
if (!initialized)
initCallback();
setInitialized(true);
;
然后在你的 React 组件上使用它:
useInit(() =>
// Your code here will be run only once
);
【讨论】:
以上是关于即使存在依赖关系,如何运行一次 useEffect ?为啥 ESLint 抱怨它?的主要内容,如果未能解决你的问题,请参考以下文章
如何绕过 React 的 useEffect() “缺少依赖项”警告?