useEffect 中缺少依赖项的 eslint 警告

时间:2021-04-09 10:52:51

标签: reactjs react-hooks

我正在制作一个可搜索的下拉菜单并收到以下 eslint 警告:

<块引用>

React Hook useEffect 缺少依赖项:“filterDropDown”和“restoreDropDown”。包括它们或删除依赖项数组。

import React, { useState, useEffect, useCallback } from "react";
const SearchableDropDown2 = () => {
  const [searchText, setSearchText] = useState("");
  const [dropdownOptions, setDropdownOptions] = useState([
    "React",
    "Angular",
    "Vue",
    "jQuery",
    "Nextjs",
  ]);
  const [copyOfdropdownOptions, setCopyOfDropdownOptions] = useState([
    ...dropdownOptions,
  ]);
  const [isExpand, setIsExpand] = useState(false);

  useEffect(() => {
    searchText.length > 0 ? filterDropDown() : restoreDropDown();
  }, [searchText]);

  const onClickHandler = (e) => {
    setSearchText(e.target.dataset.myoptions);
    setIsExpand(false);
  };

  const onSearchDropDown = () => setIsExpand(true);

  const closeDropDownHandler = () => setIsExpand(false);

  const filterDropDown = useCallback(() => {
    const filteredDropdown = dropdownOptions.filter((_) =>
      _.toLowerCase().includes(searchText.toLowerCase())
    );
    setDropdownOptions([...filteredDropdown]);
  }, [dropdownOptions]);

  const restoreDropDown = () => {
    if (dropdownOptions.length !== copyOfdropdownOptions.length) {
      setDropdownOptions([...copyOfdropdownOptions]);
    }
  };

  const onSearchHandler = (e) => setSearchText(e.target.value.trim());
  return (
    <div style={styles.mainContainer}>
      <input
        type="search"
        value={searchText}
        onClick={onSearchDropDown}
        onChange={onSearchHandler}
        style={styles.search}
        placeholder="search"
      />
      <button disabled={!isExpand} onClick={closeDropDownHandler}>
        -
      </button>
      <div
        style={
          isExpand
            ? styles.dropdownContainer
            : {
                ...styles.dropdownContainer,
                height: "0vh",
              }
        }
      >
        {dropdownOptions.map((_, idx) => (
          <span
            onClick={onClickHandler}
            style={styles.dropdownOptions}
            data-myoptions={_}
            value={_}
          >
            {_}
          </span>
        ))}
      </div>
    </div>
  );
};

const styles = {
  mainContainer: {
    padding: "1vh 1vw",
    width: "28vw",
    margin: "auto auto",
  },
  dropdownContainer: {
    width: "25vw",
    background: "grey",
    height: "10vh",
    overflow: "scroll",
  },
  dropdownOptions: {
    display: "block",
    height: "2vh",
    color: "white",
    padding: "0.2vh 0.5vw",
    cursor: "pointer",
  },
  search: {
    width: "25vw",
  },
};

我尝试将 filterDropDown 包裹在 useCallback 中,但随后可搜索下拉菜单停止工作。

以下是包含 useCallback 的更改:

const filterDropDown = useCallback(() => {
  const filteredDropdown = dropdownOptions.filter((_) =>
    _.toLowerCase().includes(searchText.toLowerCase())
  );
  setDropdownOptions([...filteredDropdown]);
}, [dropdownOptions, searchText]);

2 个答案:

答案 0 :(得分:2)

我的建议是,在您实现目标的情况下,根本不要使用 useEffect

import React, {useState} from 'react';

const SearchableDropDown = () => {
    const [searchText, setSearchText] = useState('');
    const dropdownOptions = ['React','Angular','Vue','jQuery','Nextjs'];
    const [isExpand, setIsExpand] = useState(false);


    const onClickHandler = e =>  {
        setSearchText(e.target.dataset.myoptions);
        setIsExpand(false);
    }

    const onSearchDropDown = () => setIsExpand(true);
    const closeDropDownHandler = () => setIsExpand(false);
    const onSearchHandler = e => setSearchText(e.target.value.trim());

    return (
        <div style={styles.mainContainer}>
            <input
                type="search"
                value={searchText}
                onClick={onSearchDropDown}
                onChange={onSearchHandler}
                style={styles.search}
                placeholder="search"
            />
            <button disabled={!isExpand} onClick={closeDropDownHandler}>
                -
            </button>
            <div
                style={
                    isExpand
                        ? styles.dropdownContainer
                        : {
                                ...styles.dropdownContainer,
                                height: '0vh',
                            }
                }
            >
                {dropdownOptions
                    .filter(opt => opt.toLowerCase().includes(searchText.toLowerCase()))
                    .map((option, idx) =>
                        <span key={idx} onClick={onClickHandler} style={styles.dropdownOptions} data-myoptions={option} value={option}>
                            {option}
                        </span>
                    )}
            </div>
        </div>
    );
};

