你以为你在写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,对type
、 props
、 key
、 和 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)