"组件每一次渲染都会有自己的props和state。每一次渲染都会有自己的事件处理函数。"接下来,我们来深刻理解这句话。
function Counter(){
const [count, setCount] = useState(0);
return (
<div>
<p>you clicked {count} times</p>
<button onClick={() => setCount(count+1)}>+++</button>
</div>
)
}
这段代码里,我们如果点击button,显示的count的值确实会增加。它的原理是count“监听”状态的变化然后自动更新吗?其实并不是。count不是双向绑定或者“watcher”或者其他任何东西,它只是一个普通的变量。
const count = 0;
...
<p>you clicked {count} times</p>
初始状态下,count值是0。当调用setCount(1)的时候,React重新渲染了组件,这次count的值是1。也就是说每次渲染拿到的count值是独立的,不是同一份。
// During first render
function Counter() {
const count = 0; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState()
// ...
<p>You clicked {count} times</p>
// ...
}
关键在于,任意一次渲染中,const常量是不会变化的,我们看到的输出变化,是因为组件被重新调用了,并且传入了不同的参数,这个参数独立于其他任何一次渲染。
对于基本变量来说是这样的,那事件处理函数的情况呢?
答案其实一样,函数式组件的渲染,每次都会从上到下执行一遍代码。
function Counter(){
const [count, setCount] = useState(0);
function handleClick(){
setTimeout(() => {
console.log(count);
},5000);
}
return (
<>
<p>you clicked {count} times</p>
<button onClick={() => setCount(count+1)}>+++</button>
<button onClick={handleClick}>console</button>
</>
)
}
如果我们在点击完console button后的5s时间内点击了多次+++按钮,请问会输出什么?答案是:0。
它的原理就类似于普通函数:
function sayHi(person){
setTimeout(()=>{console.log(person.name)}, 1000);
}
let someone = {name: "Dan"};
sayHi(someone); // "Dan"
someone = {name: "Mike"};
sayHi(someone); // "Mike"
someone = {name: "John"};
sayHi(someone); // "John"
即使在console.log执行之前,someone已经被赋予了新值,但在sayHi函数内,person会和某次调用的someone关联,也就是说每次调用的person都是独立的,虽然名字一样,但保存在不同的空间里。
同样的,组件每次渲染都会有新的handleClick函数,它会记住这次渲染的count值。虽然名字和之前渲染的handleClick一样,但他们是独立的。也就是说,事件处理函数“属于”某一次特定的渲染。
再来看看useEffect这个Hook,它和上面分析的结果一样,每次渲染都会有自己的Effects。直接上代码:
function Counter(){
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(()=>{
console.log(count);
}, 3000);
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
这段代码,如果在3s内连续点击五次,会依次输出0,1,2,3,4,5。因为每次调用effect,它看到的都是属于本次渲染的唯一的count。
总的来说,每一次渲染的组件都会拥有它自己的所有东西(props、state、事件处理函数、effects、定时器、API调用等)。
看完这个,也了解了为什么说多个state最好写多次useState,而不是写在一个对象里。因为函数式组件的setState操作不会合并之前的state,而是用新的替换旧的。非要用对象的话,可以使用对象结构符。