【问题标题】:useMemo hook not working with map elementsuseMemo 钩子不适用于地图元素
【发布时间】:2021-10-05 20:12:20
【问题描述】:

我正在使用 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}

【问题讨论】:

  • 你可以创建一个沙盒吗?
  • 不用useMemo也可以试试。
  • @karagoz 我得到了相同的结果,使用 useMemo 我想限制我的渲染元素。不幸的是,Item 组件渲染了两次
  • @问我不知道怎么加沙箱
  • 另一种改进 useMemo 的方法是使用 JSON.stringify(items) 作为触发器。即使引用发生变化,这看起来也一样,只要内容相同,它就不会再次运行。所以基本上 useMemo(() => {}, [JSON.stringify(items)]);

标签: reactjs react-hooks react-usememo


【解决方案1】:

上面的代码有几处不对。

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

这就是为什么还要执行一个 console.log

  • 还应该记住,记忆需要时间,并且您希望尽可能保持高效,因此您可以完全跳过带有 React.memo 组件的 useMemo,该组件负责处理数组,因为它保存在如果状态保持不变,它的引用不会在重新渲染时改变
    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);

更新

This codesandbox 表明 useMemo 仅在 items 值按预期更改时才被调用。

【讨论】:

  • 感谢@Kia Kaha 注意到 key 和 useMemo 问题,但这些类型的问题可能会实时出现。我的问题是为什么 Item 组件在没有数据更改的情况下首先调用。
  • 您已将 console.log 放置在组件内,这意味着它将在您隐藏的每个“render, right? When you tigger the loading”状态下执行并再次显示触发renderingItem 组件虽然价值没有改变。然后items 值发生变化,这会再次触发组件到rerender。希望这更清楚,因为我也试图在上面注意到它。
  • 实际问题在 Item 组件内部,我正在根据 props 更改调用 api,但由于这个问题,api 调用会多次进行
  • 那么您的示例无关紧要,并且无法根据上面的代码解决您的问题。听起来您的设计不正确,但请提供一个合理的例子。另外 - 您是否尝试过切换 setStates 并运行代码?
  • 如果我在设置项目后保留 setState,我知道它会起作用,但为什么 useMemo 不处理项目更改以及在渲染项目内部如何在没有项目更改的情况下调用 Item 组件
【解决方案2】:

使用自定义挂钩:

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)
}

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

【讨论】:

  • @jadeja,感谢您的解决方案,但我想知道为什么 useMemo 挂钩对地图元素失败
猜你喜欢
  • 2019-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-21
  • 2016-02-13
  • 1970-01-01
相关资源
最近更新 更多