Hooks出来已经有段时间了,相信你们都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个setInterval
的坑,和小伙伴们分享下解决方案。javascript
写个count
每秒自增的定时器,以下写法结果,界面上count
为1
?html
function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
复制代码
codesandbox.io/embed/hooks…java
若是某些特定值在两次重渲染之间没有发生变化,你能够通知
React
跳过对effect
的调用。就是将第二个参数改为[]
,相似于更接近类组件的componentDidMount
和componentWillUnmount
生命周期,只执行一次。effect
的第二个参数中传入的值就是 它更改的话,effect
也会从新执行一遍的值。react
由于Effect
的第二个参数为[]
,没有依赖,Effect
只会执行一次。setInterval
中拿到的是第一次渲染时的闭包count
,因此count
永远是0
,界面会一直显示1
,以下所示:git
function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(0 + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
复制代码
那有些小伙伴会说,若是咱们直接往第二个参数加count
呢github
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次
}
复制代码
那到底要怎么作才能有保障性能,定时器只监听一次,又使定时器起做用呢?app
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>;
}
复制代码
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>;
}
复制代码
思考:为何不直接
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>;
}
复制代码
下面的例子能够很好的解释。假如把myRef.current
为cur
变量,定时器的第一个参数为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
,方便重复使用。
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>;
}
复制代码
将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>;
}
复制代码
还有什么好的方案欢迎小伙伴们留言评论~~
Happy coding .. :)
raoenhui.github.io/react/2019/…
zh-hans.reactjs.org/docs/hooks-…