在React中,支援使用class和function來聲明一個元件,而實際上,我們在使用這個元件時,因為class和function的不同,是以我們的使用也存在不同
我們知道,function是可以直接調用的,但是class是需要通過new去建立一個執行個體來使用的
function
// 你的代碼
function Greeting() {
return <p>Hello</p>;
}
// React 内部
const result = Greeting(props); // <p>Hello</p>
class
// 你的代碼
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
// React 内部
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
雖然使用時不一樣,但實際上我們的目标都是去擷取渲染後的節點
首先,了解為什麼要将function和class區分開很重要,我們需要先了解new的使用,下面是我自己實作的一個new
function _new(c,...args){
if(typeof c !=="function"){
throw new TypeError(`${c} is not a constructor`)
}
let obj = Object.create(c.prototype)
let res = c.call(obj,...args)
return typeof res === "object"?res:obj
}
我們知道,new是通過建立一個新的對象,然後将方法中的this綁定在這個對象上,然後執行方法,如果方法傳回的是原始類型,那麼就傳回新建立的這個對象,如果傳回的是一個引用類型諸如對象和數組,那麼就會傳回這個引用類型
實際上,如果我們允許不采用new來調用一個類,那麼很可能導緻的結果是,建立的新執行個體的this會指向undefined或者window,甚至會建立了window.name這種屬性
而我們也要清楚,當我們在使用React的時候,實際上經常會用到babel進行一個編譯的操作,而在babel中,雖然早期類不加new也可以被調用,但現在已經被修複了,通過添加了額外的代碼來實作修複
function Person(name) {
// 稍微簡化了一下 Babel 的輸出:
if (!(this instanceof Person)) {
throw new TypeError("Cannot call a class as a function");
}
// Our code:
this.name = name;
}
new Person('Fred'); // ✅ OK
Person('George'); // 🔴 無法把類當做函數來調用
那麼,現在的問題在于,我們在使用class的時候需要使用new去調用,而使用function不用,雖然在js裡面我們可以對class和function進行一個區分,但是對于babel編譯後的類,在浏覽器看來,它和function隻是不同的函數。
我們可以在每次調用前都添加一個new嗎
雖然即使是正常函數,我們通過new來進行一個調用也不會有問題。在沒有class之前我們也會采用function來模拟一個類,但實際上,函數元件是不可取的
我們可以看以下兩個理由
首先,箭頭函數,我們知道,箭頭函數本身沒有自己的this,箭頭函數内部的this,指向了箭頭函數所在的那個作用域的this,是以,我們無法對一個箭頭函數使用new來構造,而函數元件卻是允許我們采用箭頭函數
此外,上面已經提到過,new在使用時,本身函數如果傳回了一個原始類型的話,最終會傳回的是一個新建立的對象,而我們在使用函數元件的時候,有的時候是會傳回一個純文字的,是以直接添加一個new,會使得我們無法直接傳回一個純文字
判斷方法
看過别人的部落格,才了解到,實際上上面我們考慮的,是對于所有的函數和類,去做一個判斷區分,這是一個籠統的問題,但實際上,我們是可以去解決一個具體的問題,區分一個元件是class或者function
如何去區分,我們可以想到的是,當我們使用function的時候,我們是直接聲明一個function,那,我們使用class的時候呢,會去繼承一個React.Component
看到繼承我想你應該有思路了,我們可以通過判斷原型鍊上是否有React.Component去判斷我們用了class還是function
談到原型鍊,我們可以先看看下面這段代碼
class A {}
class B extends A {}
console.log(B.prototype instanceof A); // true
這段代碼咋一看,好像沒什麼問題,但仔細想想,
class B extends A {}
這段代碼,是什麼繼承了A,是B這個類嗎?不是啊,是B的執行個體,那麼為什麼,
B.prototype instanceof A
會傳回true,這裡就要扯一下__proto__和prototype了
首先有一個問題,下面這段代碼,會列印什麼
function Person(){}
console.log(Person.prototype === Person.__proto__)
console.log(Person.prototype === new Person().__proto__)
答案是false true
這裡就說明了Person.prototype實際上指的不是Person的原型(我感覺這個誤導了很多人),而是指向了它建立的執行個體的原型,當然,如果這裡的Person傳回了一個引用類型的話,那第二個console也會傳回一個false
通過上面的代碼和描述,我們也明白了,原型鍊實際上,更像是obj.proto.proto.__proto__這種形式
知道原型鍊的繼承方式後,我們來看看class的extends,看起來似乎是一種新的繼承方式,但實際上,還是基于原型鍊實作的繼承,是以,我們可以通過原型鍊,來判斷一個元件function還是class
友善的是,instanceof就是通過原型鍊來進行類型判斷的,是以我們可以直接采用instanceof來進行判斷
然而React并不是這麼做的,具體可見https://overreacted.io/zh-hans/how-does-react-tell-a-class-from-a-function/
本文亦參考https://overreacted.io/zh-hans/how-does-react-tell-a-class-from-a-function/