reactjs 为什么不能删除数组的第一项?

bqf10yzr  于 2023-02-12  发布在  React
关注(0)|答案(2)|浏览(175)

有一个容器ul保持在一个设定的高度,因此当ul中的内容溢出时,它应该从列表中删除第一个项目。每个列表项目显示为一个搜索结果,ul在不溢出的情况下只能容纳6个结果。该列表应该使用setRecent进行更改,因为它是使用useState声明的。但是当我尝试通过使用[...list].splice(1)删除第一个项目来更改列表时,它返回以下错误:
超出了最大更新深度。当组件在componentWillUpdate或componentDidUpdate内重复调用setState时,可能会发生这种情况。React限制嵌套更新的数量以防止无限循环。
当我删除setRecent并只返回值[...list].splice(1)时,它返回的列表没有第一项,但是需要setRecent来更改列表的值。
我该怎么解决这个问题?

const [recent, setRecent] = useState(["Dubai", "Paris", "London", "paris", "new york", "dubai", "New York"]);

const InsideSection = () => {
  const ulRef = createRef();
  useLayoutEffect(() => {
    if (onSearch) {
      const element = ulRef.current;
      
      if (element.scrollHeight > element.clientHeight) { // checks for overflow
        console.log("overflow");
        console.log(recent, "before"); // returns [] instead of the list 
        console.log([...recent].slice(1)); 
        // this should return ["Paris", "London", "paris", "new york", "dubai", "New York"] (without the first "Dubai") 
        // but when there is a setRecent inside the if statement, it causes an error as recent is []
        setRecent([...recent].slice(1)); // causes the error
        console.log(recent, "after"); // returns []
      }
    }
  }, [ulRef]);

  if (onSearch) {
    return (
      <div className = "search-container" >
          ...
          <ul ref = {ulRef} >
              <RecentSearches />
          </ul> 
      </div >
    );
  }
}
5ktev3wc

5ktev3wc1#

您的问题是在父组件内定义InsideSection组件。从InsideSection调用父组件的setRecent状态处理程序时,将导致重新呈现,从而导致再次执行父组件并重新创建InsideSection组件。这将再次触发useLayoutEffect。useLayoutEffect重新更新父对象的状态,父对象重新呈现并重新创建InsideSection,InsideSection一次又一次地重新触发useLayoutEffect。
每一次,它都会将你最近的数组从[“迪拜”、“巴黎”、“伦敦”、“巴黎”、“纽约”、“迪拜”、“纽约”]切片到[“巴黎”、“伦敦”、“巴黎”、“纽约”、“迪拜”、“纽约”]再到[“伦敦”、“巴黎”、“纽约”、“迪拜”,“纽约”]等等,直到它是[]并且不能再切片(这是它抛出错误的地方)。
解决方案是将InsideSection组件移出父组件,如果最近使用的变量仅由InsideSection组件使用,则在父组件中而不是父组件中定义它。如果最近使用的变量需要由父组件和子组件使用,则“不太”干净的解决方案是将其作为prop传递。
可能的解决方案如下所示:

const InsideSection = (props: {onSearch: boolean, recent: string[], setRecent: (recent: string[]) => void}) => {
  const { onSearch, recent, setRecent } = props;
  const ulRef = createRef();

  useLayoutEffect(() => {
    if (onSearch) {
      const element = ulRef.current;
      
      if (element.scrollHeight > element.clientHeight) {
        setRecent([...recent].slice(1));
      }
    }
  }, [ulRef]);

  if (onSearch) {
    return (
      <div className = "search-container" >
          ...
          <ul ref = {ulRef} >
              <RecentSearches />
          </ul> 
      </div >
    );
  }

  return <></>;
}

const Parent = () => {
  const onSearch = true; // Dunno where it comes from
  const [recent, setRecent] = useState(["Dubai", "Paris", "London", "paris", "new york", "dubai", "New York"]);

  return (
    <InnerSection onSearch={onSearch} recent={recent} setRecent={setRecent} />
  );
}

另一个解决方案是使用Context,我给你留了文档:https://reactjs.org/docs/context.html
PS:代码可能需要一些调整,这只是一个例子

hvvq6cgz

hvvq6cgz2#

它并不完美,但如果您使用带有ref和超时的去抖策略,它就可以发挥作用:
https://codesandbox.io/s/pensive-gould-4bmhns?file=/src/App.js

import { createRef, useLayoutEffect, useState, useRef } from "react";

export default function App() {
  const [recent, setRecent] = useState([
    "Dubai",
    "Paris",
    "London",
    "paris",
    "new york",
    "dubai",
    "New York"
  ]);
  const [updatedRecent, setUpdatedRecent] = useState();
  const onSearch = true;
  const ulRef = createRef();
  let timeoutId = useRef();

  useLayoutEffect(() => {
    console.log("> debounced", updatedRecent);
    setRecent(() => updatedRecent);
    timeoutId.current = null;
  }, [updatedRecent]);

  useLayoutEffect(() => {
    if (onSearch) {
      const element = ulRef.current;
      if (element.scrollHeight > element.clientHeight) {
        if (!timeoutId.current) {
          timeoutId.current = setTimeout(() => {
            console.log("update");
            setUpdatedRecent(() => [...recent].slice(1));
          });
        }
      }
    }
  }, [onSearch, ulRef, recent, timeoutId]);
// [...]

相关问题