React hooks:为什么useEffect需要详尽的依赖关系数组?

时间:2020-08-02 12:44:31

标签: javascript reactjs typescript react-hooks

我在React组件中有以下用例。

它是使用React Autosuggest的搜索用户输入。它的值始终是一个ID,因此我仅以用户ID作为道具。因此,在初次加载以显示用户名值时,我需要在初次安装时获取它。

编辑:我不想在以后更改时再次获取该值,因为我已经从建议请求中获取了该值。

type InputUserProps = {
  userID?: string;
  onChange: (userID: string) => void;
};

// Input User is a controlled input
const InputUser: React.FC<InputUserProps> = (props) => {
  const [username, setUsername] = useState<string | null>(null);

  useEffect(() => {
    if (props.userID && !username) {
      fetchUsername(props.userID).then((username) => setUsername(username));
    }
  }, []);

  async function loadSuggestions(inputValue: string): Suggestion[] {
    return fetchSuggestions(inputValue);
  }

  function selectSuggestion(suggestion: Suggestion): void {
    props.onChange(suggestion.userID); // I don't want to rerun the effect from that change...
    setUsername(suggestion.username); // ...because I set the username here
  }

  return (
    <InputSuggest
      value={props.userID}
      label={username}
      onSuggestionsRequested={loadSuggestions}
      onSuggestionSelected={selectSuggestion}
    />
  );
};

export default InputUser;

(我正在添加一种简化的方式来调用此组件)

const App: React.FC<AppProps> = (props) => {
    // simplified, I use react-hook-form in the real code
    const [userID, setUserID] = useState<string?>(any_initial_value_or_null);
    return <InputUser userID={userID} onChange={(newValue)=>setUserID(newValue)} />
};

export default App;

它可以工作,但是我的useEffect钩上有以下警告

React Hook useEffect has missing dependencies: 'props.userID' and 'username'. Either include them or remove the dependency array.eslint(react-hooks/exhaustive-deps)

但是,如果这样做,由于挂钩本身更改了用户名,我将多次运行它!由于它没有所有依赖项,因此我想知道:

  • 我该如何彻底解决我的案件?
  • 这些依赖关系是什么?为什么建议穷尽这些依赖关系?

3 个答案:

答案 0 :(得分:1)

看起来userId确实应该是一个依赖项,因为如果更改,则希望再次运行查询。

我认为您可以放弃username的支票,并且总是在userId发生变化时才提取:

useEffect(() => {
    if(props.userID) {
        fetchUsername(props.userID)
            .then((username) => setUsername(username))
    }
}, [props.userID])

通常来说,您希望列出效果中的所有闭包变量,以避免在执行效果时使用过时的引用。

-编辑以解决OP问题: 因为在您的用例中,您知道只希望对初始安装执行操作,所以通过一个空的依赖项数组是一种有效的方法。

另一种选择是跟踪获取的用户ID,例如

const fetchedIds = useRef(new Set())

每当获取新ID的用户名时,您都可以更新ref:

fetchedIds.current.add(newId)

效果可以测试:

if (props.userID && !fetchedIds.current.has(props.userID)) {
   // do the fetch
}

答案 1 :(得分:1)

有哪些依赖关系?

useEffect采用一个可选的第二个参数,它是一个依赖项数组。 useEffect钩子的依赖关系告诉它只要依赖关系发生变化,就可以运行效果。

如果您没有将可选的第二个参数传递给useEffect挂钩,则它将在每次重新渲染组件时执行。空的依赖关系数组指定您只希望在组件的初始渲染之后仅运行一次效果。在这种情况下,useEffect钩子的行为几乎类似于类组件中的componentDidMount。

为什么建议对它们详尽无遗?

效果从定义它们的渲染中看到propsstate。因此,当您在回调函数内部使用功能组件范围内的一些东西来参与react的数据流(例如props或state)时,在useEffect钩子的作用下,该回调函数函数将关闭该数据,并且除非使用props和state的新值定义了新效果,否则您的效果将看到陈旧的props和state值。

下面的代码片段演示了如果您对useEffect钩子的依赖项可能会出错。

