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
,Elements
和Inastances
的可以点击这里。
// 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里面含有img
、script
类似的标签,它们最终也不会以实际上的标签显示。React会对渲染的内容进行转译,比如说上面的攻击代码会被转译为:
message = '<img onerror="alert(2)" src=""/>';
// 转译为
message = '<img onerror="alert(2)" src=""/>'
因此,这样就可以避免大部分场景下的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
React 中无用但可以装逼的知识的更多相关文章
- 为什么现在很多年轻人愿意来北上广深打拼,即使过得异常艰苦,远离亲人,仍然义无反顾? 谈谈程序员返回家乡的创业问题 利基市场就是那些不大不小的缝隙中的市场 马斯洛的需求无层次不适合中国。国人的需求分三个层次——生存、稳定、装逼。对应的,国内的产品也分三个层次——便宜、好用、装B。人们愿意为这些掏钱
信念.思考.行动-谈谈程序员返回家乡的创业问题 昨天晚上在微博上看了篇 <为什么现在很多年轻人愿意来北上广深打拼,即使过得异常艰苦,远离亲人,仍然义无反顾?>,有些话想说. 感觉很多人的担 ...
- 装逼手册之 python中的内存分配的小秘密
装逼手册之 python中的内存分配的小秘密 虽然我们现在得益于时代和技术的发展,不用再担心内存的问题:但是遥想当年,都是恨不得一个钢镚掰成俩份用,所以我就想深入了解一下,在python中内存分配的一 ...
- 用reduce装逼 之 多个数组中得出公共子数组,统计数组元素出现次数
昨天做了一道美团的面试题,要求是给N个数组,找出N个数组的公共子数组. ,,,,]; ,,,,]; ,,,,]; ,,,,]; 以上四个数组,有公共子数组2, 3,7 function main(){ ...
- 关于NGINX的502的装逼打怪之路
写日志之前先copy一段nginx502的原因,从某网看到如下,然而这并不是重点,最重要还是看博主手敲的东西. 一.NGINX 502错误排查 NGINX 502 Bad Gateway错误是Fast ...
- WebApp简单制作(后端也可以装逼啦)
前端越来越吃香的感觉 年后回来,跟之前和几个同事和朋友聊天,发现有两个.net的和一个php的朋友都转到了前端,真是出乎意料.自从之前的webapp兴起后,前端感觉比后端吃香很多,总结朋友们转的原因, ...
- JavaScript 装逼指南
Summary 本文秉承着 你看不懂是你sb,我写的代码就要牛逼 的理念来介绍一些js的装逼技巧. 下面的技巧,后三个,请谨慎用于团队项目中(主要考虑到可读性的问题),不然,leader 干你没商量. ...
- 前端 JSer 装逼手册
阅读 8143收藏 2352016-7-18 SegmentFault 分享:吉祥物 @ SegmentFault 在装逼成本越来越高的 JS 圈,是时候充值一下了 -- 题记. 作者:kenberk ...
- Dev-C++之开启装逼效果
Dev-C++是个不错的C++IDE——在10年前,它是很不错,在现在,它是个以界面丑陋和调试像吃粑粑这两点著称,如下图.
- JavaScript装逼指南
如何写JavaScript才能逼格更高呢?怎样才能组织JavaScript才能让别人一眼看出你不简单呢?是否很期待别人在看完你的代码之后感叹一句“原来还可以这样写”呢?下面列出一些在JavaScrip ...
随机推荐
- JAVA 8 主要新特性 ----------------(七)新时间日期 API ----- Duration “时间”间隔
Duration:用于计算两个“时间”间隔 简介: 用法: 1.Zero常量 实例: Duration duration = Duration.ZERO; System.out.println(&qu ...
- RK3288 uboot启动流程
VS-RK3288嵌入式板卡 U-boot 启动流程小结 bl board_init_f -> crt0.S initcall_run_list(init_sequence_f) - ...
- mysql几种中间件对比
网上找到的图 重点比较几个 1.atlas 基于mysql-proxy,360团队 优点: 配置简单,支持读写分离 缺点: 年份久,功能有限 地址:https://github.com/Qihoo36 ...
- python-mysql驱动64位
安装Python-MySQL驱动一直没有成功: https://pypi.python.org/pypi/MySQL-python/1.2.5#downloads 上面网站下的版本安装都不能通过 提示 ...
- [转]Java工程师技术栈--成神之路
一.基础篇 1.1 JVM 1.1.1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 http://www.jcp.org/en/jsr/detail?id=133http://if ...
- Windows + Apache + WSGI 部署Django
注意Python Apache和mod_wagi的版本要一致哦 1.安装Apache服务器(下载后,解压即可,目录不能有中文) 2.安装mod_wsgi (pip install 它的路径) 3.打开 ...
- Ubuntu英文变为中文
Ubuntu英文变为中文 注:(我也是第一次变语言,写的有点乱,我把重点的用红字表示.) 1.点击这个软件更新. 2.只有点击了上面那一步,这里才会软件资源 Software Sources ...
- Android开发者的Anko使用指南(二)之Dialogs
在项目中使用Anko Dialogs dependencies { compile "org.jetbrains.anko:anko-commons:$anko_version" ...
- spring 单元测试方法及其错误整理
spring 单元测试及其错误整理 目录: NO1 spring单元测试方法 - NO1.1 pom.xml文件中确认有下面依赖 - NO1.2 在需要测试的类上,或者新建的测试类上添加注解 - NO ...
- 'An instance 0x155e74a0 of class UIWebView was deallocated while key value observers were still registered with it.
在iOS和html混编的时候,当用iOS原生的navigation导航pop回去的时候,出现 *** Terminating app due to uncaught exception 'NSInte ...