react hook依赖链_如何处理React Hook中的循环依赖

react hook依赖链

July 27, 2020

2020年7月27日

TL; DR (TL;DR)

Use this hook:

使用此钩子:

// This hook provides a ref which is perpetually up to date but will
// not trigger any renders. This is useful for resolving circular
// references in dependency arrays.
export default function useNoRenderRef(currentValue) {
const ref = useRef(currentValue); ref.current = currentValue; return ref;
}

Like this:

像这样:

const [item, setItem] = useState(0);
const [queue, setQueue] = useState([]);
const queueNoRenderRef = useNoRenderRef(queue);useEffect(() => {
// Access queue data using the special ref from the hook, but use
// the normal state writer. Since we don't have to include `queue`
// in the dependency array, this effect will not be run again when
// we use `setQueue`.
setQueue(queueNoRenderRef.current.concat(item));
}, [item, queueNoRenderRef]);// since we updated queue, this effect will be run
useEffect(() => {
console.log("The queue was updated!");
}, [queue]);console.log(queue); // [0, 1, 2, 3, ...]

We all love hooks, for good or for bad. With every advancement though, comes stumbling blocks.

我们都喜欢钩子,无论好坏。 但是,随着每一次进步,都会遇到绊脚石。

The stumbling block that I have faced most often is an effect hook which is continually executed because its dependencies keep changing. I have found a way to deal with this, which I would like to share with you today.

我最经常遇到的绊脚石是一个效果钩子,由于其依赖关系不断变化,该钩子会不断执行。 我已经找到一种解决方法,今天我想与大家分享。

建立 (Setup)

Sometimes, I have an effect that looks like this:

有时,我会有如下效果:

const [item, setItem] = useState(0);
const [queue, setQueue] = useState([]);useEffect(() => {
setQueue(queue.concat(item));
}, [item]);console.log(queue); // [0], [1], ...

第一个问题 (The First Problem)

This hook is written in a way that conveys my intent perfectly. Whenever item changes, I want to add it to our queue. The hook even functions as I expect...almost.

该钩子的编写方式可以完美传达我的意图。 每当item更改时,我都想将其添加到我们的队列中。 钩子甚至可以按我期望的那样工作...几乎。

Since queue is not in the dependency array, the hook will never have access to an updated value for it. That means that within the useEffect hook, queue will always be [].

由于queue不在依赖项数组中,因此该挂钩永远无法访问其更新后的值。 这意味着在useEffect挂钩中, queue始终为[]

This means that every time we update the queue, it is set to [item], overwriting any elements that were previously in the queue.

这意味着每次我们更新队列时,它将设置为[item] ,从而覆盖队列中先前存在的所有元素。

解决方案 (The Solution)

Here is what is looks like when we fix that issue:

解决此问题时,情况如下所示:

const [item, setItem] = useState(0);
const [queue, setQueue] = useState([]);useEffect(() => {
setQueue(queue.concat(item));
}, [item, queue]);

第二个问题 (The Second Problem)

If you use the updated code, you will be stuck in an infinite loop, because the effect runs whenever the item or the queue are updated. Since the queue is updated when the effect is run, the effect will continually trigger itself.

如果使用更新的代码,则将陷入无限循环,因为只要项目队列更新,效果就会运行。 由于队列是在运行效果时更新的,因此效果将不断触发自身。

解决方案 (The Solution)

The best way I know of to deal with this is using a ref.

我知道处理此问题的最好方法是使用ref。

A ref is available anywhere in your functional component (or custom hook, whichever you are writing). That is because it is a reference which points at an object which has one property, current. The value of current is changed, but you never change the pointer itself. For more information, check out the docs.

ref可以在功能组件中的任何位置使用(或自定义钩子,无论您写的是什么)。 这是因为它是指向具有一个属性current的对象的引用。 current的值已更改,但是您永远不会更改指针本身。 有关更多信息,请查看docs

We can use a ref like this:

我们可以使用这样的引用:

const [item, setItem] = useState(0);
const queueRef = useRef([]);useEffect(() => {
queueRef.current = queueRef.current.concat(item);
}, [item]);console.log(queue); // [0, 1, 2, 3, ...]

Now we can to use queueRef.current anywhere to access the value.

现在我们可以在任何地方使用queueRef.current来访问该值。

What if our code looked like this?

如果我们的代码看起来像这样怎么办?

const [item, setItem] = useState(0);
const queueRef = useRef([]);useEffect(() => {
queueRef.current = queueRef.current.concat(item);
}, [item]);useEffect(() => {
console.log("The queue was updated!");
}, []);console.log(queue); // [0, 1, 2, 3, ...]

If you run this, you will find that you don’t get the log messages when you update the queue. You might think “oh, just put queueRef.current in the dependencies array!” If you use the eslint hooks plugin, you will be told that isn’t valid if you do that. This is because ref’s do not trigger renders (although technically in my experimentation, it did work in this case).

