5

我正在使用 useMemo 钩子来渲染地图项。我将 items 参数添加到 useMemo 钩子中,基于它会呈现的项目更改。但是改变加载状态和项目改变,Item 自定义组件渲染两次。我在使用 useMemo 钩子时是否犯了任何错误,请纠正我。

Home:

import React, { useState, useEffect, useMemo } from "react";
import Item from "./Item";

const array = [1];
const newArray = [4];

const Home = () => {
  const [items, setItems] = useState(array);
  const [loading, setLoading] = useState(false);
  const [dataChange, setDataChange] = useState(1);

  const renderItems = (item, index) => {
    return (
      <div key={item}>
        <Item id={item}></Item>
      </div>
    );
  };
  useEffect(() => {
    if (dataChange === 2) {
      setLoading(true);
      setTimeout(() => {
        setLoading(false);
        setItems(newArray);
      }, 3000);
    }
  }, [dataChange]);

  const memoData = useMemo(() => {
    return <div>{items.map(renderItems)}</div>;
  }, [items]);

  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <input
        onClick={() => {
          setDataChange(2);
        }}
        style={{ height: 40, width: 100, margin: 20 }}
        type="button"
        value="ChangeItem"
      ></input>
      <div>{loading ? <label>{"Loading"}</label> : <div>{memoData}</div>}</div>
    </div>
  );
};
export default React.memo(Home);

Item:

import React,{useEffect} from "react";
const Item = (props) => {
  console.log("props", props);
useEffect(() => {
// call api with props.id
 }, [props]);
  return <div>Hello world {props.id}</div>;
};
export default React.memo(Item);

结果:第一次: props {id: 1}

点击后: props {id: 1} props {id: 4}

4

2 回答 2

8

上面的代码中有一些不正确的地方。

  • key应该在数组迭代中传递给父元素 - 在你的情况下renderItems应该传递keydiv元素
  • loading您在更新items数组之前关闭状态,切换两个setState表达式将在大多数情况下解决您的情况,尽管setState它是一个异步函数并且不能保证
  • 如果常量或函数没有与组件的状态紧密耦合,则最好将其提取到组件之外,就像以下情况一样renderItems

这就是为什么还要执行一个 console.log 在此处输入图像描述

  • 还应该记住,记忆需要时间,并且您希望尽可能保持高效,因此您可以完全跳过处理数组useMemoReact.memo组件,因为它保持在状态并且它的引用不会改变如果状态保持不变则重新渲染
    const array = [1];
    const newArray = [4];
    
    const Home = () => {
      const [items, setItems] = useState(array);
      const [loading, setLoading] = useState(false);
      const [dataChange, setDataChange] = useState(1);
    
      useEffect(() => {
        if (dataChange === 2) {
          setLoading(true);
          setTimeout(() => {
            setItems(newArray);
            setLoading(false);
          }, 3000);
        }
      }, [dataChange]);
    
      return (
        <div style={{ display: "flex", flexDirection: "column" }}>
          <input
            onClick={() => {
              setDataChange(2);
            }}
            style={{ height: 40, width: 100, margin: 20 }}
            type="button"
            value="ChangeItem"
          ></input>
          <div>
            {loading ? <label>{"Loading"}</label> : <ItemsMemo items={items} />}
          </div>
        </div>
      );
    };
    
    const renderItems = (item) => {
      return (
        <span key={item} id={item}>
          {item}
        </span>
      );
    };
    
    const Items = ({ items }) => {
      console.log({ props: items[0] });
    
      return (
        <div>
          Hello world <span>{items.map(renderItems)}</span>
        </div>
      );
    };
    
    const ItemsMemo = React.memo(Items);

更新

此代码框显示useMemo仅当items值按预期更改时才被调用。

在此处输入图像描述

于 2021-09-27T07:31:44.227 回答
0

使用自定义钩子:

import { useEffect, useRef } from "react"

export default function useUpdateEffect(callback, dependencies) {
  const firstRenderRef = useRef(true)

  useEffect(() => {
    if (firstRenderRef.current) {
      firstRenderRef.current = false
      return
    }
    return callback()
  }, dependencies)
}

在您的项目中创建这些自定义挂钩并使用它们。它将防止您的第一个呼叫问题。

于 2021-10-02T07:59:05.003 回答