今天和同事讨论了React依赖关系。他认为组件所依赖的产生副作用的所有变量都应该放入依赖数组中,例如
import React, { useContext, useEffect, useState } from 'react';
export const Component = props => {
const { someInfo } = props;
const [info, setInfo] = useState(null);
const { updateInfo } = useContext(someInfoContext);
const initSomething = async publicId => {
const res = await getPublicInfo(publicId);
updateInfo(res);
setInfo(res);
};
// Here only someInfo.publicId is used
useEffect(() => {
initSomething(someInfo.publicId);
}, [someInfo.publicId]);
return <div>hellp world</div>;
};
这里,useEffect中的副作用依赖于变量someInfo.publicId,所以我将其放入依赖数组中,以确保副作用每次发生变化时都可以重新执行。然而,我的同事不同意。他认为useEffect中的所有变量都是变量依赖,所以他认为应该这样写
import React, { useCallback, useContext, useEffect, useState } from 'react';
export const Component = props => {
const { someInfo } = props;
const [info, setInfo] = useState(null);
const { updateInfo } = useContext(someInfoContext);
const initSomething = useCallback(
async publicId => {
const res = await getPublicInfo(publicId);
updateInfo(res);
setInfo(res);
},
[updateInfo]
);
// Here someInfo.publicId and initSomething are used
useEffect(() => {
initSomething(someInfo.publicId);
}, [someInfo.publicId, initSomething]);
return <div>hellp world</div>;
};
我觉得这样写有一个缺点,就是依赖性具有传染性。必须保证函数的引用不变,所以函数必须用useCallback包装,initSomething中的updateInfo也必须用useCallback处理。这就需要一直处理,带来很大的精神负担,而且代码容易出现问题,死循环。不过,如果像我先写的那样,组件渲染时会重新声明initSomething,但可以保证代码执行逻辑没有问题
我想知道第一种和第二种写法哪个是正确的?
问题的两个可能答案是:
a) 当然,只要你是团队中唯一的人,你就可以遵循你认为最好的任何方式。这意味着您是唯一的一名开发人员,也是唯一一个将在软件的整个生命周期中维护该软件的人。
b) 如果情况与 a 点不同,那么遵循社区的最佳实践和库开发人员的建议可能会有所帮助。
这里引用 React 团队用来描述依赖数组被开发人员操纵的情况的确切词。他们将开发人员的这种行为称为“Lie to React”。开发商撒谎了! 是否可以抑制依赖linter?
但是,您的观点是有效的。尽管效果中的整个代码被视为响应式逻辑,但有时效果也会具有非响应式逻辑。 React 团队已经开始着手解决这个问题,因为我们可以看到这一行中提出了 API useEffectEvent。
虽然 linter 可以被抑制并且开发人员可以继续代码,但是这样的代码变得缺乏表达力,容易出现逻辑错误并且与未来的库版本不兼容。因此,将依赖项设置为完整的额外效果,或使用解决方法来避免操作依赖项数组是值得尝试的。
回答问题:
请检查创建函数对象 initSomething 的必要性。 它真的应该是一个命名的功能对象吗?它是否被多个调用站点引用?它可能不是真正的命名者。请参阅下面可能的重构代码。
const initSomething = useCallback(
async publicId => {
const res = await getPublicInfo(publicId);
updateInfo(res);
setInfo(res);
},
[updateInfo]
);
事实上,如果函数 getPublicInfo 不是异步函数,那么函数 initSomething 可能会被重构如下。这里讨论这个案例只是为了展示重构简化了代码。
useEffect(() => {
const res = getPublicInfo(someInfo.publicId);
updateInfo(res);
setInfo(res);
}, [someInfo.publicId, updateInfo, setInfo]);
由于 getPublicInfo 是一个异步函数,并且需要使用await在这里进行计算,因此下面的代码是最少的。在这里,立即调用的匿名函数已用于包装调用。 但是,updateInfo 和 setInfo 也需要设置为依赖项,尽管它将保持不变。通过添加它,代码变得更具表现力。
useEffect(() => {
(async () => {
const res = await getPublicInfo(someInfo.publicId);
updateInfo(res);
setInfo(res);
})();
}, [someInfo.publicId, updateInfo, setInfo]);