温馨提示:本文翻译自stackoverflow.com,查看原文请点击:reactjs - React, how to update a state without repainting, or how to trigger useEffect without useState (and w
react-hooks reactjs use-effect use-state use-layout-effect

reactjs - 反应,如何在不重绘的情况下更新状态,或者如何在没有useState(和w的情况下)触发useEffect

发布于 2020-03-28 23:29:33

情况

  • 我想根据图像的高度重新分配网格(使用砌体方法)
  • 这些图像是延迟加载的

我想要的是

  • 加载图像后,它们会发送一个标志,此标志使砖石逻辑重新分配网格,每个人都很高兴
  • 然后,当用户滚动时,将加载更多图像,再次触发标志并再次调整砖石网格

怎么了

* const [myState, setMyState] = use State(0)

  • 加载图像后,我 setMyState(1)
  • 在砌体逻辑中,useLayoutEffect(() =>{}, [myState])当状态改变时,我有一个调整网格
  • Then I reset the state with setMyState(0), so that when a new image is loaded it will trigger again the useLayoutEffect
  • BUT, when I reset the state it repaints the images, so the images get loaded again and trigger again the useLayoutEffect, which again resets the state, creating an infinite loop
  • If I don't reset the state, the loop is stopped
  • BUT then when a new image is loaded with the scroll, setMyState(1) doesn't change anything and the useLayoutEffect is not triggered

So I'm now stuck

This is a simplified code

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} />;
}

Attempt 1

I cannot change the state in the useLayoutEffect, so I will change stuff only when the images are loaded and not again

Problem

When I scroll and more images are loaded, I cannot triger the useLayoutEffect anymore

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} />;
}

Attempt 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不会触发

查看更多

查看更多

提问者
GWorking
被浏览
24
GWorking 2020-01-31 18:18

我无法使用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} />;
}