以下代碼,沒有使用模塊化的方式,使用的是CDN的方式。如果需要源代碼,請從這個地址下載:
```text
鏈接:https://pan.baidu.com/s/1s57mr5AE_ecWBFZ5TJTwqw 提取碼:f7x2
```
另外,這篇文章,主要是剖析**組件的初次渲染和重新渲染**。所以,其它部分不要太較勁。
### **一、react類組件和函數式組件重新渲染時的區別**
### **1、看現象:**
### **1)代碼(demo01)**
**類組件:**
```js
// 1、類組件
class ComClass extends React.Component {
constructor(props) {
super();
this.props = props;
console.log("類組件的構造函數被調用了");
}
render() {
console.log("類組件的render被調用了");
return (
<div style={{ "backgroundColor": "pink" }}>
<h5 >我是類組件</h5>
<p>{this.props.name}</p>
</div>
);
}
}
```
**函數式組件:**
```js
// 2、函數式組件
function ComFn(props) {
console.log("函數式組件的函數被調用了");
return (
<div style={{ "backgroundColor": "skyblue" }}>
<h5 >我是函數式組件</h5>
<p>{props.name}</p>
</div>
);
}
```
**使用組件:**
```js
let name = "張三瘋";
function changeName() {
name = "張四瘋"
renderDom();
}
function renderDom() {
ReactDOM.render(
<div>
<button onClick={changeName} >修改</button>
<ComClass name={name} /><br />
<ComFn name={name} />
</div>, document.getElementById("box"));
}
renderDom();
```
### **2)運行:**
2.1)初次運行時,我們發現在控制臺中打印的內容為:
```js
類組件的構造函數被調用了
類組件的render被調用了
函數式組件的函數被調用了
```
2.2)當點擊“修改”按鈕時,我們發現控制臺中打印的內容為:
```js
類組件的render被調用了
函數式組件的函數被調用了
```
### **3)總結(敲黑板,重點):**
1、類組件重新渲染時,只調用了render
2、函數式組件重新渲染時,會調用函數整個本身(哈哈,不調用它也不行?。?/p>
### **2、看原理:**
### **1)用原生的方式剖析:**
函數式組件的剖析:
```js
標簽的方式使用函數式組件:
<ComFn name={name} />
基本上等價于:
{ComFn({name})} //組件的方式使用,就是在調用函數
```
類組件的剖析:
```js
標簽的方式使用類組件:
<ComClass name={name} /><br/>
等價于
{new ComClass({name}).render()}
但是
這樣改了后,初次渲染沒有問題。當點擊了“修改”按鈕時,不一樣了。
所以,類組件里,應該是把new ComClass({name})實例化出來的對象,記錄起來了。所以,應該等價于
1、定義一個對象,保存了new ComClass({name})
//在react里對類組件對象進行了保存。
let comObj = new {new ComClass({name});
2、在渲染時,只是調用了類組件的render函數。
comObj.render();
```
即:最終代碼變成了如下:
### **2)代碼(demo02):**
類組件和函數式組件的代碼不用改變。
**使用組件**
```js
let name = "張三瘋";
//此處保存了類組件的實例化對象(這個只是模擬實現,react內部并不是這么簡單)
let comObj = new ComClass({name});
function changeName(){
name = "張四瘋";
comObj.props = {name}
renderDom();
}
function renderDom(){
ReactDOM.render(
<div>
<button onClick={changeName} >修改數據</button>
{/*此處用原生的方式使用組件*/}
{comObj.render()}
{ComFn({name})}
</div>, document.getElementById("box"));
}
renderDom();
```
### **3)運行:**
3.1)、初次運行時,我們發現在控制臺中打印的內容為:
```js
類組件的構造函數被調用了
類組件的render被調用了
函數式組件的函數被調用了
```
3.2)、當點擊“修改”按鈕時,我們發現控制臺中打印的內容為:
```js
類組件的render被調用了
函數式組件的函數被調用了
```
> 運行結果和組件的方式一樣。
### **二、看看組件里使用定時器,并且,重新渲染組件**
### **1、看現象**
### **1)代碼(demo03):**
**類組件:**
```js
// 1、類組件
class ComClass extends React.Component {
constructor(props) {
super();
this.props = props;
console.log("類組件的構造函數被調用了");
}
showMessage = () => {
//在顯示props時(通過this訪問props),props里的內容被改變了。
console.log('類組件: ' + this.props.name);
};
handleClick = () => {
// 分析問題:
// 1、3秒鐘后調用函數(通過 this 的方式調用)
setTimeout(this.showMessage, 3000);
};
render() {
console.log("類組件的render被調用了");
return (
<div style={{ "backgroundColor": "pink" }}>
<h5 >我是類組件</h5>
<p>name:{this.props.name}</p>
<input type="button" value="調用帶著定時器的函數" onClick={this.handleClick} />
</div>
);
}
}
```
**函數式組件:**
```js
// 2、函數式組件
function ComFn(props) {
console.log("函數式組件的函數被調用了");
//這個是閉包:
// 每調用一次父函數(ComFn),都會重新定義一個新的子函數。新的函數中保存著父函數新的形參
const showMessage = () => {
console.log('函數式組件: ' + props.name);
};
//這個是閉包:
//每調用一次父函數(ComFn),都會重新定義一個新的子函數。新的函數中保存著父函數新的形參
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<div style={{ "backgroundColor": "skyblue" }}>
<h5 >我是函數式組件</h5>
<p>name:{props.name}</p>
{/*先點擊這個按鈕,調用,第一次定義的 showMessage和handleClick*/}
<input type="button" value="調用帶著定時器的函數" onClick={handleClick} />
</div>
);
}
```
**使用組件:**
```js
let name = "張三瘋";
function changeName() {
name = "張四瘋"
renderDom();
}
function renderDom() {
ReactDOM.render(
<div>
<button onClick={changeName} >修改</button>
<ComClass name={name} /><br />
<ComFn name={name} />
</div>, document.getElementById("box"));
}
renderDom();
```
### **2)、運行:**
**2.1)類組件:**
點擊類組件的“調用帶著定時器的函數” 按鈕后,再點擊”修改“按鈕(**注意先后順序**)。我們發現了:界面上打印的和控制臺打印的是**一樣的**。這是**不對的**:因為,我點擊“調用帶著定時器的函數” 按鈕時,name的值是”張三瘋“,應該打印”張三瘋“
**2.2)函數式組件:**
點擊類組件的“調用帶著定時器的函數” 按鈕后,再點擊”修改“按鈕(**注意先后順序**)。我們發現了:界面上打印的和控制臺里打印的**不一樣**,這是**對的**:因為,我點擊“調用帶著定時器的函數” 按鈕時,name的值是”張三瘋“,應該打印”張三瘋“
### **3)總結**
原因何在?以下文字要細細的品,如果品不出來,就結合上面的代碼,再看看。
類組件:
當**類組件重新渲染**時,**只調用了render函數**。組件的this不變。等定時器到了時,讀取屬性的的值時,先通過this找到props。再通過props找到name。而此時,name的值,已經發生了變化。所以,自然讀到的是新值“張四瘋” ,這個應該好理解。
函數式組件:
(你必須對閉包是清楚的),當**函數式組件重新渲染**時,**調用了函數**(組件),那么在函數式組件里的 函數(showMessage,handleClick)就會被重新定義,新定義的函數保存著父函數(組件)的新的形參和局部變量。而我們點擊“調用帶著定時器的函數”時,調用的是 第一次定義的showMessage,handleClick(這兩個函數里保存了父函數(組件)第一次傳入的形參和局部變量)。
其實,上面的代碼中,已經做了注釋。我再次解釋后,如果不明白的話,看看下面的剖析原理的代碼。
### **2、看原理:**
### **1)用原生的方式剖析**
與第一大點的第二小點的“剖析原理”一樣:
函數式組件的剖析:
```js
標簽的方式使用函數式組件:
<ComFn name={name} />
基本上等價于:
{ComFn({name})} //組件的方式使用,就是在調用函數
```
類組件的剖析:
```js
把new ComClass({name})實例化出來的對象,記錄起來了。
1、定義一個對象,保存了new ComClass({name})
//在react里對類組件對象進行了保存。
let comObj = new {new ComClass({name});
2、在渲染時,只是調用了類組件的render函數。
comObj.render();
```
### **2)代碼(demo04):**
類組件和函數式組件的代碼不用變(同第二大點的第一小點:組件里使用定時器,并重新渲染組件)。
**使用組件:**
這個代碼等同于第一大點的第二小點。
```js
let name = "張三瘋";
let comObj = new ComClass({ name });
function changeName() {
name = "張四瘋";
comObj.props = { name }
renderDom();
}
function renderDom() {
ReactDOM.render(
<div>
<button onClick={changeName} >修改</button>
{comObj.render()}
{ComFn({name})}
</div>, document.getElementById("box"));
}
renderDom();
```
### **三、為什么react現在更推薦函數式組件**
React的核心理念之一就是,**界面應當是數據的不同形式的簡單投影**。**相同的輸入應該產生相同的輸出**。而函數式組件的寫法,使用閉包的特性,顯然符合這一理念:每個閉包里保存在父函數的當前形參(props)和局部變量。而類組件里,由于,每次讀取數據,要根據this指針去讀取,那必然不會讀取到屬于自己當前狀態的值。而是更新后的最新的值。
### **四、補充:類組件如何解決以上問題呢:**
其實還是利用了閉包。
### **看類組件的代碼修改(demo05):**
```js
//修改類組件里的showMessage 和 handleClick 函數。
showMessage = (name) => {
console.log('類組件: ' + name);
};
handleClick = () => {
let name = this.props.name;
//此處:
//1 、()=>this.showMessage(name)是閉包。父函數是handleClick,
//2、閉包會把當前的局部變量name持有,然后,調用 showMessage(name)時,把name的值傳入showMessge里
setTimeout(()=>this.showMessage(name), 3000);
};
```
### **五、類組件和函數式組件區別:**
在react的模式和開發思維上,函數組件和類組件還是有區別的。
1、各自的特點:
1)類的組件主要是面向對象編程,是建立在繼承之上,它的生命周期等核心概念的特點 2)函數組件主要是函數式編程,無副作用,并且在引用的時候透明的特點
2、使用場景:
因為兩者主打的特點不一樣,所以在使用場景上自然就存在一些差異了: 如果組件依賴生命周期,并且它的設計上需要繼承的特性,我們在選擇上更傾向類組件會更合適一點
由于HOOK的推出,逐漸降低了生命周期的概念,那么函數組件可以更好的取代類組件的開發,而且官方也推崇“組合優于繼承”的思想,所以類組件的優勢也在慢慢的淡出。更多關于web培訓的問題,歡迎咨詢千鋒教育在線名師。千鋒教育擁有多年IT培訓服務經驗,采用全程面授高品質、高體驗培養模式,擁有國內一體化教學管理及學員服務,助力更多學員實現高薪夢想。