Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hooks 陷阱 #37

Open
shaozj opened this issue Apr 13, 2020 · 0 comments
Open

hooks 陷阱 #37

shaozj opened this issue Apr 13, 2020 · 0 comments

Comments

@shaozj
Copy link
Owner

shaozj commented Apr 13, 2020

本文从实际示例出发,分析 hooks 在使用时容易遇到的陷阱,以及提出如何避免掉进 hooks 陷阱的方法。

问题一

下方的代码存在什么问题?

import React, { useState, useEffect } from 'react';

function App() {
  const [data, setData] = useState();
  useEffect(async () => {
    const result = await fetch(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
    setData(result.data);
  }, []);
  return (
    <div>
      {data}
    </div>
  );
}
export default App;


答:会出现 warning,useEffect 应该返回一个清理函数或者什么都不返回。 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => ...) are not supported, but you can call an async function inside an effect.


正确写法:

useEffect(() => {
  const fetchData = async () => {
    const result = await fetch(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
    setData(result.data);
  };
  fetchData();
}, []);

问题二

下方强制更新组件的代码能 work 吗?

const setUpdate = useState()[1];
const forceUpdate = () => setUpdate();

forceUpdate();


答:不能 work,在函数式组件中,如果 setState 没有修改对应 state 的值(浅比较相等),那么不会触发 re-render,这点和 class 组件不同,class 组件只要 setState 就会触发 re-render,因为它每次会合并 state 中所有属性,生成一个新的 state。

正确写法:

const setUpdate = useState()[1];
const forceUpdate = () => setUpdate({});

forceUpdate();

问题三

下方代码存在什么问题?

function Timer() {
    const [count, setCount] = React.useState(0);

    useEffect(() => {
        const intervalId = setInterval(() => {
            setCount(count + 1);
        }, 1000);
        return () => clearInterval(intervalId);
    }, []);

    return (
        <div>The count is: {count}</div>
    );
}


答:这是一个最常见的说明 useEffect 陷阱的例子了。在上方的代码运行后,count 首先为 0,一秒后更新为 1,之后就不变化了。这是由闭包导致的,useEffect 中的 count 值始终为 0,setInterval 每次执行都将其更新为 1。


方案1:依赖数组中每个依赖的外部变量都添加进去(性能不佳)

function Timer() {
    const [count, setCount] = React.useState(0);

    useEffect(() => {
        const intervalId = setInterval(() => {
            setCount(count + 1);
        }, 1000);
        return () => clearInterval(intervalId);
    }, [count]);

    return (
        <div>The count is: {count}</div>
    );
}


方案2:使用 useRef 生成一个可变对象,记住我们的变量(个人最推荐,适用于所有需要依赖外部变量的情况)

function Timer() {
    const [count, setCount] = React.useState(0);
    const countRef = React.useRef(0);

    useEffect(() => {
        const intervalId = setInterval(() => {
            countRef.current = countRef.current + 1;
            setCount(countRef.current);
        }, 1000);
        return () => clearInterval(intervalId);
    }, []);

    return (
        <div>The count is: {count}</div>
    );
}


方案3:如果是依赖外部变量做 setState,可以用 [functional-updates](https://reactjs.org/docs/hooks-reference.html#functional-updates) 然后把依赖的变量去除(很适合这个例子,但是适用范围有限)
function Timer() {
    const [count, setCount] = React.useState(0);

    useEffect(() => {
        const intervalId = setInterval(() => {
            setCount(count => count + 1);
        }, 1000);
        return () => clearInterval(intervalId);
    }, []);

    return (
        <div>The count is: {count}</div>
    );
}

问题四

我想使用类似于 component 组件中的 this 该怎么办,用它跟踪变量的变化,但是不触发 re-render.


答:这个问题前面其实已经给出答案了,那就是使用 useRef。

问题五

以下代码存在什么问题?该如何修改?

let stopPolling = false;

function pay({ visible }) {
  const [data, setData] = useState();
  
	function polling() {
    if (stopPolling) {
      return;
    }
  	fetch().then(
    	if (res.finished) {
    		setData(res.data);
    	} else {
       	setTimeout(polling, 1000); 
      }
    );
  }
  
  useEffect(() => {
  	stopPolling = !visible;
  }, [visible]);
  
  return <div onClick={polling}>{data}</div>;
}

最后这个问题留作课后作业吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant