問題描述
使用 useEffect 和 setInterval 一個接一個地打印一個字母 (print one letter after the other with useEffect and setInterval)
我試圖了解使用 useEffect
和 創建每隔一段時間(如打字機)打印一個字母的組件的最佳方法是什麼setInterval
- 這樣會導致interval永遠運行:
function Printer({ str }) {
const [val, setVal] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setVal((preVal) => preVal + 1);
}, 200);
return () => {
clearInterval(interval);
};
}, [str]);
return <div>{str.slice(0, val)}</div>;
}
- 而這個會抱怨
val
是 useEffect 依賴數組的缺失依賴:
function Printer({ str }) {
const [val, setVal] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
if (val < str.length) {
setVal((preVal) => preVal + 1);
} else {
clearInterval(interval);
}
}, 200);
return () => {
clearInterval(interval);
};
}, [str]);
return <div>{str.slice(0, val)}</div>;
}
- 如果將
val
添加到依賴數組中 ‑ 我們設置了多個冗餘間隔(並刪除它們,所以這感覺有點糟糕):
function Printer({ str }) {
const [val, setVal] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
if (val < str.length) {
setVal((preVal) => preVal + 1);
} else {
clearInterval(interval);
}
}, 200);
return () => {
clearInterval(interval);
};
}, [str, val]);
return <div>{str.slice(0, val)}</div>;
}
不確定我是否遺漏了有關 useEffect 的內容,依賴數組或者這只是 linter 拖釣我。
參考解法
方法 1:
You can use useRef()
to store the end condition (stop
):
const { useState, useRef, useEffect } = React;
function Printer({ str }) {
const [val, setVal] = useState(0);
const stop = useRef();
useEffect(() => {
stop.current = val === str.length;
});
useEffect(() => {
const interval = setInterval(() => {
if(stop.current) clearInterval(interval);
else setVal((preVal) => preVal + 1);
}, 200);
return () => {
clearInterval(interval);
};
}, []);
return <div>{str.slice(0, val)}</div>;
}
ReactDOM.render(
<Printer str="cats" />,
root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react‑dom@17/umd/react‑dom.development.js"></script>
<div id="root"></div>
方法 2:
Put both the string prop and the state index into the dependency array, and to make things easier, use a timeout instead of an interval. Also add a callback that runs when the string prop changes so you can reset the state index to 0.
const Printer = ({ str }) => {
const [index, setIndex] = React.useState(0);
React.useEffect(() => {
if (index < str.length) {
const timeoutId = setTimeout(() => {
setIndex(index + 1);
return () => clearTimeout(timeoutId);
}, 200);
return () => clearTimeout(timeoutId);
}
}, [index, str]);
React.useEffect(() => {
setIndex(0);
}, [str]);
return <div>{str.slice(0, index)}</div>;
}
const App = () => {
const [str, setStr] = React.useState('');
return (
<div>
<input value={str} onChange={(e) => setStr(e.currentTarget.value)} />
<Printer str={str} />
</div>
);
};
ReactDOM.render(<App/>, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react‑dom@16/umd/react‑dom.development.js"></script>
<div class='react'></div>
(by mikey.nagler、Ori Drori、CertainPerformance)