天天看點

在React中如何判斷元件是函數還是類

在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/