在以下代码段中,有两个组成部分,AppUserApp组件具有三个按钮,并维护User组件显示的用户ID。用户ID作为道具从App传递到User组件,并且User组件从jsonplaceholder API中获取作为道具传递的ID的用户。

现在,以下代码段中的问题是它无法正常工作。原因是它与useEffect挂钩的依赖性有关。 useEffect钩子依赖于userID来从API中获取用户,但是由于我跳过了将userID作为依赖项添加的情况,useEffect钩子并非每次都{{1 }}属性更改。

userID
function User({userID}) {
  const [user, setUser] = React.useState(null);
  
  React.useEffect(() => {
    if (userID > 0) {
      fetch(`https://jsonplaceholder.typicode.com/users/${userID}`)
        .then(response => response.json())
        .then(user => setUser(user))
        .catch(error => console.log(error.message));
    }
  }, []); 
  
  return (
    <div>
      {user ? <h1>{ user.name }</h1> : <p>No user to show</p>}
    </div>
  );
}

function App() {
  const [userID, setUserID] = React.useState(0);
  
  const handleClick = (id) => {
    setUserID(id);
  };
  
  return(
    <div>
      <button onClick={() => handleClick(1)}>User with ID: 1</button>
      <button onClick={() => handleClick(2)}>User with ID: 2</button>
      <button onClick={() => handleClick(3)}>User with ID: 3</button>
      <p>Current User ID: {userID}</p>
      <hr/>
      <User userID={userID} />
    </div>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));

以上代码段显示了如果您依赖于依赖项可能会出现的几个问题之一,这就是为什么您不能跳过或依赖<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script> <div id="root"></div>钩子或具有依赖项数组的任何其他挂钩,例如useEffect挂钩。'

要修复先前的代码段,您只需将useCallback作为依赖项添加到userID钩子的依赖项数组中,以便在{{1} } prop发生更改,并且以等于useEffect prop的id提取新用户。

userID
userID

以您为例,如果您跳过在function User({userID}) { const [user, setUser] = React.useState(null); React.useEffect(() => { if (userID > 0) { fetch(`https://jsonplaceholder.typicode.com/users/${userID}`) .then(response => response.json()) .then(user => setUser(user)) .catch(error => console.log(error.message)); } }, [userID]); return ( <div> {user ? <h1>{ user.name }</h1> : <p>No user to show</p>} </div> ); } function App() { const [userID, setUserID] = React.useState(0); const handleClick = (id) => { setUserID(id); }; return( <div> <button onClick={() => handleClick(1)}>User with ID: 1</button> <button onClick={() => handleClick(2)}>User with ID: 2</button> <button onClick={() => handleClick(3)}>User with ID: 3</button> <p>Current User ID: {userID}</p> <hr/> <User userID={userID} /> </div> ); } ReactDOM.render(<App/>, document.getElementById('root'));钩子的依赖项数组中添加<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script> <div id="root"></div>,则当prop props.userID更改时,效果将不会获取新数据。

要了解有关省略useEffect钩子依赖性的负面影响的更多信息,请参阅:

我如何彻底解决我的案件?

由于效果取决于prop值userID,因此应将其包括在依赖项数组中,以在useEffect发生更改时始终获取新数据。

每次将userID更改时,将userID作为依赖项添加到props.userID钩子将触发效果,但是问题是您在{{1 }}。您应该删除它,因为不需要useEffect值,并且也不应决定何时应提取新的用户数据,因此不需要删除它。您只希望效果在props.userID更改时运行。

您还可以使用username钩子来管理和更新状态,从而使操作与状态更新脱钩。

编辑

由于即使useEffect钩子使用username时也只想运行效果,因此在您的情况下,可以使用空数组作为第二个参数,而忽略eslint警告。您也不能忽略props.userID钩子的任何依赖关系,并在useReducer钩子中使用某些条件,该条件在效果第一次运行并更新状态后评估为false。

我个人建议您尝试更改组件的结构,以免您一开始就不必处理此类问题。

答案 2 :(得分:0)

如果您确定在安装InputUser组件之前,所有从属道具都已填充且它们具有正确的值,则在eslint-disable-next-line react-hooks/exhaustive-deps行之前添加}, []),但如果从属道具没有值在第一次安装组件时,因此您必须将它们添加到useEffect依赖项中。