React 中無用但可以裝逼的知識

来源:https://www.cnblogs.com/chenjg/archive/2019/04/15/10709277.html
-Advertisement-
Play Games

最近看了 "Dan Abramov" 的一些 "博客" ,學到了一些React的一些有趣的知識。決定結合自己的理解總結下。這些內容可能對你實際開發並沒有什麼幫助,不過這可以讓你瞭解到更多React底層實現的內容以及為什麼要怎樣實現。可以讓你跟別人有更多的談資,當然,也可以在某些場合裝一下逼。那麼接下 ...


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

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

我們可以考慮從幾種方式:

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

問題:

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

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

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

    我們都知道,使用new操作符後,只有當函數返回非null 和非undefined的對象的時候,返回值才會生效。否則new操作符的返回值都會是對象。關於new操作符詳細的內容可以點擊這裡

    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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • pc端個性化日曆實現 技術:vue = v for、slot scop 插槽域 需求:需要實現日曆上每一天動態顯示不同的信息 思路:運用vue 中 slot scop 插槽域的知識點,將個性化的代碼樣式放到slot中 再通過slot scop 獲取這個插槽中的所需數據 一、實現日曆組件 思路:佈局上 ...
  • 原理:通過動態生成canvas然後轉為base64格式 代碼Demo export const waterMark = (text) = { let _wm = document.createElement('canvas') _wm.setAttribute('width',150) _wm.se ...
  • 工作的越久,有些基礎知識我們可能就逐漸淡忘了,今天我們來回顧一下css的聖杯佈局和雙飛翼佈局, 這兩個名詞你可能不熟, 那三欄佈局你肯定就非常熟悉了, 就是兩邊定寬, 中間自適應 的 佈局 1 , 聖杯佈局 首先HTML結構是這樣的,因為要保證中間的結構先渲染, 所以 center 要放在 最前面 ...
  • 下麵是五個正則合到一起的一個正則表達式 this.options.formData[8].value = value.replace(/[^\d.]/g, '').replace(/\.{2,}/g, '.').replace('.', '$#$').replace(/\./g, '').repla ...
  • 1.標簽選擇器 div{} 2.類選擇器 .one class="one" 3.id選擇器(定義+調用) #one{} id="one" 4.通配符選擇器 *{} 5.後代選擇器 .one a {} 6.子代選擇器 .one>a{} 7.交集選擇器 p.one {} 8.並集選擇器 .one,.tw ...
  • 1.變數 var num=10; var num1,num2,num3; num1=10; num2=20; num3=30; var num1=10,num2=20,num3=30; 註意點: console.log(a); //報錯 var b; console.log(b); //undefi ...
  • table 表頭有時候需要加一些小樣式比如 必填項 這是我項目中遇到的需求 比例,產品, 部門為必填項,這個時候就需要在表頭加個紅色小星星。 首先在table中綁定:header-cell-class-name="must"事件 然後methods中寫must事件如下 must(obj) { if ...
  • 瀏覽器F5刷新的時候有一個刷新執行之前的事件,beforeunload 事件,這個事件可以提示用戶在刷新頁面之前有一個提示。 下麵是beforeunload的用法: 首先在methods中定義beforeunload事件 beforeunloadHandler(e) { // e.preventDe ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...