一.Class Component VS Function Component
1.Capture(捕获) 特性
对比下面两段代码 Function Component
function Counter() {
const [count, setCount] = useState(0);
const log = () => {
setCount(count + 1);
setTimeout(() => {
console.log(count);
}, 3000);
};
return (
<div>
<p>You clicked {count} times</p>
<button onClick={log}>Click me</button>
</div>
);
}
Class Component
class Counter extends Component {
state = { count: 0 };
log = () => {
this.setState({
count: this.state.count + 1
});
setTimeout(() => {
console.log(this.state.count);
}, 3000);
};
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.log}>Click me</button>
</div>
);
}
}
在3s内连续点击button3次,Class和Function的结果完全不同
Class Component 首先 state 是 Immutable(不可变) 的,setState 后一定会生成一个全新的 state 引用。但 Class Component 通过 this.state 方式读取 state,这导致了每次代码执行都会拿到最新的 state 引用,所以快速点击三次的结果是 3 3 3。
Function Component useState 产生的数据也是 Immutable(不可变) 的,通过数组第二个参数 Set 一个新值后,原来的值会形成一个新的引用在下次渲染时。但由于对 state 的读取没有通过 this. 的方式,使得每次 setTimeout 都读取了当时渲染闭包环境的数据,虽然最新的值跟着最新的渲染变了,但旧的渲染里,状态依然是旧值。
为了更容易理解,来模拟三次 Function Component 模式下点击按钮时的状态:
第一次点击,共渲染了 2 次,setTimeout 生效在第 1 次渲染,此时状态为:
function Counter() {
const [0, setCount] = useState(0);
const log = () => {
setCount(0 + 1);
setTimeout(() => {
console.log(0);
}, 3000);
};
return ...
}
第二次点击,共渲染了 3 次,setTimeout 生效在第 2 次渲染,此时状态为:
function Counter() {
const [1, setCount] = useState(0);
const log = () => {
setCount(1 + 1);
setTimeout(() => {
console.log(1);
}, 3000);
};
return ...
}
第三次点击,共渲染了 4 次,setTimeout 生效在第 3 次渲染,此时状态为:
function Counter() {
const [2, setCount] = useState(0);
const log = () => {
setCount(2 + 1);
setTimeout(() => {
console.log(2);
}, 3000);
};
return ...
}
可以看到,每一个渲染都是一个独立的闭包,在独立的三次渲染中,count 在每次渲染中的值分别是 0 1 2,所以无论 setTimeout 延时多久,打印出来的结果永远是 0 1 2。
2.如何规避掉capture特性?
第一种方法是useRef
function Counter() {
const count = useRef(0);
const log = () => {
count.current++;
setTimeout(() => {
console.log(count.current);
}, 3000);
};
return (
<div>
<p>You clicked {count.current} times</p>
<button onClick={log}>Click me</button>
</div>
);
}
这样打印结果就是 3 3 3。
useRef 的功能:通过 useRef 创建的对象,其值只有一份,而且在所有 Rerender 之间共享。 所以我们对 count.current 赋值或读取,读到的永远是其最新值,而与渲染闭包无关,因此如果快速点击三下,必定会返回 3 3 3 的结果。
但这种方案有个问题,就是使用 useRef 替代了 useState 创建值,那么很自然的问题就是,如何不改变原始值的写法,达到同样的效果呢?
|