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遇到的问题 #19

Open
lulusir opened this issue Mar 11, 2019 · 4 comments
Open

记录使用Hooks遇到的问题 #19

lulusir opened this issue Mar 11, 2019 · 4 comments

Comments

@lulusir
Copy link
Owner

lulusir commented Mar 11, 2019

定时器没有获取到最新的state

定时器中的count 会一直会0,
因为定时器一直引用着旧的变量。
demo

function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      // 下面的count 会一直为0,因为定时器一直引用着旧的变量。
      // 
      console.log(count);
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

解决方法:
用一个变量来保存引用值,在每次react执行时更新。既然使用hook,我们可以用useRef来保存这个引用
demo

function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(null);
  // 更新引用值,
  countRef.current = count;
  useEffect(() => {
    const timer = setInterval(() => {
      // 这里的引用值会定期更新
      console.log(count, countRef.current, " == count, countRef.current");
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

或者在外层作用域保存这个变量(注意这里会出现多个组件引用同一个变量的问题:参考3楼的demo)

let val = 0
function App() {
  const [count, setCount] = useState(0);
  // 更新引用值,
  val = count;
  useEffect(() => {
    const timer = setInterval(() => {
      // 这里的引用值会定期更新
      console.log(count, val, " == count, obj.current");
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

我们可以把这个功能包装成hooks,demo

function useRefState(initialState) {
  const [state, setState] = useState(initialState);
  const stateRef = useRef(null);
  stateRef.current = state;
  return [state, setState, stateRef];
}

function App() {
  const [count, setCount, countRef] = useRefState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      // 这里的引用值会定期更新
      console.log(count, countRef.current, " == count, countRef.current");
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

参考 facebook/react#14543

@liyuanqiu
Copy link

函数每次执行都更新 current 确实没问题,但是从更加精确的控制角度来讲,是不是这样会更好:

useEffect(() => {
  stateRef.current = state;
}, [state]);

let val = 0;

在函数组建作用域外使用可变变量,会导致该组件同时只能使用一个。

@lulusir
Copy link
Owner Author

lulusir commented Apr 8, 2019

@liyuanqiu 嗯,像你这样比对更新的确会更好。

function useRefState(initialState) {
  const [state, setState] = useState(initialState);
  const stateRef = useRef(null);
  useEffect(() => {
    stateRef.current = state;
  }, [state]);
  return [state, setState, stateRef];
}

在函数组建作用域外使用可变变量,会导致该组件同时只能使用一个。

这个是什么意思?

@liyuanqiu
Copy link

A Button component:

let sum = 0;
function Button({
  name,
}) {
  function handleClick() {
    sum += 1;
    console.log(`Button(${name}) has been clicked for ${sum} times!`);
  }
  return (
    <button
      onClick={handleClick}
    >
      click me!
    </button>
  );
}

In your app:

function App() {
  return (
    <Button name="button-1" />
    <Button name="button-2" />
  );
}

See:
https://codesandbox.io/s/w0rjxro1q7
(Open console to see logs)

@hzfvictory
Copy link

第一个例子
像这样 setCount(precount => precount + 1);
他拿到的是上一次的count值,他会一直改变count ,虽然定时器里面的count,不会改变,但是页面上的值,会一直跟随者定时器来更新

第二个例子你使用了useRef()

 setCount(countRef.current + 1);

你大可不必用函数的方式,因为这种是间接拿到的是count 值

import React, {Fragment, useState, useEffect, useRef, useCallback} from "react";

const useInterval = (callback: any, val: any) => {
    const saveCallback = useRef();
    useEffect(() => {
        saveCallback.current = val
    },[val]);

    useEffect(() => {
        let id = setInterval(() => callback(saveCallback.current), 1000);
        return () => clearInterval(id);
    }, []);
};

function App() {
    const [count, setCount] = useState(0);

    useInterval((val: number) => {
        setCount(val + 1);
    }, count);

    return (
        <Fragment>
            <p> count: {count} </p>
        </Fragment>
    );
}

export default App

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

3 participants