返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Reactjsx转换与createElement使用超详细讲解
  • 129
分享到

Reactjsx转换与createElement使用超详细讲解

Reactjsx转换ReactcreateElement 2022-11-13 19:11:32 129人浏览 八月长安
摘要

目录jsx的转换16.x版本及之前17.x版本及之后React.createElement源码React.Component 源码总结jsx的转换 我们从 react 应用的入口开始

jsx的转换

我们从 react 应用的入口开始对源码进行分析,创建一个简单的 hello, world 应用:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class App extends Component {
  render() {
    return <div>hello, world</div>;
  }
}
ReactDOM.render(<App />, document.getElementById('root'));

我们注意到,我们在 App 组件中直接写了 return <div>hello, world</div> 的 jsx 语句,那么 jsx 语法是如何被浏览器识别执行的呢?

另外我在第一次学习 react 的时候,就有一个疑惑: import React, { Component } from 'react' 这段代码中,React 似乎在代码中没有任何地方被用到,为什么要引入呢?

16.x版本及之前

我们在 react16.8 版本的代码中,尝试将 React 的引用去掉:

// import React, { Component } from 'react';
import { Component } from 'react'; // 去掉 React 的引用
import ReactDOM from 'react-dom';
export default class App extends Component {
  render() {
    return <div>hello, world</div>;
  }
}
ReactDOM.render(<App />, document.getElementById('root'));

运行应用程序,发现会提示 'React' must be in scope when using JSX 的 error:

这是因为上述的类组件 render 中返回了 <div>hello, world</div> 的 jsx 语法,在React16版本及之前,应用程序通过 @babel/preset-react 将 jsx 语法转换为 React.createElement 的 js 代码,因此需要显式将 React 引入,才能正常调用 createElement。我们可以在 Babel REPL 中看到 jsx 被 @babel/preset-react 编译后的结果

17.x版本及之后

React17版本之后,官方与 bbel 进行了合作,直接通过将 react/jsx-runtime 对 jsx 语法进行了新的转换而不依赖 React.createElement,转换的结果便是可直接供 ReactDOM.render 使用的 ReactElement 对象。因此如果在React17版本后只是用 jsx 语法不使用其他的 react 提供的api,可以不引入 React,应用程序依然能够正常运行。

更多有关于 React jsx 转换的内容可以去看官网了解:介绍全新的JSX转换,在这里就不再过多展开了。

React.createElement源码

虽然现在 react17 之后我们可以不再依赖 React.createElement 这个 api 了,但是实际场景中以及很多开源包中可能会有很多通过 React.createElement 手动创建元素的场景,所以还是推荐学习一下React.createElement源码。

React.createElement 其接收三个或以上参数:

  • type:要创建的 React 元素类型,可以是标签名称字符串,如 'div' 或者 'span' 等;也可以是 React组件 类型(class组件或者函数组件);或者是 React fragment 类型。
  • config:写在标签上的属性的集合,js 对象格式,若标签上未添加任何属性则为 null。
  • children:从第三个参数开始后的参数为当前创建的React元素的子节点,每个参数的类型,若是当前元素节点的 textContent 则为字符串类型;否则为新的 React.createElement 创建的元素。

函数中会对参数进行一系列的解析,源码如下,对源码相关的理解都用注释进行了标记:

export function createElement(type, config, children) {
  let propName;
  // 记录标签上的属性集合
  const props = {};
  let key = null;
  let ref = null;
  let self = null;
  let source = null;
  // config 不为 null 时,说明标签上有属性,将属性添加到 props 中
  // 其中,key 和 ref 为 react 提供的特殊属性,不加入到 props 中,而是用 key 和 ref 单独记录
  if (config != null) {
    if (hasValidRef(config)) {
      // 有合法的 ref 时,则给 ref 赋值
      ref = config.ref;
      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    if (hasValidKey(config)) {
      // 有合法的 key 时,则给 key 赋值
      key = '' + config.key;
    }
    // self 和 source 是开发环境下对代码在编译器中位置等信息进行记录,用于开发环境下调试
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 将 config 中除 key、ref、__self、__source 之外的属性添加到 props 中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  // 将子节点添加到 props 的 children 属性上
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    // 共 3 个参数时表示只有一个子节点,直接将子节点赋值给 props 的 children 属性
    props.children = children;
  } else if (childrenLength > 1) {
    // 3 个以上参数时表示有多个子节点,将子节点 push 到一个数组中然后将数组赋值给 props 的 children
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    // 开发环境下冻结 childArray,防止被随意修改
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }
  // 如果有 defaultProps,对其遍历并且将用户在标签上未对其手动设置属性添加进 props 中
  // 此处针对 class 组件类型
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // key 和 ref 不挂载到 props 上
  // 开发环境下若想通过 props.key 或者 props.ref 获取则 warning
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 调用 ReactElement 并返回
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

相关参考视频:传送门

由代码可知,React.createElement 做的事情主要有:

  • 解析 config 参数中是否有合法的 key、ref、__source 和 __self 属性,若存在分别赋值给 key、ref、source 和 self;将剩余的属性解析挂载到 props 上
  • 除 type 和 config 外后面的参数,挂载到 props.children
  • 针对类组件,如果 type.defaultProps 存在,遍历 type.defaultProps 的属性,如果 props 不存在该属性,则添加到 props 上
  • 将 type、key、ref、self、props 等信息,调用 ReactElement 函数创建虚拟 dom,ReactElement 主要是在开发环境下通过 Object.defineProperty 将 _store、_self、_source 设置为不可枚举,提高 element 比较时的性能:
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 用于表示是否为 ReactElement
    $$typeof: REACT_ELEMENT_TYPE,
    // 用于创建真实 dom 的相关信息
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };
  if (__DEV__) {
    element._store = {};
    // 开发环境下将 _store、_self、_source 设置为不可枚举,提高 element 的比较性能
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    // 冻结 element 和 props,防止被手动修改
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }
  return element;
};

