原文: Why Do React Elements Have a $$typeof Property?
譯文原文: 為什么react元素有個$$typeof 屬性
你可能認(rèn)為你在寫JSX:
<marquee bgcolor="#ffa7c4">hi</marquee>
但是實際上是你在調(diào)用一個函數(shù):
React.createElement(
/* type */ 'marquee',
/* props */ { bgcolor: '#ffa7c4' },
/* children */ 'hi'
)
這個函數(shù)給你返回了一個對象,我們把這個對象叫做React元素。它告訴React接下來渲染什么,組件就是返回對象??。
{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'), // ?? Who dis
}
像上面這樣,如果你使用React你可能熟悉type, props, key, ref這些字段。但是$$typeof是什么?為什么會有個Symbol作為值?
這個也是你在寫react的時候不需要知道的一件事,但是如果你知道了,那感覺會很棒。在這篇文章中還有一些你可能想知道的安全性的提示。也許有一天你會編寫自己的UI庫,所有這些都會派上用場。我希望是這樣的。
在客戶端UI庫變得普遍并添加一些基本保護(hù)之前,應(yīng)用程序代碼通常構(gòu)造HTML并將其插入DOM:
const messageEl = document.getElementById('message');
messageEl.innerHTML = '<p>' + message.text + '</p>';
這就可以了,除非當(dāng)message.text是像'<img src onerror="stealYourPassword()">'這樣的時候。 你不希望陌生人編寫的內(nèi)容顯示在應(yīng)用程序呈現(xiàn)的HTML中。
(有趣的事實:如果你只做客戶端渲染,這里的<script>標(biāo)簽不會讓你運(yùn)行JavaScript。但是,不要讓這使你陷入虛假的安全感。)
為了防止此類攻擊,你可以使用安全的API,例如document.createTextNode或textContent,它只處理文本。你還可以通過在用戶提供的文本中替換<,>等其他潛在危險字符來搶先“轉(zhuǎn)義”輸入。
盡管如此,錯誤的成本很高,每次將用戶編寫的字符串插入輸出時,記住它都很麻煩。這就是為什么像React這樣的現(xiàn)代庫在默認(rèn)的情況下為字符串轉(zhuǎn)義文本內(nèi)容的原因:
<p>
{message.text}
</p>
如果message.text是帶有<img>或其他的標(biāo)簽,則它不會變成真正的<img>標(biāo)簽(tag)。React將轉(zhuǎn)義內(nèi)容,然后將其插入DOM。所以你應(yīng)該看標(biāo)記而不是看img標(biāo)簽。
要在React元素中呈現(xiàn)任意HTML,你必須寫dangerouslySetInnerHTML = {{__ html:message.text}}。然而事實上,這么笨拙的寫法是一個功能。 它意味著高度可見,便于在代碼審查和代碼庫審計中捕獲它。
這是否意味著React對于注入攻擊是完全安全的?不是。 HTML和DOM提供了大量的攻擊面,對于React或其他UI庫來說,要緩解這些攻擊面要么太難要么太慢。大多數(shù)剩余的攻擊都偏向于屬性上進(jìn)行。 例如,如果渲染<a href={user.website}>,請注意其user.website可能是“javascript:stealYourPassword()”。像<div {... userData}>那樣擴(kuò)展用戶的輸入很少見,但也很危險。
React可以隨著時間的推移提供更多保護(hù),但在許多情況下,這些都是服務(wù)器問題的結(jié)果,無論如何都應(yīng)該在那里修復(fù)。
仍然,轉(zhuǎn)義文本內(nèi)容是合理的第一道防線,可以捕獲大量潛在的攻擊。知道像這樣的代碼是安全的,這不是很好嗎?
// Escaped automatically
<p>
{message.text}
</p>
好吧,這也不總是正確的。 這時候就需要派$$typeof上場了。
React的elements在設(shè)計的時候就決定是一個對象。
{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'),
}
雖然通常使用React.createElement創(chuàng)建它們,但它不是必要的。React有一些有效的用例來支持像我剛剛上面所做的那樣編寫的普通元素對象。當(dāng)然,你可能不希望像這樣編寫它們 - 但這對于優(yōu)化編譯器,在工作程序之間傳遞UI元素或者將JSX與React包解耦是有用的。
但是,如果你的服務(wù)器有一個漏洞,允許用戶存儲任意JSON對象, 而客戶端代碼需要一個字符串,這可能會成為一個問題:
// Server could have a hole that lets user store JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* put your exploit here */'
},
},
// ...
};
let message = { text: expectedTextButGotJSON };
// Dangerous in React 0.13
<p>
{message.text}
</p>
在這種情況下,React 0.13很容易受到XSS攻擊。再次澄清一下,這種攻擊取決于現(xiàn)有的服務(wù)器漏洞。 盡管如此,React可以做到更好,防止遭受它攻擊。從React 0.14開始,它做到了。
React 0.14中的修復(fù)是使用Symbol標(biāo)記每個React元素:
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'),
}
這是有效的,因為你不能只把Symbol放在JSON中。因此,即使服務(wù)器具有安全漏洞并返回JSON而不是文本,該JSON也不能包含Symbol.for('react.element')。React將檢查element.$$ typeof,如果元素丟失或無效,將拒絕處理該元素。
并且使用Symbol.for的好處是符號在iframe和worker等環(huán)境之間是全局的。因此,即使在更奇特的條件下,此修復(fù)也不會阻止在應(yīng)用程序的不同部分之間傳遞可信元素。同樣,即使頁面上有多個React副本,它們?nèi)匀豢梢岳^續(xù)工作。
那些不支持Symbols的瀏覽器呢?
好吧,他們沒有得到這種額外的保護(hù)。 React仍然在元素上包含$$ typeof字段以保持一致性,但它設(shè)置為一個數(shù)字 - 0xeac7。
為什么是個具體的號碼? 0xeac7看起來有點像“React”。