Реагируйте: Если useState обновлен, как он может быть не обновлен одновременно? - PullRequest
2 голосов
/ 10 февраля 2020

У меня есть код реакции, который

  • устанавливает empty state
  • fills состояние
  • после заполнения этого состояния, оно renders некоторые изображения
  • , затем эти изображения вызывают событие onLoad
  • , это событие onLoad затем вызывает function, reads начальное state
  • Но это состояние empty

Как это может быть? Если функция вызывается, это означает, что состояние больше не пустое

Ручка https://codesandbox.io/s/usestate-strange-things-9rydu

Код

import React, { useRef, useState, useEffect } from "react";
import styled from "@emotion/styled";

const useMyHook = (virtual_structure, setVirtual_structure) => {
  useEffect(() => {
    console.log("virtual_structure is updated!");
    console.log(virtual_structure);
    console.log("____virtual_structure is updated!");
  }, [virtual_structure]);

  const refs = useRef([]);

  const createStructure = () => {
    console.log("virtual_structure, is it empty?");
    console.log(virtual_structure);
  };

  useEffect(() => {
    createStructure();
  }, []);

  const assignRef = r =>
    r && (refs.current.includes(r) || refs.current.push(r));

  return [assignRef, createStructure];
};

export default function App() {
  const [virtual_structure, setVirtual_structure] = useState([]);

  const [assignRef, updateGrid] = useMyHook(
    virtual_structure,
    setVirtual_structure
  );

  useEffect(() => {
    const temp_structure = Array.from({ length: 4 }, () => ({
      height: 0,
      cells: []
    }));
    temp_structure[0].cells = Array.from({ length: 10 }, () => {
      const rand = Math.random();
      const r = rand > 0.1 ? parseInt(500 * rand) : parseInt(500 * 0.1);
      return {
        height: "",
        el: (
          <div ref={assignRef}>
            <Image
              alt=""
              onload={updateGrid}
              num=""
              src={`https://picsum.photos/200/${r}`}
            />
          </div>
        )
      };
    });

    setVirtual_structure(temp_structure);
  }, []);

  return (
    <Container>
      {virtual_structure.map((col, i) => (
        <div key={`col${i}`}>
          {col.cells && col.cells.map((cell, j) => <>{cell.el}</>)}
        </div>
      ))}
    </Container>
  );
}

const Image = ({ alt, onload, num, src }) => (
  <>
    <Label>{num}</Label>
    <Img src={src} alt={alt} onLoad={onload} />
  </>
);

const Img = styled.img`
  border: 1px solid #000;
  height: min-content;
  margin: 0;
  padding: 0;
`;
const Label = styled.div`
  position: absolute;
`;

const Container = styled.div`
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  background: #ccc;
  align-content: center;

  div {
    flex: 1;

    div {
      color: #fff;
      font-weight: 700;
      font-size: 32px;
      margin: 4px;
    }
  }
`;

А console.log

virtual_structure is updated!
index.js:27 Array(0)length: 0__proto__: Array(0)
index.js:27 ____virtual_structure is updated!
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure is updated!
index.js:27 Array(4)0: {height: 0, cells: Array(10)}1: {height: 0, cells: Array(0)}2: {height: 0, cells: Array(0)}3: {height: 0, cells: Array(0)}length: 4__proto__: Array(0)
index.js:27 ____virtual_structure is updated!
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 Array(0)length: 0__proto__: Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 []length: 0__proto__: Array(0)
index.js:27 virtual_structure, is it empty?
index.js:27 []

Ответы [ 2 ]

3 голосов
/ 10 февраля 2020

Это происходит из-за замыканий .

Вы передаете функцию updateGrid каждому Image компоненту один раз при монтировании :

// useEffect closes its lexixal scope upon "updateGrid" 
const [assignRef, updateGrid] = useMyHook(
  virtual_structure,
  setVirtual_structure
);

useEffect(() => {
  ...
  temp_structure[0].cells = Array.from({ length: 10 }, () => {
    return {
      el: (
        <div ref={assignRef}>
//                          v Used inside the callback scope
          <Image onload={updateGrid} />
        </div>
      )
    };
  });

  setVirtual_structure(temp_structure);
}, []);

Но значение virtual_structure в updateGrid (то есть createStructure, которое вы переименовали) фактически всегда равно [] в лексической области обратного вызова useEffect. Хотя createStructure обновляет данные при рендеринге, оно никогда не передавалось компоненту Image с ожидаемым значением .

const createStructure = () => {
  console.log('virtual_structure, is it empty?');
  console.log(virtual_structure); // always virtual_structure=[]
};

Примечание: никогда не игнорируйте предупреждения lint , хотя вы можете знать, что делаете, это может привести к неожиданным ошибкам.

0 голосов
/ 10 февраля 2020

Как сказано в ответе @ Dennis-va sh, "замыкания" замораживают переменную useState внутри функции области видимости, так что эта функция никогда не видит текущее (обновленное) значение этой переменной

Обходной путь - вместо вызова функции, выполняющей logi c, я всегда могу вызвать функцию, которая обновляет состояние, а затем использовать это состояние для запуска функции (теперь useEffect вместо функции )


Я оставлю вопрос открытым на несколько дней, если кто-то захочет предложить лучшую альтернативу для решения этой проблемы (?)


Код

https://codesandbox.io/s/usestate-strange-things-tneue

import React, { useRef, useState, useEffect } from "react";
import styled from "@emotion/styled";

const useMyHook = (virtual_structure, setVirtual_structure, updateGrid) => {
  const refs = useRef([]);

  useEffect(() => {
    console.log("virtual_structure, is it empty?");
    console.log(virtual_structure);
  }, [updateGrid, virtual_structure]);

  const assignRef = r =>
    r && (refs.current.includes(r) || refs.current.push(r));

  return [assignRef];
};

export default function App() {
  const [virtual_structure, setVirtual_structure] = useState([]);
  const [updateGrid, setUpdateGrid] = useState();

  const [assignRef] = useMyHook(
    virtual_structure,
    setVirtual_structure,
    updateGrid
  );

  const update = async () => setUpdateGrid(updateGrid + 1);

  useEffect(() => {
    const temp_structure = Array.from({ length: 4 }, () => ({
      height: 0,
      cells: []
    }));
    temp_structure[0].cells = Array.from({ length: 10 }, () => {
      const rand = Math.random();
      const r = rand > 0.1 ? parseInt(500 * rand) : parseInt(500 * 0.1);
      return {
        height: "",
        el: (
          <div ref={assignRef}>
            <Image
              alt=""
              onload={update}
              num=""
              src={`https://picsum.photos/200/${r}`}
            />
          </div>
        )
      };
    });

    setVirtual_structure(temp_structure);
  }, []);

  return (
    <Container>
      {virtual_structure.map((col, i) => (
        <div key={`col${i}`}>
          {col.cells &&
            col.cells.map((cell, j) => (
              <React.Fragment key={`cell${j}`}>{cell.el}</React.Fragment>
            ))}
        </div>
      ))}
    </Container>
  );
}

const Image = ({ alt, onload, num, src }) => (
  <>
    <Label>{num}</Label>
    <Img src={src} alt={alt} onLoad={onload} />
  </>
);

const Img = styled.img`
  border: 1px solid #000;
  height: min-content;
  margin: 0;
  padding: 0;
`;
const Label = styled.div`
  position: absolute;
`;

const Container = styled.div`
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  background: #ccc;
  align-content: center;

  div {
    flex: 1;

    div {
      color: #fff;
      font-weight: 700;
      font-size: 32px;
      margin: 4px;
    }
  }
`;
...