所以通过流程图总结一下 createElement 所做的事情如下:

React.Component 源码

我们回到上述 hello,world 应用程序代码中,创建类组件时,我们继承了从 react 库中引入的 Component,我们再看一下React.Component源码:

function Component(props, context, updater) {
  // 接收 props,context,updater 进行初始化,挂载到 this 上
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // updater 上挂载了 isMounted、enqueueForceUpdate、enqueueSetState 等触发器方法
  this.updater = updater || ReactNoopUpdateQueue;
}
// 原型链上挂载 isReactComponent,在 ReactDOM.render 时用于和函数组件做区分
Component.prototype.isReactComponent = {};
// 给类组件添加 `this.setState` 方法
Component.prototype.setState = function(partialState, callback) {
  // 验证参数是否合法
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null
  );
  // 添加至 enqueueSetState 队列
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// 给类组件添加 `this.forceUpdate` 方法
Component.prototype.forceUpdate = function(callback) {
  // 添加至 enqueueForceUpdate 队列
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

从源码上可以得知,React.Component 主要做了以下几件事情:

  • 将 props, context, updater 挂载到 this 上
  • 在 Component 原型链上添加 isReactComponent 对象,用于标记类组件
  • 在 Component 原型链上添加 setState 方法
  • 在 Component 原型链上添加 forceUpdate 方法,这样我们就理解了 react 类组件的 super() 作用,以及 this.setStatethis.forceUpdate 的由来

总结

本章讲述了 jsx 在 react17 之前和之后的不同的转换,实际上 react17 之后 babel 的对 jsx 的转换就是比之前多了一步 React.createElement 的动作:

另外讲述了 React.createElementReact.Component 的内部实现是怎样的。通过 babel及 React.createElement,将 jsx 转换为了浏览器能够识别的原生 js 语法,为 react 后续对状态改变、事件响应以及页面更新等奠定了基础。

后面的章节中,将探究 react 是如何一步步将状态等信息渲染为真实页面的。

到此这篇关于React jsx转换与createElement使用超详细讲解的文章就介绍到这了,更多相关React jsx转换与createElement内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Reactjsx转换与createElement使用超详细讲解

本文链接: https://lsjlt.com/news/170763.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Reactjsx转换与createElement使用超详细讲解
    目录jsx的转换16.x版本及之前17.x版本及之后React.createElement源码React.Component 源码总结jsx的转换 我们从 react 应用的入口开始...
    99+
    2022-11-13
    React jsx转换 React createElement
  • C++超详细讲解强制类型转换
    目录1 C 强制类型转换2 C++ 强制类型转转1 C 强制类型转换 C 方式的强制类型转换的用法如下代码所示: (Type)(Expression) Type:需要转换成的类型Ex...
    99+
    2024-04-02
  • PyTorch Dataset与DataLoader使用超详细讲解
    目录一、Dataset1. 在控制台进行操作①获取图片的基本信息②获取文件的基本信息2. 编写一个继承Dataset 的类加载数据①定义 MyData类②创建类的实例并调用二、Dat...
    99+
    2024-04-02
  • C++超详细讲解强制类型转换的用法
    目录static_castdynamic_castconst_castreinterpret_caststatic_cast static_cast<type-id>(e...
    99+
    2024-04-02
  • C++ 超详细讲解stack与queue的使用
    目录stack介绍和使用模拟实现stack的使用例题最小栈栈的弹出压入序列逆波兰表达式求值queue模拟实现容器适配器deque简介priority_queue优先级队列priori...
    99+
    2024-04-02
  • C++超详细讲解auto与nullptr的使用
    目录一. auto关键字1. auto介绍2. 使用规则3. auto不能推导的场景二. 基于范围的for循环(C++11)1. 范围for的语法2. 范围for的使用条件三. 指针...
    99+
    2024-04-02
  • Netty与NIO超详细讲解
    目录Linux下的五种I/O模型阻塞IO的流程IO复用信号驱动I/O异步IONIOI0多路复用NIO核心组件使用Java原生API实现NIO操作Redis为什么支持高并发Linux下...
    99+
    2022-11-13
    Netty NIO模型 Netty NIO原理
  • MyBatis配置与CRUD超详细讲解
    目录CRUD增删改查示例代码Map传参模糊查询MyBatis配置解析核心配置文件mybatis-config.xml环境变量(Environments)属性(Properties)类...
    99+
    2023-02-21
    MyBatis配置解析 MyBatis CRUD
  • Spring超详细讲解IOC与解耦合
    目录前言一.所谓耦合二.Spring三.核心IOC理解1.容器2.控制反转3.依赖注入四.Bean的实例化1.无参构造2.工厂静态方法3.工厂实例方法(常用)五.Bean的依赖注入1...
    99+
    2022-11-13
    Spring IOC Spring 解耦合
  • Java超详细讲解抽象类与接口的使用
    目录一、抽象类1、抽象类的语法2、抽象类的特性3、抽象类的作用二、接口1、接口的概念2、接口使用3、接口特性4、实现多个接口5、接口间的继承6、常用的接口(1)Comparable接...
    99+
    2024-04-02
  • C语言超详细讲解宏与指针的使用
    目录1、关于define2、初识指针(1)内存(2)示例(3)指针的使用示例(4)指针变量的大小1、关于define define是一个预处理指令,有两种用法,一种是用define定...
    99+
    2024-04-02
  • Java超详细讲解ThreadLocal类的使用
    目录Threadlocal有什么用:ThreadLocal使用实例API介绍ThreadLocal的使用Threadlocal 的源码分析原理源码内部类ThreadLocalMapT...
    99+
    2024-04-02
  • Python超详细讲解元类的使用
    目录类的定义一、什么是元类二、注意区分元类和继承的基类三、type 元类的使用四、自定义元类的使用类的定义 对象是通过类创建的,如下面的代码: # object 为顶层基类 clas...
    99+
    2024-04-02
  • C++超详细讲解友元的使用
    目录一、友元的概念二、友元的用法三、友元的语法四、友元的尴尬五、注意事项六、小结一、友元的概念 什么是友元友元是 C++ 中的一种关系友元关系发生在函数与类之间或者类与类之间友元关系...
    99+
    2024-04-02
  • C++超详细讲解模板的使用
    目录一、函数模板1.1函数模板概念1.2 函数模板格式1.3 函数模板的原理1.4 函数模板的实例化二、类模板2.1 类模板的定义格式2.2类模板的实例化总结一、函数模板 1.1函数...
    99+
    2024-04-02
  • C++BoostLockfree超详细讲解使用方法
    目录一、说明二、示例和代码Boost.Lockfree 一、说明 Boost.Lockfree 提供线程安全和无锁容器。可以从多个线程访问此库中的容器,而无需同步访问。 在 1.56...
    99+
    2022-11-21
    C++ Boost Lockfree C++ Lockfree方案
  • C++超详细讲解树与二叉树
    目录树树的定义树的名词解释树的表示树的存储结构二叉树的概念及结构二叉树的概念二叉树的性质二叉树的存储结构顺序存储结构链式存储结构树 树的定义 Q:什么是树 A:树是一种 非线性 的数...
    99+
    2024-04-02
  • C++设计与声明超详细讲解
    目录让接口被正确使用不易被误用宁以pass-by-reference-to-const替换pass-by-value必须返回对象时将成员变量声明为private以non-member...
    99+
    2024-04-02
  • C语言超详细讲解轮转数组
    目录题目描述实例解题思路1. 先整体逆转2.逆转子数组[0, k - 1]3.逆转子数组[k, numsSize - 1]易错点代码题目描述 给你一个数组,将数组中的元素向右轮转 k...
    99+
    2024-04-02
  • C语言超详细讲解指针的概念与使用
    目录一、指针与一维数组1. 指针与数组基础2. 指针与数组3. 一个思考二、指针与字符串三、指针和二维数组1. 指针数组与数组指针2. 指针数组3. 数组指针一、指针与一维数组 1....
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作