const styles = {
    mainContainer: {
        padding: '1vh 1vw',
        width: '28vw',
        margin: 'auto auto'
    },
    dropdownContainer: {
        width: '25vw',
        background: 'grey',
        height: '10vh',
        overflow: 'scroll'
    },
    dropdownOptions: {
        display: 'block',
        height: '2vh',
        color: 'white',
        padding: '0.2vh 0.5vw',
        cursor: 'pointer',
    },
    search: {
        width: '25vw',
    },
};

只需过滤 dropDownOptions,然后再将它们映射到 span 元素。

此外,如果 dropDownOptions 的列表会改变,那么使用 setState 否则只需将其保留为一个简单的列表。如果 dropDownOptions 来自 api 调用,则在 setState 挂钩中使用 useEffect

答案 1 :(得分:0)

您当前的代码:

// 1. Filter

const filterDropDown = useCallback(() => {
  const filteredDropdown = dropdownOptions.filter((_) =>
    _.toLowerCase().includes(searchText.toLowerCase())
  );
  setDropdownOptions([...filteredDropdown]);
}, [dropdownOptions]);

// 2. Restore

const restoreDropDown = () => {
  if (dropdownOptions.length !== copyOfdropdownOptions.length) {
    setDropdownOptions([...copyOfdropdownOptions]);
  }
};

// 3. searchText change effect

useEffect(() => {
  searchText.length > 0 ? filterDropDown() : restoreDropDown();
}, [searchText]);

以上代码产生 eslint 警告:

<块引用>

React Hook useEffect 缺少依赖项:“filterDropDown”和“restoreDropDown”。包括它们或删除依赖数组

但是按照警告的内容做了之后,我们的代码最终看起来是这样的:

// 1. Filter

const filterDropDown = useCallback(() => {
  const filteredDropdown = dropdownOptions.filter((_) =>
    _.toLowerCase().includes(searchText.toLowerCase())
  );
  setDropdownOptions(filteredDropdown);
}, [dropdownOptions, searchText]);

// 2. Restore

const restoreDropDown = useCallback(() => {
  if (dropdownOptions.length !== copyOfdropdownOptions.length) {
    setDropdownOptions([...copyOfdropdownOptions]);
  }
}, [copyOfdropdownOptions, dropdownOptions.length]);

// 3. searchText change effect

useEffect(() => {
  searchText.length > 0 ? filterDropDown() : restoreDropDown();
}, [filterDropDown, restoreDropDown, searchText]);

但是上面的代码已经形成了一个无限循环,因为:

  1. “过滤”功能正常。 (它会在 searchTextdropdownOptions 更改时重新创建。这是有道理的。)
  2. “恢复”功能正常。 (它会在 copyOfdropdownOptionsdropdownOptions.length 更改时重新创建。这也是有道理的。)
  3. searchText 变化效果看起来糟糕。此效果将在以下情况下运行:
    =>(一)。 searchText 已更改 (OK),或
    => (b)。 “过滤器”函数被改变(不行因为当这个钩子运行时这个函数本身会改变。点1),或者
    => (c)。 "Restore" 函数已更改(不行,因为运行此钩子时,此函数本身会更改。第 2 点)

我们可以清楚地看到第 3 点(a、b、c)中的无限循环。

如何解决?

可能有几种方法。一种可能是:

下面的copy是常量,所以要么把它保存在一个Ref中,要么把它移到组件定义之外:

const copyOfdropdownOptions = useRef([...dropdownOptions]);

并且,移动钩子内部的“过滤器”和“恢复”函数(即不需要在外部定义它并将其作为依赖项),如下所示:

useEffect(() => {
  if (searchText.length) {
    // 1. Filter
    setDropdownOptions((prev) =>
      prev.filter((_) => _.toLowerCase().includes(searchText.toLowerCase()))
    );
  } else {
    // 2. Restore
    setDropdownOptions([...copyOfdropdownOptions.current]);
  }
}, [searchText]);

如您所见,上述效果仅在更改 searchText 时才会运行。