React-Native 使用 React Hooks 声明 setInterval

React-Native Making setInterval Declarative with React Hooks

我已经使用 React Hooks 几个小时了,我可能 运行 遇到了一个有趣的问题:使用 setInterval 并不像我对 react-native 的预期那样有效

function Counter() {
  const [time, setTime] = useState(0);
  const r = useRef(null);
  r.current = { time, setTime };
  useEffect(() => {
    const id = setInterval(() => {
      console.log("called");
      r.current.setTime(r.current.time + 1);
    }, 1000);
    return () => {
      console.log("cleared");
      clearInterval(id);
    };
  }, [time]);

  return <Text>{time}</Text>;
}

上面的代码应该在每次 time 状态改变时清除间隔 它在 ReactJS 上工作正常,但在 React-native 上我收到一条错误消息 "Callback() it's not a function"

enter image description here

它在 Reactjs 中按预期工作

https://codesandbox.io/s/z69z66kjyx

"dependencies": {
    "react": "16.8.3",
    "react-native": "^0.59.6",
    ...}

更新: 我尝试像这个例子一样使用 ref 但仍然得到相同的错误

const [time, setTime] = useState(0);
        useInterval(() => {
            setTime(time +1);
        });

        return (<Text>{time}</Text>);
}

function useInterval(callback) {
    const savedCallback = useRef();

    // Remember the latest function.
    useEffect(() => {
        savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useEffect(() => {
            let id = setInterval(()=>savedCallback.current(), delay);
            return () => clearInterval(id);
    });
}

您应该仍然可以在效果挂钩中使用状态挂钩变量,因为它们在范围内。

useRef:此处的突变未被跟踪,因此它们不会触发 re-renders。

CodeSandbox Counter Example

我觉得以您尝试的方式使用 refs 比直接使用状态和 setter 更冗长。 useRef ref 用于超过 时间的可变值,但您已经通过 useState 钩子获得了它。 ref 起作用是因为你并没有真正改变 ref,而是在每个渲染周期简单地用 useState 挂钩的内容覆盖它 updated.

我已经更新了我的沙箱以像您一样使用 useRef,您的 useEffect 挂钩导致您的清理函数在每次渲染时触发,因此删除了依赖项。您会注意到现在您只能看到 "called",直到您刷新。

因为您正在通过 DOM 节点引用来改变 DOM 并且 DOM 突变将在渲染和渲染之间改变 DOM 节点的外观你的效果改变了它。那么你不需要使用 useEffect 你会想要使用 useLayoutEffect

useLayoutEffect 此 运行 在 React 执行所有 DOM 突变后立即同步。

import React, {useState, useLayoutEffect,useRef} from 'react';
import { Text} from 'react-native';

const [time, setTime] = useState(0);
        useInterval(() => {
            setTime(time +1);
        });

        return (<Text>{time}</Text>);
}

function useInterval(callback) {
    const savedCallback = useRef();

    // Remember the latest function.
    useLayoutEffect(() => {
        savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useLayoutEffect(() => {
            let id = setInterval(()=>{
              console.log('called');
              return savedCallback.current();
            }, delay);
            return () => {
                console.log('cleared');
                return clearInterval(id);
            }
    });
}

如果您只是使用 useEffect 并收到此错误

Uncaught TypeError: callback is not a function at flushFirstCallback (scheduler.development.js:348) at flushWork (scheduler.development.js:441) at MessagePort.channel.port1.onmessage (scheduler.development.js:188)

这是 RN 中的一个错误,因为 scheduler 版本错误,不幸的是,RN 没有明确依赖于错误的调度程序版本。 Dan Abramov 已经在调度程序版本 "0.14.0"

上修复了这个错误

解决问题只需运行以下命令

npm install scheduler@0.14.0 --save

或者尝试将 "scheduler": "0.14.0" 添加到 dependencies 中的 package.json 和 re-running 您的包管理器