useInterval
First Attempt
この例では、もしre-renderingが発生した場合にclearIntervalが実行されるのが早すぎてしまい、タイミングがずれてしまう
CustomHooksをと来るときはre-renderingのタイミングとunmountのタイミングが適切かどうかを確認する必要がある
code: first_attempt.js
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
function Counter() {
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
});
return <h1>{count}</h1>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
Scond Attempt
この例では、useEffectの第2引数に[]を入れて初期のレンダリングのみで実行している。
これだと値が更新されないために、countが増えていかない
その代わりに setCount(count + 1)の部分をsetCount(count => count + 1)にしてみるとうまくいく。
しかし、このようにすると再利用性がなくなってしまい、propsに対しての変更を加えられなくなってしまう
またsetIntervalの中ではクロージャーになってしまうので、propsの変更が反映されなかったりバグを生む可能性がある
その為、setInterval内の関数は常に新しい関数にリセットする必要がある
新しい関数にリセットするためにはuseRefを使う
code: second_attempt.js
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
function Counter() {
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
Best Attempt
useRefを使うことでsetInterval内の関数を最新の状態に保っている
code: best_attempt.js
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
function Counter() {
useInterval(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>;
}
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
useEffect(() => {
function tick() {
savedCallback.current();
}
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
参考