React 中无用但能够装逼的知识

最近看了Dan Abramov的一些博客,学到了一些React的一些有趣的知识。决定结合本身的理解总结下。这些内容可能对你实际开发并无什么帮助,不过这可让你了解到更多React底层实现的内容以及为何要怎样实现。可让你跟别人有更多的谈资,固然,也能够在某些场合装一下逼。那么接下来直接进入正文。javascript

React如何区分类组件和函数组件

咱们能够考虑从几种方式来区分:html

统一使用new方法来生成实例

经过这种方式的话,咱们就不须要去区分该组件是类组件仍是函数组件了。但是,这种方式存在着一些问题:java

  • 对于函数组件而言,这样会让它们生成一个多余的this做为对象实例。react

  • 对于箭头函数而言,会报错。由于箭头函数并无this,它的this是取自于定义这个箭头函数所在环境的thisgit

    const fun = () => console.log(2);
    new fun(); // Uncaught TypeError: fun is not a constructor
    复制代码
  • 使用new会妨碍函数组件返回原始类型(string、number等)。github

    咱们都知道,使用new操做符后,只有当函数返回非null 和非undefined的对象的时候,返回值才会生效。不然new操做符的返回值都会是对象。关于new操做符详细的内容能够点击这里react-native

    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

相关文章
相关标签/搜索