使用React hook实现自增计数器
Use React hook to implement a self-increment counter
代码在这里:https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
问题是每个 setCounter
触发重新渲染,因此间隔被重置并重新创建。这可能看起来不错,因为状态(计数器)不断递增,但是当与其他挂钩结合时它可能会冻结。
正确的做法是什么?在 class 组件中,使用一个保存间隔的实例变量很简单。
你可以给一个空数组作为 useEffect
的第二个参数,这样函数在初始渲染后只 运行 一次。由于闭包的工作方式,这将使 counter
变量始终引用初始值。然后,您可以使用 setCounter
的函数版本来始终获得正确的值。
例子
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
一种更通用的方法是创建一个新的自定义挂钩,将函数存储在 ref
and only creates a new interval if the delay
should change, like Dan Abramov does in his great blog post "Making setInterval Declarative with React Hooks".
中
例子
const { useState, useEffect, useRef } = React;
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
let id = setInterval(() => {
savedCallback.current();
}, delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [counter, setCounter] = useState(0);
useInterval(() => {
setCounter(counter + 1);
}, 1000);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
正如 已经显示的那样,可以使其 useEffect
仅回调 运行 一次,并且与 componentDidMount
的工作方式类似。在这种情况下,由于函数范围的限制,有必要使用状态更新函数,否则更新的 counter
将无法在 setInterval
回调中使用。
另一种方法是在每次计数器更新时进行 useEffect
回调 运行。在这种情况下 setInterval
应替换为 setTimeout
,并且更新应限于 counter
更新:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
执行此操作的正确方法是 运行 效果仅一次。因为你只需要运行效果一次,因为在挂载时,你可以传入一个空数组作为第二个参数来实现。
但是,您需要更改 setCounter
以使用 counter
的先前值。原因是因为传入 setInterval
闭包的回调仅在第一次渲染中访问 counter
变量,它无法访问后续渲染中的新 counter
值,因为useEffect()
没有被第二次调用; counter
在 setInterval
回调中的值始终为 0。
和你熟悉的setState
一样,state hooks有两种形式:一种是接受更新后的状态,另一种是传入当前状态的回调形式。你应该使用第二种形式并在 setState
回调中读取最新的状态值,以确保在递增之前拥有最新的状态值。
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, []); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
代码在这里:https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
问题是每个 setCounter
触发重新渲染,因此间隔被重置并重新创建。这可能看起来不错,因为状态(计数器)不断递增,但是当与其他挂钩结合时它可能会冻结。
正确的做法是什么?在 class 组件中,使用一个保存间隔的实例变量很简单。
你可以给一个空数组作为 useEffect
的第二个参数,这样函数在初始渲染后只 运行 一次。由于闭包的工作方式,这将使 counter
变量始终引用初始值。然后,您可以使用 setCounter
的函数版本来始终获得正确的值。
例子
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
一种更通用的方法是创建一个新的自定义挂钩,将函数存储在 ref
and only creates a new interval if the delay
should change, like Dan Abramov does in his great blog post "Making setInterval Declarative with React Hooks".
例子
const { useState, useEffect, useRef } = React;
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
let id = setInterval(() => {
savedCallback.current();
}, delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [counter, setCounter] = useState(0);
useInterval(() => {
setCounter(counter + 1);
}, 1000);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
正如 useEffect
仅回调 运行 一次,并且与 componentDidMount
的工作方式类似。在这种情况下,由于函数范围的限制,有必要使用状态更新函数,否则更新的 counter
将无法在 setInterval
回调中使用。
另一种方法是在每次计数器更新时进行 useEffect
回调 运行。在这种情况下 setInterval
应替换为 setTimeout
,并且更新应限于 counter
更新:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
执行此操作的正确方法是 运行 效果仅一次。因为你只需要运行效果一次,因为在挂载时,你可以传入一个空数组作为第二个参数来实现。
但是,您需要更改 setCounter
以使用 counter
的先前值。原因是因为传入 setInterval
闭包的回调仅在第一次渲染中访问 counter
变量,它无法访问后续渲染中的新 counter
值,因为useEffect()
没有被第二次调用; counter
在 setInterval
回调中的值始终为 0。
和你熟悉的setState
一样,state hooks有两种形式:一种是接受更新后的状态,另一种是传入当前状态的回调形式。你应该使用第二种形式并在 setState
回调中读取最新的状态值,以确保在递增之前拥有最新的状态值。
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, []); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>