React 中無用但能夠裝逼的知識

最近看了Dan Abramov的一些博客,學到了一些React的一些有趣的知識。決定結合本身的理解總結下。這些內容可能對你實際開發並無什麼幫助,不過這可讓你瞭解到更多React底層實現的內容以及爲何要怎樣實現。可讓你跟別人有更多的談資,固然,也能夠在某些場合裝一下逼。那麼接下來直接進入正文。javascript

React如何區分類組件和函數組件

咱們能夠考慮從幾種方式來區分:html

統一使用new方法來生成實例

經過這種方式的話,咱們就不須要去區分該組件是類組件仍是函數組件了。但是,這種方式存在着一些問題:java

  • 對於函數組件而言,這樣會讓它們生成一個多餘的this做爲對象實例。react

  • 對於箭頭函數而言,會報錯。由於箭頭函數並無this,它的this是取自於定義這個箭頭函數所在環境的thisgit

    const fun = () => console.log(2);
    new fun(); // Uncaught TypeError: fun is not a constructor
    複製代碼
  • 使用new會妨礙函數組件返回原始類型(string、number等)。github

    咱們都知道,使用new操做符後,只有當函數返回非null 和非undefined的對象的時候,返回值纔會生效。不然new操做符的返回值都會是對象。關於new操做符詳細的內容能夠點擊這裏react-native

    function Greeting() {
      return 'Hello';
    }
    
    // 並不會返回字符串
    new Gretting(); // Gretting {}
    複製代碼

綜上所述,這個方法不可行。數組

經過instanceof來判斷

不知道你有沒有察覺,咱們寫React的類組件的時候,咱們都須要經過extends React.Component的方式來寫。那麼,咱們是否能夠經過如下方式來判斷呢?瀏覽器

class A extends React.Component {
}

A.prototype instanceOf React.Component; // true
複製代碼

這種方式看起來挺靠譜的,經過這種方式,咱們確實能夠區分類組件和函數組件,但是也存在一些問題:安全

  • 箭頭函數沒有prototyoe

    這個問題其實好解決,以下

    function getType(Component) {
      if (Component.prototyoe && Component.prototype instance React.Component) {
        return 'class';
      }
      
      return 'function';
    }
    複製代碼
  • 對於一些項目(雖然不多)可能存在着多個React副本,而且咱們目前要檢查的組件它繼承的React.Component是來自於另外一個React副本的,這就會出現問題。這個問題的話就沒辦法解決了。所以這種方式也存在問題。

經過爲React.Component增長一個特別的標記

寫過React的類組件的人都知道,咱們每個類組件都是要繼承於React.Component的。所以,若是咱們在React.Component增長一個標記isReactComponent,這樣經過繼承的方式,咱們就能夠根據這個標記來判斷是否是類組件了。

// React 內部
class Component {}
Component.prototype.isReactComponent = {};

// 檢查
class Greeting extends Component {};
console.log(Greeting.prototype.isReactComponent);
複製代碼

事實上,React目前就是經過這種方式來進行檢查的。若是你沒有extends React.Component,React不會在原型上找到isReactComponent,所以不會把組件當作類組件來處理。

React Elements爲何要有一個$typeof屬性

假如咱們的jsx長這個樣子:

<Button type="primary">點擊</Button>
複製代碼

實際上,在通過babel後,它會變成下面這段代碼:

React.createElement(
  /* type */ 'Button',
  /* props */ { type: 'primary' },
  /* children */ '點擊'
)
複製代碼

以後,這個函數執行結果會返回一個對象,這個對象咱們稱爲React Element。它是一個用來描述咱們將要渲染的頁面結構的一個不可變對象。想了解更多與React Component,ElementsInastances的能夠點擊這裏

