19

React Hooks与setInterval

 4 years ago
source link: https://segmentfault.com/a/1190000020937799
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。

前言

写个 count 每秒自增的定时器,如下写法结果,界面上 count1

function Counter() {
  let [count, setCount] = useState(0);
  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <h1>{count}</h1>;
}

https://codesandbox.io/embed/hooks-setinterval-error-w4qu6

如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用。就是将第二个参数改成 [] ,类似于更接近类组件的 componentDidMountcomponentWillUnmount 生命周期,只执行一次。 effect 的第二个参数中传入的值就是 它更改的话, effect 也会重新执行一遍的值。

因为 Effect 的第二个参数为 [] ,没有依赖, Effect 只会执行一次。 setInterval 中拿到的 count 永远是 0 ,界面会一直显示 1 ,如下所示:

function Counter() {
  let [count, setCount] = useState(0);
  useEffect(() => {
    let id = setInterval(() => {
      setCount(0 + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
 return <h1>{count}</h1>;
}

那有些小伙伴会说,如果我们直接往第二个参数加 count

function Counter() {
//... 
useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [count]);
//...
}

这样效果是对的,但是性能不好。每当 count 更改了, useEffect 就会渲染一次,定时器也会不停的被新增与移除。如下所示:

//第一次
function Counter() {
//... 
useEffect(() => {
    let id = setInterval(() => {
      setCount(0 + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [0]);
//...
}
//第二次
function Counter() {
//... 
useEffect(() => {
    let id = setInterval(() => {
      setCount(1 + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [1]);
//...
//第N次
}

那到底要怎么做才能有保障性能,定时器只监听一次,又使定时器起作用呢?

方案一、函数式更新

useState 中的set方法可接收函数,该函数将接收先前的 state ,并返回一个更新后的值。这样定时器每次拿到的是最新的值。

function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
    let id = setInterval(() => {
      setCount(v => {
        return v + 1;
      });
    }, 1000);
    return () => clearInterval(id);
  }, []);
return <h1>{count}</h1>;
}

https://codesandbox.io/embed/hooks-setinterval-usestate-grres

方案二、用useRef

useRef 返回一个可变的 ref 对象,返回的 ref 对象在组件的整个生命周期内保持不变。

将定时器函数提取出来,每次定时器触发时,都能取到最新到 count .

function Counter() {
  let [count, setCount] = useState(0);
  const myRef = useRef(null);
  myRef.current = () => {
    setCount(count + 1);
  };
  useEffect(() => {
    let id = setInterval(() => {
      myRef.current();
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <h1>{count}</h1>;
}

https://codesandbox.io/embed/hooks-setinterval-useref-cgif3

思考:为什么不直接 setInterval(myRef.current, 1000) 这样写不行呢,还要包个方法返回?

function Counter() {
  let [count, setCount] = useState(0);
  const myRef = useRef(null);
  myRef.current = () => {
    setCount(count + 1);
  };
  useEffect(() => {
    let id = setInterval(myRef.current, 1000);
    return () => clearInterval(id);
  }, []);
 return <h1>{count}</h1>;
}

https://codesandbox.io/embed/hooks-setinterval-useref-error-52dm0

下面的例子可以很好的解释。假如把 myRef.currentcur 变量,定时器的第一个参数为 interval 变量, cur 变量更改, interval 的取的还是之前赋值的值。

var cur=()=>{var count=0;console.log(count)};
var interval=cur;
var cur=()=>{var count=1;console.log(count)};
interval();//0

var cur=()=>{var count=0;console.log(count)};
var interval=()=>{cur()};
var cur=()=>{var count=1;console.log(count)};
interval();//1

方案三、自定义hook

可以写个自定义 hook ,方便重复使用。

function useInterval(fun) {
  const myRef = useRef(null);
  useEffect(() => {
    myRef.current = fun;
  }, [fun]);
  useEffect(() => {
    let id = setInterval(() => {
      myRef.current();
    }, 1000);
    return () => clearInterval(id);
  }, []);
}

function Counter() {
  let [count, setCount] = useState(0);
  useInterval(() => {
    setCount(count + 1);
  });
  return <h1>{count}</h1>;
}

https://codesandbox.io/embed/hooks-setinterval-ownhooks-0tpxe

方案四、用useReducer

count 变量存入 reducer 中,使用 useReducer 更新 count

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, 0);
  useEffect(() => {
    setInterval(() => {
      dispatch({ type: "increment" });
    }, 1000);
  }, []);
  return <h1>{state}</h1>;
}

https://codesandbox.io/embed/hooks-setinterval-usereducer-2byrm

还有什么好的方案欢迎小伙伴们留言评论~~

Happy coding .. :)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK