生命周期
1-装载2-渲染4-卸载.
它更像(docs):
- Mount
- Render,(React calls your component function).
- Commit到DOM (React reconciles elements with the DOM)
- #
- #
- Update (repeats)
- Render,(React calls your component function).
- Commit到DOM (React reconciles elements with the DOM)
- #
- #
- Cleanup(如果有的话)(React React calls any effect cleanup callbacks)
- Unmount
- Removal,(React removes elements from the DOM).
- Cleanup(如果有的话)(React React calls any effect cleanup callbacks)
您的问题
当在Use Effect之前或之后(或在它内部)声明倒计时状态和x变量时?
在此之前.Reaction库不能改变执行JavaScript代码的方式.useState
调用和相关声明在useEffect
调用之前,因此它们发生在useEffect
调用之前.
当If或For短语被声明时(在本例中为If短语),它们真的在Use Effect中吗?
不会,只有useEffect
回调中的代码才会被调用为效果.
代码的运行方式
这里的循环是:
- 第一次渲染
- Reaction为元素创建幕后实例存储.
- Reaction调用您的组件函数.
- 您的代码声明了一个本地
x
变量,并将其设置为0
.
- 您的代码声明
countDown
和setCountDown
并调用useState
,这将在实例存储中分配一个状态槽;您的代码将useState
返回的内容(初始状态值和setter)存储在这些常量中.
- 您的
x = x + 1
语句运行,将x
更新为1
.
- 您的
if
语句运行,但条件永远不为真,因为-x
是局部变量,而不是状态成员,因此它的值在此时始终为1
.
- 您对
useEffect
的调用计划在countDown
更改时进行效果回调.
- 此时,您的代码应该从
Countdown
返回元素.
- 第一次提交/"装载"
- Reaction接受
Countdown
应该返回的元素,并将它们commits返回到DOM(使DOM显示它们所描述的内容).
- Reaction调用您的
useEffect
个回调(总是在挂载之后调用useEffect
个回调)
- 您的回调创建一个时间间隔计时器,该计时器在运行时将调用
setCountDown
.
- 您的回调日志(log)为
x
,将为1
.
- 定时器调用
setCountDown
,更改该值.
- Second render
- React calls your function to re-render.
- 您的代码声明了一个新的局部
x
变量,并将其设置为0
.
- 您的代码声明
countDown
和setCountDown
并调用useState
,后者从实例存储中检索更新后的状态;您的代码将useState
返回的内容(当前状态值和setter)存储在这些常量中.
- 运行您的
x = x + 1
语句,将x
更新为1
.
- 您的
if
语句将运行,但条件永远不会为真.
- 您对
useEffect
的呼叫计划在countDown
更改时进行效果回调.
- 此时,您的代码应该返回
Countdown
中的元素.
- Because
countDown
changed, React calls your useEffect
callback
- 您的回调创建了一个新的时间间隔计时器,运行时将调用
setCountDown
.
- 您的回调记录为
x
,也就是1
.
- 依此类推,直到/除非组件被其父组件卸载.
代码的问题
您所展示的代码中有几个错误
- 您永远不会取消间隔计时器,但每次更改
countDown
次时,您都会创建一个新的计时器.这将很快导致数百乃至数千个计时器全部触发更新调用.您应该:
- 至少要记住计时器句柄并在效果清理中取消计时器.
- 考虑不使用
countDown
作为依赖,这样效果只在挂载时生效.然后使用回调表单setCountDown
.
- (如前所述)您的组件永远不会返回任何元素
- 您的代码似乎希望在调用函数之间保持值
x
,但它是一个局部变量,所以每次都会重新创建它.
- 当
countDown
达到0
时,没有什么特别的事情发生,所以它只会一直走到-1
、-2
,等等.
更新版
这是一个带有一些注释的更新版本.我本来打算删除x
,因为它并不是真的用来做任何事情,但后来我想,最好还是留下 comments .我没有对上面的第四点做任何事情,因为我不确定你想要做什么.
const Countdown = () => {
let x = 0;
const [countDown, setCountDown] = useState(10);
x = x + 1;
if (x > 100) { // `x` will always be `1` here, remember that
x = 0; // `x` is a *local variable*
}
useEffect(() => {
const interval = setInterval(() => {
// Use the callback form of the setter so you can update the
// up-to-date value
setCountDown((c) => c - 1);
// Will always show 1
console.log(x);
}, 1000);
// Return a cleanup callback that removes the interval timer
return () => {
clearInterval(interval);
};
}, []);
// ^^ don't use `countDown` as a dependency (in this particular case),
// since we don't use it (anymore, now we use the callback setter)
// Return some elements
return <div>{countDown}</div>;
};
const { useState, useEffect } = React;
const Countdown = () => {
let x = 0;
const [countDown, setCountDown] = useState(10);
x = x + 1;
if (x > 100) { // `x` will always be `1` here, remember that
x = 0; // `x` is a *local variable*
}
useEffect(() => {
const interval = setInterval(() => {
// Use the callback form of the setter so you can update the
// up-to-date value
setCountDown((c) => c - 1);
// Will always show 1
console.log(x);
}, 1000);
// Return a cleanup callback that removes the interval timer
return () => {
clearInterval(interval);
};
}, []);
// ^^ don't use `countDown` as a dependency (in this particular case),
// since we don't use it (anymore, now we use the callback setter)
// Return some elements
return <div>{countDown}</div>;
};
const Example = () => {
return <Countdown />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
如果我们希望倒计时在达到0
时停止并关闭计时器,有几种方法可以做到这一点,参见显示不同方法的两个现场示例中的注释:
const { useState, useEffect } = React;
const Countdown = () => {
const [countDown, setCountDown] = useState(10);
useEffect(() => {
const interval = setInterval(() => {
// We could cancel the interval from within the setter
// callback. It's a bit dodgy, though, to have side-
// effects in setter callbacks.
setCountDown((c) => {
const updated = c - 1;
if (updated === 0) {
clearInterval(interval);
}
return updated;
});
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <div>{countDown === 0 ? "Done" : countDown}</div>;
};
const Example = () => {
return <Countdown />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
const { useState, useEffect, useRef } = React;
const Countdown = () => {
// We could store the timer handle in a ref (which is maintained
// across renders) and use a second `useEffect` to cancel it when
// `countDown` reaches zero.
const intervalRef = useRef(0);
const [countDown, setCountDown] = useState(10);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCountDown((c) => c - 1);
}, 1000);
return () => {
// (It's okay if this tries to clear an interval that
// isn't running anymore.)
clearInterval(intervalRef.current);
};
}, []);
useEffect(() => {
if (countDown === 0) {
clearInterval(intervalRef.current);
}
}, [countDown]);
return <div>{countDown === 0 ? "Done" : countDown}</div>;
};
const Example = () => {
return <Countdown />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>