// React Element
{
  type: 'Button',
  props: {
    type: 'primary',
    children: '點擊',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'), // 爲何有這個東西
}
複製代碼

對於React開發者來講,上面這些屬性大部分都是比較常見的。但是爲何混進了一個奇怪的$$typeof??它是幹嗎的呢?它的值爲何是一個Symbol呢?

這個屬性的引入,其實要從一個安全漏洞提及。

假如咱們要顯示一個變量,若是你使用純js來寫的話,多是這樣:

const messageEl = document.getElementById('message');
messageEl.innerHTML = `<div>${message}</div>`;
複製代碼

這一段代碼,對於熟悉或者瞭解過XSS攻擊的人來講,一看就知道會有問題,存在着XSS攻擊。若是message是用戶能夠控制的變量(好比說是用戶輸入的評論)的話,那麼用戶就能夠進行攻擊了。好比用戶能夠構造下面的代碼來進行攻擊:

message = '<img onerror="alert(2)" src="" />';
複製代碼

若是咱們明確知道,咱們只想單純的渲染文本,不想把它當成html來渲染的話,那麼咱們能夠經過textContent來避免這個問題。

const messageEl = document.getElementById('message');
messageEl.textContent = `<div>${message}</div>`;
複製代碼

而對於React而言的話,想要實現相同的效果,只須要:

<div>{message}</div>
複製代碼

即便message裏面含有imgscript相似的標籤,它們最終也不會以實際上的標籤顯示。React會對渲染的內容進行轉譯,好比說上面的攻擊代碼會被轉譯爲:

message = '<img onerror="alert(2)" src=""/>';
// 轉譯爲
message = '&lt;img onerror="alert(2)" src=""/&gt;'
複製代碼

所以,這樣就能夠避免大部分場景下的XSS攻擊了。

固然,React也提供了另外一種方式來將用戶輸入的內容當成html來渲染:

<div dangerouslySetInnerHTML={{ __html: message }}></div>
複製代碼

前面說了這麼多,那麼跟$$typeof又有什麼關係呢?別急,重點來了。

對於下面這種寫法,咱們通常都知道,message能夠傳基本類型、自定義組件和jsx片斷。

<div>{message}</div>
複製代碼

但是,其實咱們還能夠直接傳React Element。好比,咱們能夠直接這樣寫

class App extends React.Component {
  render() {
    const message = {
      type: "div",
      props: {
        dangerouslySetInnerHTML: {
          __html: `<h1>Arbitrary HTML</h1> <img onerror="alert(2)" src="" /> <a href='http://danlec.com'>link</a>`
        }
      },
      key: null,
      ref: null,
      $$typeof: Symbol.for("react.element")
    };
    return <>{message}</>; } } 複製代碼

這樣在運行的時候,就會彈出一個alert框了。查看demo。那麼,這樣會有什麼風險呢?

考慮一個場景,好比一個博客網站的評論信息message是由用戶提供的,而且支持傳入JSON。那麼若是用戶直接將上文的message發送給後臺保存。以後,經過下面這種方式展現的話,用戶就能夠進行XSS攻擊了。

<div>{message}</div>
複製代碼

假設若是沒有$$typeof屬性的話,這種攻擊確實可行。由於其餘的屬性都是可序列化的。

const message = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: `<h1>Arbitrary HTML</h1> <img onerror="alert(2)" src="" /> <a href='http://danlec.com'>link</a>`
    }
  },
  key: null,
  ref: null,
};

JSON.stringify(message);
複製代碼

事實上,React 0.13當時就存在着這個漏洞。以後,React 0.14就修復了這個問題,修復方式就是經過引入$$typeof屬性,而且用Symbol來做爲它的值。

// 引入 $$typeof
const message = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: `<h1>Arbitrary HTML</h1> <img onerror="alert(2)" src="" /> <a href='http://danlec.com'>link</a>`
    }
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for("react.element")
};

JSON.stringify(message); // Symbol沒法被序列化
複製代碼

這是一個有效的方法,由於JSON是不支持Symbol類型的。因此,即便用戶提交了如上的message信息,到最後服務端也不會保存$$typeof屬性。而在渲染的時候,React 會檢測是否有$$typeof屬性。若是沒有這個屬性,則拒絕處理該元素。

那麼若是瀏覽器不支持Symbol怎麼辦?

是的,那這種保護方案就沒用了。React 依然會加上$$typeof字段,而且將其值設置爲0xeac7。(爲何是這個數字呢,由於這個數字看起來有點像React)。

想查看具體的攻擊流程,能夠查看這篇博客

總結

  • React會給React.Component.prototype增長一個isReactElement標誌。這樣,React就能夠在渲染的時候判斷當前渲染的組件是類組件仍是函數組件。
  • React Element是一個用於描述要渲染的頁面結構的一個不可變對象。React函數組件和類組件執行到最後,其實都是生成一個React Elements樹。以後再由實際的渲染層(react-dom、react-native)根據這個React Elements樹渲染爲實際的頁面。
  • <div>{message}</div>這種方式不只能夠傳原型類型、jsx和組件,還能夠直接傳React Element對象。
  • $$typeof的出現就是爲了防止服務端容許儲存JSON而引發的XSS攻擊。但是對於不支持Symbol的瀏覽器,這個問題依然存在。

本文地址在->本人博客地址, 歡迎給個 start 或 follow。

參考資料

Why Do React Elements Have a $$typeof Property?

How Does React Tell a Class from a Function?

XSS via a spoofed React element

相關文章
相關標籤/搜索