import Taro, { useState, useEffect } from '@tarojs/taro'
import { View } from '@tarojs/components'
export default function Loading(props) {
const [ellipsis, setEllipsis] = useState('.')
useEffect(() => {
const timer = setInterval(() => {
console.log(ellipsis)
if (ellipsis.length === 6) {
setEllipsis('.')
} else {
setEllipsis(val => val + '.')
}
}, 500)
return () => {
clearInterval(timer)
}
}, [])
return (
<View className="container">
<View className="hint">{props.title + ellipsis}</View>
</View>
)
}
Loading.defaultProps = {
title: '疯狂加载中',
}
效果是视图上会每 500ms 在省略号上多加一个 .,但是 js 里 ellipsis 永远是 .,并没有更新到最新状态。
欢迎提交 Issue~
如果你提交的是 bug 报告,请务必遵循 Issue 模板的规范,尽量用简洁的语言描述你的问题,最好能提供一个稳定简单的复现。🙏🙏🙏
如果你的信息提供过于模糊或不足,或者已经其他 issue 已经存在相关内容,你的 issue 有可能会被关闭。
Good luck and happy coding~
定时器 在hooks 不是这样用的,可以查找 react hooks setInterval
@renshengwudi 非常感谢。
我去搜了 Dan 的这篇文章:https://overreacted.io/making-setinterval-declarative-with-react-hooks/#second-attempt
里面就提到了这个常犯的错误,是由于闭包引起的:
例子:
function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
这里的 count 会一直是 1。因为 setInterval 会拿到一个过期的 count(称作 "stale state")。原话解释:
The problem is that
useEffectcaptures thecountfrom the first render. It is equal to0. We never re-apply the effect so the closure insetIntervalalways references thecountfrom the first render, andcount + 1is always1.
(但我还不是很能理解为什么这里会由于闭包呢?是和 setCount 还是 useEffect 的内部实现有关系吗?)
解决方法之一,是把 setCount(count + 1) 改成 setCount(c => c + 1)。这样可以确保一直拿到的是新鲜的 count(称作 "fresh state")。
但是我在 Taro 中使用碰到的上述问题里,确实是使用了这种 setCount(c => c + 1) 的方式呀。而且即便 useEffect 会因为闭包而拿到 stale state,jsx 中也不应该有不同的表现。 这是不是 Taro 的一个 bug 呢? @luckyadam
然后是关于 setInterval 在 React 中究竟该咋用的一些讨论——
React 官方文档里用了 setInterval 为例子来解释 useRef() 的作用:https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables 。但我不确定这是不是意味着 setInterval 最好就这么用。
Dan 的那篇文章里也是用了 useRef() 来实现,而且还自己包装了一个 useInterval():
https://overreacted.io/making-setinterval-declarative-with-react-hooks/#just-show-me-the-code ,具体代码:
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
但在我看来,这也太「过度实现」了。之前有生命周期的 class 写法,反而更直观。或许也还是我思维还没转变过来,useEffect(() => {}, []) 并不能简单地当成是 componentDidMount 和 componentWillUnmount 的替代。
那么咋办呢。最后,我还是选择了用 setTimeout 来代替实现,避免使用 setInterval。就像 Ryan 在 React Conf 2018 上实现的那样。(https://youtu.be/dpw9EHDh2bM?t=4537)
function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
let id = setTimeout(() => {
setCount(count + 1);
}, 1000);
return () => clearTimeout(id);
}, [count]);
return <h1>{count}</h1>;
}
```
@Songkeys
(但我还不是很能理解为什么这里会由于闭包呢?是和
setCount还是useEffect的内部实现有关系吗?)
并不是什么内部实现的问题,理解闭包的基本原理就可以理解,函数在哪里定义,就从此作用域开始往上寻找引用的变量而已。
但是我在 Taro 中使用碰到的上述问题里,确实是使用了这种
setCount(c => c + 1)的方式呀。而且即便useEffect会因为闭包而拿到 stale state,jsx 中也不应该有不同的表现。 这是不是 Taro 的一个 bug 呢?
你 setCount 用法是对的,但你直接用 ellipsis 进行判断: if (ellipsis.length === 6) ,根据闭包可得 ellipsis 每次渲染都会为 '.'。
解决办法很简单,用 useRef 保存最新的 ellipsis,判断时取出来判断。
function Index() {
const [ellipsis, setEllipsis] = useState('.')
const ellRef = useRef()
ellRef.current = ellipsis
useEffect(() => {
const timer = setInterval(() => {
console.log(ellRef.current)
if (ellRef.current.length === 6) {
setEllipsis('.')
} else {
setEllipsis(val => val + '.')
}
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return (
<div>
<div>{ellipsis}</div>
</div>
)
}
Hello~
您的问题楼上已经有了确切的回答,如果没有更多的问题这个 issue 将在 15 天后被自动关闭。
如果您在这 15 天中更新更多信息自动关闭的流程会自动取消,如有其他问题也可以发起新的 Issue。
Good luck and happy coding~
你 setCount 用法是对的,但你直接用 ellipsis 进行判断:
if (ellipsis.length === 6),根据闭包可得 ellipsis 每次渲染都会为 '.'。解决办法很简单,用 useRef 保存最新的 ellipsis,判断时取出来判断。
@Chen-jj @Songkeys 的确是由于useEffect没有ellipsis状态的依赖,导致useEffect只执行了一次,并且不会执行clearInterval,所以程序一直在跑。
那么直接在useEffect的依赖里加上ellipsis不就解决了吗。虽然useRef也能解决问题但是我觉得完全没必要啊,还是有其他场景我没考虑到呢。
你 setCount 用法是对的,但你直接用 ellipsis 进行判断:
if (ellipsis.length === 6),根据闭包可得 ellipsis 每次渲染都会为 '.'。
解决办法很简单,用 useRef 保存最新的 ellipsis,判断时取出来判断。@Chen-jj @Songkeys 的确是由于
useEffect没有ellipsis状态的依赖,导致useEffect只执行了一次,并且不会执行clearInterval,所以程序一直在跑。
那么直接在useEffect的依赖里加上ellipsis不就解决了吗。虽然useRef也能解决问题但是我觉得完全没必要啊,还是有其他场景我没考虑到呢。
sorry,反复阅读了上面提到的dan的那篇文章,终于理解了:
When we run clearInterval and setInterval, their timing shifts. If we re-render and re-apply effects too often, the interval never gets a chance to fire!
We can see the bug by re-rendering our component within a smaller interval:
https://codesandbox.io/s/9j86r218y4
Most helpful comment
@Songkeys
并不是什么内部实现的问题,理解闭包的基本原理就可以理解,函数在哪里定义,就从此作用域开始往上寻找引用的变量而已。
你 setCount 用法是对的,但你直接用 ellipsis 进行判断:
if (ellipsis.length === 6),根据闭包可得 ellipsis 每次渲染都会为 '.'。解决办法很简单,用 useRef 保存最新的 ellipsis,判断时取出来判断。