【问题标题】:eslint warning for missing dependency in useEffecteslint 警告 useEffect 中缺少依赖项
【发布时间】:2021-04-09 10:52:51
【问题描述】:

我正在制作一个可搜索的下拉菜单并收到以下 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]);

【问题讨论】:

标签: reactjs react-hooks


【解决方案1】:

我的建议是在你正在实现的目标中根本不要使用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 调用,则在 useEffect 挂钩中使用 setState

【讨论】:

    【解决方案2】:

    您当前的代码:

    // 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 已更改(确定),或
      => (b)。 “过滤器”功能已更改(不正常,因为此挂钩运行时此功能本身会更改。第 1 点),或
      => (c)。 “恢复”功能已更改(不正常,因为此挂钩运行时此功能本身会更改。第 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 时才会运行。

    【讨论】:

    • 哎呀!我看到一个错误:我们需要始终过滤原始数据:setDropdownOptions(copyOfdropdownOptions.current.filter((_) =&gt; _.toLowerCase().includes(searchText.toLowerCase()))
    猜你喜欢
    • 2020-05-28
    • 2021-08-13
    • 2020-11-21
    • 2020-05-06
    • 1970-01-01
    • 2022-04-04
    • 2021-12-07
    • 1970-01-01
    • 2020-09-22
    相关资源
    最近更新 更多