前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码学习入门(二)React的render究竟返回的是什么?

React源码学习入门(二)React的render究竟返回的是什么?

作者头像
孟健
发布2022-09-21 11:23:02
6700
发布2022-09-21 11:23:02
举报
文章被收录于专栏:前端工程

React的render返回的是什么?

在进行React源码的深度讲解之前,我们先来看看一个最基础的核心问题:

❝React render的返回值到底是什么?

理解这个问题,才能顺利完成对React源码的进一步分析。

React render的返回值类型

其实要回答这个问题很简单,我们只需要看一下React官方TS声明的类型:

代码语言:javascript
复制
class Component<P, S> {
  render(): ReactNode;
}

type ReactNode = ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined;

声明源文件

可以很明显的看出来,render返回值是一个ReactNode,而ReactNode可以是很多类型,其中最重要常见的类型是ReactElement

JSX的编译

我们一般在写React的时候,一般是这样写的:

代码语言:javascript
复制
render() {
  return (
    <button onClick={this.handleClick}>
      Click me! Number of clicks: {this.state.count}
    </button>
  );
}

在JS里面书写类似HTML标签的语法,是React团队早期定义的一个JavaScript的语法扩展,名叫JSX。

这个语法在经过babel编译时,会产生如下结果:

代码语言:javascript
复制
render() {
  return /*#__PURE__*/React.createElement("button", {
    onClick: this.handleClick
  }, "Click me! Number of clicks: ", this.state.count);
}

在babel平台上直接编译

可以看到,我们书写的HTML标签、React组件,最终都会被编译成React.createElement的方法调用,而render的返回值,正是这个函数的返回值。

createElement原理解析

❝以下代码分析源自于React v15.6.2版本,原因可参考新手如何学习React源码

createElement的实现位于src/isomorphic/classic/element/ReactElement.js:

代码语言:javascript
复制
ReactElement.createElement = function(type, config, children) {
  // ...
}

我们先来看看它的入参,它支持三个参数:

  • type,这个其实就是标签本身,如果是个HTML标签,那它是字符串,比如上面的'button',如果是一个React组件,那它就是这个组件的class/function本身。
  • config,这个是标签上的属性对象,对于React组件来说,其实可以简单理解为它的props,对于HTML元素来说,是它的attribute所构成的对象。
  • children,顾名思义就是它的子元素了,children的类型同样也是一个ReactNode,由createElement生成。注意children参数可以再往后扩展,第三个参数之后的传参全部都会被视为children。

这个时候再看下面的代码就会清晰很多:

代码语言:javascript
复制
React.createElement("button", {
  onClick: this.handleClick
}, "Click me! Number of clicks: ", this.state.count);

现在来看看createElement的实现,首先,是props的生成:

代码语言:javascript
复制
ReactElement.createElement = function(type, config, children) {
  // 以下为生成props的片段
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
}

这段代码并不复杂,我们忽略内部变量selfsource,其实这段代码就是从config中提取生成了以下属性:

  • key,也就是React中的key属性
  • ref,也就是React中的ref属性
  • props,剩下的config被拷贝到props对象上

其次是children的生成:

代码语言:javascript
复制
ReactElement.createElement = function(type, config, children) {
  // children的生成
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }
}

这段代码同样也非常简单,就是把第三个参数和之后的参数,全部合并到propschildren属性上。

Virtual DOM的表示

最终createElement的返回值,其实是一个ReactElement对象:

代码语言:javascript
复制
ReactElement.createElement = function(type, config, children) {
  // 返回值
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  return element;
};

可以看到,ReactElement就是一个非常普通的对象,包含了typeprops等这些关键属性,而这个对象就是React内部render返回的实际底层表示。

这样一个对象的形式,完全包含了ReactNode想要表达的所有信息,我们也可以称它为一个标记(token),更进一步,它可以理解为一种DSL

而React通过这层DSL的抽象表示,构建了整个组件的嵌套树,我们称之为Virtual DOM,通过这样的数据结构,React屏蔽了DOM和Natvie在具体实现上的差异,做到了跨端跨平台的通用处理,也正是得益于这样的表示,让SSR成为了可能,同时有了高效的Diff算法提升整体更新的性能。

不得不说,在2013年React团队就能提出这样的思想和实现,十分令人敬佩,也同样开启了前端一个崭新的时代。

一句话总结

回到标题的问题:

  • React的render究竟返回的是什么?

本质上,它返回的就是一个ReactElement,一个普普通通的对象,通过这些对象,React构建出了大名鼎鼎的Virtual DOM,从而开启了前端新纪元。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟健的前端认知 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • React的render返回的是什么?
    • React render的返回值类型
      • JSX的编译
        • createElement原理解析
          • Virtual DOM的表示
            • 一句话总结
            相关产品与服务
            腾讯云代码分析
            腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档