爲何React元素有一個$$typeof屬性?

你以爲你在寫JSX:javascript

<marquee bgcolor="#ffa7c4">hi</marquee>
複製代碼

其實,你在調用一個方法:html

React.createElement(
  /* type */ 'marquee',
  /* props */ { bgcolor: '#ffa7c4' },
  /* children */ 'hi'
)
複製代碼

以後方法會返回一個對象給你,咱們稱此對象爲React的 元素(element),它告訴React下一個要渲染什麼。你的組件(component)返回一個它們組成的樹(tree)。前端

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'), // 🧐 Who dis
}
複製代碼

若是你用過React,對typepropskey、 和 ref應該熟悉。 $$typeof 是什麼?爲何用 Symbol() 做爲它的值java

這又是一個與你學習使用React不 相關 的點,但瞭解後你會以爲舒坦。這篇文章裏也提到了些關於安全的提示,你可能會感興趣。也許有一天你會有本身的UI庫,這些都會派上用場的,我真的但願如此。react


在客戶端UI庫變得廣泛且具備基本保護做用以前,應用程序代碼一般是先構建 HTML,而後把它插入DOM中:git

const messageEl = document.getElementById('message');
messageEl.innerHTML = '<p>' + message.text + '</p>';
複製代碼

這樣看起來沒什麼問題,但當你 message.text 的值相似 '<img src onerror="stealYourPassword()">' 時,你不會但願別人寫的內容在你應用的HTML中逐字顯示的。github

(有趣的是:若是你只是在前端渲染,這裏爲 <script> 標籤,JavaScript代碼不會被運行。但不要所以讓你陷入已經安全的錯覺。)web

爲何防止此類攻擊,你能夠用只處理文本的 document.createTextNode() 或者 textContent等安全的API。你也能夠事先將用戶輸入的內容,用轉義符把潛在危險字符(<>等)替換掉。瀏覽器

儘管如此,這個問題的成本代價很高,且很難作到用戶每次輸入都記得轉換一次。所以像React等新庫會默認進行文本轉義:安全

<p>
  {message.text}
</p>
複製代碼

若是 message.text 是一個帶有 <img> 或其餘標籤的惡意字符串,它不會被當成真的 <img> 標籤處理,React會先進行轉義 而後 插入DOM裏。因此<img>標籤會以文本的形式展示出來。

要在React元素中渲染任意HTML,你不得不寫dangerouslySetInnerHTML={{ __html: message.text }}其實這種愚蠢的寫法是一個功能,在code reviews和代碼庫審覈時,你能夠很是清晰的定位到代碼。


這意味着React徹底不懼注入攻擊了嗎?不,HTML和DOM暴露了大量攻擊點,對React或者其餘UI庫來講,要減輕傷害太難或進展緩慢。大部分存在的攻擊方向涉及到屬性,例如,若是你渲染<a href={user.website},要提防用戶的網址是 'javascript: stealYourPassword()'。 像<div {...userData}>寫法幾乎不受用戶輸入影響,但也有危險。

React能夠逐步提供更多保護,但在不少狀況下,威脅是服務器產生的,這無論怎樣都應該要避免。

不過,轉義文本這第一道防線能夠攔下許多潛在攻擊,知道這樣的代碼是安全的就夠了嗎?

// Escaped automatically
<p>
  {message.text}
</p>
複製代碼

好吧,也不老是有效的。這就是 $$typeof 的用武之地了。


React元素(elements)是設計好的 plain object

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}
複製代碼

雖然一般用 React.createElement() 建立它,但這不是必須的。有一些React用例來證明像上面這樣的 plain object元素是有效的。固然,你不會 這樣寫的,但這能夠用來優化編譯器,在 workers 之間傳遞UI元素,或者將JSX從React包解耦出來。

可是,若是你的服務器有容許用戶存儲任意JSON對象的漏洞,而前端須要一個字符串,這可能會發生一個問題:

// 服務端容許用戶存儲JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* 把你想的擱着 */'
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };

// Dangerous in React 0.13
<p>
  {message.text}
</p>
複製代碼

在這個例子中,React 0.13很容易受到XSS攻擊。再次聲明,這個攻擊是服務端存在漏洞致使的。不過,React會爲了你們的安全作更多工做。從React 0.14開始,它作到了。

React 0.14修復手段是用Symbol標記每一個React元素(element):

{
  type: 'marquee',
  props: {
    bgcolor: '#ffa7c4',
    children: 'hi',
  },
  key: null,
  ref: null,
  $$typeof: Symbol.for('react.element'),
}
複製代碼

這是個有效的辦法,由於JSON不支持Symbol類型。因此即便服務器存在用JSON做爲文本返回安全漏洞,JSON裏也不包含 Symbol.for('react.element')。React會檢測 element.$$typeof,若是元素丟失或者無效,會拒絕處理該元素。

特地用 Symbol.for() 的好處是 Symbols 通用於 iframes 和 workers 等環境中。所以不管在多奇怪的條件下,這方案也不會影響到應用不一樣部分傳遞可信的元素。一樣,即便頁面上有不少個React副本,它們也 「接受」 有效的$$typeof值。


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

唉,那這種保護方案就無效了。React仍然會加上 $$typeof 字段以保證一致性,但只是設置一個數字而已—— 0xeac7

爲何是這個數字?由於 0xeac7看起來有點像 「React」。

翻譯原文Why Do React Elements Have a $$typeof Property?(2018-12-03)

相關文章
相關標籤/搜索