React,如何在不重绘的情况下更新状态,或者如何在没有 useState(和 useRef)的情况下触发 useEffect,或者如何停止无限循环
React, how to update a state without repainting, or how to trigger useEffect without useState (and with useRef), or how to stop infinite loop
情况
- 我想根据图像的高度重新分布网格(使用砌体方法)
- 这些图像是延迟加载的
我想要的
- 加载图像时,他们发送一个标志,这个标志使砌体逻辑重新分配网格,大家都很高兴
- 然后当用户滚动时,加载更多图片,再次触发标志,masonry 再次调整网格
会发生什么
* const [myState, setMyState] = use State(0)
- 当我加载图像时
setMyState(1)
- 在砌体逻辑中,我有一个
useLayoutEffect(() =>{}, [myState])
所以当状态改变时,我调整网格
- 然后我用
setMyState(0)
重置状态,这样当加载新图像时它会再次触发 useLayoutEffect
- BUT,当我重置状态时它会重新绘制图像,因此图像会再次加载并再次触发
useLayoutEffect
,这会再次重置状态,创建无限循环
- 如果我不重置状态,循环就会停止
- BUT 然后当滚动加载新图像时,
setMyState(1)
不会改变任何东西并且 useLayoutEffect
不会被触发
所以我现在卡住了
这是一个简化的代码
import React, { useRef, useLayoutEffect, useState} from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ setImageLoader }) => {
// lazy load the image with an Observer, and then changes the state
setImageLoader(1);
return <></>;
};
export default function App() {
const [imageLoaded, setImageLoader] = useState(0);
useLayoutEffect(() => {
const update_grid = async stillMounted => {
// change stuff
await wait(10000); // careful with crashing chrome
console.log("updated");
setImageLoader(0);
};
const stillMounted = { value: true };
update_grid(stillMounted);
return () => (stillMounted.value = false);
}, [imageLoaded]);
return <Image setImageLoader={setImageLoader} />;
}
尝试 1
我无法更改 useLayoutEffect
中的状态,因此我只会在加载图像时更改内容,而不会再次更改
问题
当我滚动并加载更多图像时,我无法再触发 useLayoutEffect
import React, { useRef, useLayoutEffect, useState} from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ setImageLoader }) => {
setImageLoader(1);
return <>hey</>;
};
export default function App() {
const [imageLoaded, setImageLoader] = useState(0);
useLayoutEffect(() => {
// change stuff
const update_grid = async stillMounted => {
await wait(40000);
console.log("updated");
// setImageLoader(0); NOW THIS IS NOT CAUSING THE REPAINT, BUT NEITHER WHEN NEW IMAGES ARE LOADED
};
const stillMounted = { value: true };
update_grid(stillMounted);
return () => (stillMounted.value = false);
}, [imageLoaded]);
return <Image setImageLoader={setImageLoader} />;
}
尝试 2
如果我不想重绘,但想触发 useLayoutEffect,我可以使用 useRef
而不是 useState
import React, { useRef, useLayoutEffect, useState, useEffect } from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ imageLoaded }) => {
imageLoaded.current++;
return <>hey</>;
};
export default function App() {
const imageLoaded = useRef(0);
useLayoutEffect(() => {
// change stuff
const update_grid = async stillMounted => {
await wait(10000);
console.log("updated " + imageLoaded.current);
imageLoaded.current++;
};
const stillMounted = { value: true };
update_grid(stillMounted);
return () => (stillMounted.value = false);
}, [imageLoaded.current]);
return <Image imageLoaded={imageLoaded} />;
}
但是 codesandbox 已经警告我 imageLoaded.current
不会导致组件的重新渲染(即使我或多或少想要这个?)
所以最后,我看到 imageLoaded.current
在加载新图像时增加,但是 useLayoutEffect
没有被触发
我无法使用 useEffect
或类似方法
解决该问题
相反,我使用了一个简单的函数来调整网格,并在加载图像时从延迟加载逻辑中调用它
import React, { useRef, useEffect, useState } from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ adjustMasonry }) => {
// lazy load the image with an Observer, and then changes the state
const scroll = async () => {
adjustMasonry();
adjustMasonry(); // only causes one update due to the implemented buffer
await wait(4000);
adjustMasonry();
};
scroll();
return <></>;
};
export default function App() {
let signals = 0;
const adjustMasonry = async stillMounted => {
signals++;
const last_signal = signals;
await wait(200); // careful with crashing chrome
if (signals !== last_signal) return; // as a way to minimize the number of consecutive calls
if (stillMounted && !stillMounted.value) return;
// change stuff
console.log("updated");
};
useEffect(() => {
const stillMounted = { value: true };
adjustMasonry(stillMounted);
return () => {
stillMounted.value = false;
};
}, []);
return <Image adjustMasonry={adjustMasonry} />;
}
情况
- 我想根据图像的高度重新分布网格(使用砌体方法)
- 这些图像是延迟加载的
我想要的
- 加载图像时,他们发送一个标志,这个标志使砌体逻辑重新分配网格,大家都很高兴
- 然后当用户滚动时,加载更多图片,再次触发标志,masonry 再次调整网格
会发生什么
* const [myState, setMyState] = use State(0)
- 当我加载图像时
setMyState(1)
- 在砌体逻辑中,我有一个
useLayoutEffect(() =>{}, [myState])
所以当状态改变时,我调整网格 - 然后我用
setMyState(0)
重置状态,这样当加载新图像时它会再次触发useLayoutEffect
- BUT,当我重置状态时它会重新绘制图像,因此图像会再次加载并再次触发
useLayoutEffect
,这会再次重置状态,创建无限循环 - 如果我不重置状态,循环就会停止
- BUT 然后当滚动加载新图像时,
setMyState(1)
不会改变任何东西并且useLayoutEffect
不会被触发
所以我现在卡住了
这是一个简化的代码
import React, { useRef, useLayoutEffect, useState} from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ setImageLoader }) => {
// lazy load the image with an Observer, and then changes the state
setImageLoader(1);
return <></>;
};
export default function App() {
const [imageLoaded, setImageLoader] = useState(0);
useLayoutEffect(() => {
const update_grid = async stillMounted => {
// change stuff
await wait(10000); // careful with crashing chrome
console.log("updated");
setImageLoader(0);
};
const stillMounted = { value: true };
update_grid(stillMounted);
return () => (stillMounted.value = false);
}, [imageLoaded]);
return <Image setImageLoader={setImageLoader} />;
}
尝试 1
我无法更改 useLayoutEffect
中的状态,因此我只会在加载图像时更改内容,而不会再次更改
问题
当我滚动并加载更多图像时,我无法再触发 useLayoutEffect
import React, { useRef, useLayoutEffect, useState} from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ setImageLoader }) => {
setImageLoader(1);
return <>hey</>;
};
export default function App() {
const [imageLoaded, setImageLoader] = useState(0);
useLayoutEffect(() => {
// change stuff
const update_grid = async stillMounted => {
await wait(40000);
console.log("updated");
// setImageLoader(0); NOW THIS IS NOT CAUSING THE REPAINT, BUT NEITHER WHEN NEW IMAGES ARE LOADED
};
const stillMounted = { value: true };
update_grid(stillMounted);
return () => (stillMounted.value = false);
}, [imageLoaded]);
return <Image setImageLoader={setImageLoader} />;
}
尝试 2
如果我不想重绘,但想触发 useLayoutEffect,我可以使用 useRef
而不是 useState
import React, { useRef, useLayoutEffect, useState, useEffect } from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ imageLoaded }) => {
imageLoaded.current++;
return <>hey</>;
};
export default function App() {
const imageLoaded = useRef(0);
useLayoutEffect(() => {
// change stuff
const update_grid = async stillMounted => {
await wait(10000);
console.log("updated " + imageLoaded.current);
imageLoaded.current++;
};
const stillMounted = { value: true };
update_grid(stillMounted);
return () => (stillMounted.value = false);
}, [imageLoaded.current]);
return <Image imageLoaded={imageLoaded} />;
}
但是 codesandbox 已经警告我 imageLoaded.current
不会导致组件的重新渲染(即使我或多或少想要这个?)
所以最后,我看到 imageLoaded.current
在加载新图像时增加,但是 useLayoutEffect
没有被触发
我无法使用 useEffect
或类似方法
相反,我使用了一个简单的函数来调整网格,并在加载图像时从延迟加载逻辑中调用它
import React, { useRef, useEffect, useState } from "react";
const wait = ms =>
new Promise((res, rej) => setTimeout(() => res("timed"), ms));
const Image = ({ adjustMasonry }) => {
// lazy load the image with an Observer, and then changes the state
const scroll = async () => {
adjustMasonry();
adjustMasonry(); // only causes one update due to the implemented buffer
await wait(4000);
adjustMasonry();
};
scroll();
return <></>;
};
export default function App() {
let signals = 0;
const adjustMasonry = async stillMounted => {
signals++;
const last_signal = signals;
await wait(200); // careful with crashing chrome
if (signals !== last_signal) return; // as a way to minimize the number of consecutive calls
if (stillMounted && !stillMounted.value) return;
// change stuff
console.log("updated");
};
useEffect(() => {
const stillMounted = { value: true };
adjustMasonry(stillMounted);
return () => {
stillMounted.value = false;
};
}, []);
return <Image adjustMasonry={adjustMasonry} />;
}