如果运行此命令,您将发现更新队列时未收到日志消息。 您可能会想“哦,只需将queueRef.current放入依赖项数组中!” 如果您使用eslint hooks插件,则会被告知无效。 这是因为ref不会触发渲染(尽管从技术上讲,在我的实验中,它确实可以正常工作)。

So, how do we resolve it?

那么,我们如何解决呢?

Let’s add the queue state back to our code:

让我们将队列状态添加回我们的代码中:

const [item, setItem] = useState(0);
const [queue, setQueue] = useState([]);
const queueRef = useRef([]);useEffect(() => {
setQueue(queueRef.current.concat(item));
}, [item]);useEffect(() => {
console.log("The queue was updated!");
}, [queue]);console.log(queue); // [0]

We are almost there…We now have a way to read the queue which does not trigger another render because it isn’t in our dependency array. We also have our message working when we update the queue.

我们快到了……我们现在有了一种读取队列的方法,该队列不会触发另一个渲染,因为它不在我们的依赖项数组中。 更新队列时,我们的消息也起作用。

There is just one problem. We are back to the very first issue. We always set the queue to [item]! That is because we are not updating queueRef.current anywhere.

只有一个问题。 我们回到了第一个问题。 我们总是将队列设置为[item] ! 那是因为我们不在任何地方更新queueRef.current

Let’s do that:

让我们这样做:

const [item, setItem] = useState(0);
const [queue, setQueue] = useState([]);
const queueRef = useRef([]);queueRef.current = queue;useEffect(() => {
setQueue(queueRef.current.concat(item));
}, [item]);useEffect(() => {
console.log("The queue was updated!");
}, [queue]);console.log(queue); // [0, 1, 2, 3, ...]

If you run this code, you will find that it works perfectly. It has everything we need. No extra renders, and our ref is always pointing at the right data.

如果运行此代码,您会发现它运行完美。 它具有我们需要的一切。 没有多余的渲染,我们的参考始终指向正确的数据。

We have solved the problem!

我们已经解决了问题!

重构 (Refactoring)

I have faced this often enough that I wanted a re-useable solution. Since that is exactly what hooks were created for, I decided to create a custom hook. This is what I came up with:

我经常面对这个问题,以至于我想要一个可重用的解决方案。 由于这正是创建钩子的目的,因此我决定创建一个自定义钩子。 这是我想出的:

// This hook provides a ref which is perpetually up to date but will
// not trigger any renders. This is useful for resolving circular
// references in dependency arrays.
export default function useNoRenderRef(currentValue) {
const ref = useRef(currentValue); ref.current = currentValue; return ref;
}

更新的代码 (Updated Code)

If we use the hook if our code, it looks like this:

如果我们在代码中使用钩子,则如下所示:

const [item, setItem] = useState(0);
const [queue, setQueue] = useState([]);
const queueNoRenderRef = useNoRenderRef(queue);useEffect(() => {
setQueue(queueNoRenderRef.current.concat(item));
}, [item, queueNoRenderRef]);useEffect(() => {
console.log("The queue was updated!");
}, [queue]);console.log(queue); // [0, 1, 2, 3, ...]

This code works exactly as you would expect. Notice that you need to use the current property on your "no render ref" value, because it is a ref object. The hook does all of the work keeping the ref's value up to date for us, and we now have a way to access the value within other hooks without triggering any re-renders.

该代码完全按照您的期望工作。 注意,您需要在“ no render ref”值上使用current属性,因为它是一个ref对象。 该挂钩完成了所有工作,从而使ref的值保持最新,我们现在可以在不触发任何重新渲染的情况下访问其他挂钩中的值。

If you are confused why there is no re-render even though queueNoRenderRef is in the dependency array, remember that is a ref object, which never changes. Instead, the value referenced by queueNoRenderRef.current is changed. The dependency array does not know about that though, so no re-render occurs.

如果您对为什么即使queueNoRenderRef在依赖项数组中也没有重新渲染感到困惑,请记住这是一个ref对象,它永远不会改变。 而是,更改queueNoRenderRef.current引用的值。 但是,依赖项数组不知道这一点,因此不会发生任何重新渲染。

The only reason I have included queueNoRenderRef in the dependency array is to satisfy the eslint hooks plugin. Technically, the code would still function properly even if it wasn't in there.

我在依赖项数组中包含queueNoRenderRef的唯一原因是为了满足eslint挂钩插件的要求。 从技术上讲,即使该代码不在其中,它仍然可以正常运行。

If you would like to see this code functioning, I have created a codesandbox here.

如果您希望该代码正常运行,我在这里创建了一个codeandbox。

谢谢你,朋友 (Thank You, Friend)

Thanks for stopping by, I really hope this helped you!

感谢您的光临,我真的希望这对您有所帮助!

Brandon ConwayI enjoy learning about and writing code in many programming languages

Brandon Conway 我喜欢学习和编写许多编程语言的代码

Originally published at https://brandoncc.dev.

最初发布在 https://brandoncc.dev

翻译自: https://medium.com/swlh/how-to-deal-with-circular-dependencies-in-react-hooks-70cf028b689e

react hook依赖链

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值