你需要知道的jsx


theme: channing-cyan
highlight: atom-one-dark

前言

作为一个react开发者,那么在开发的过程中,不可避免的会接触到jsx来编写组件代码。虽然可以编写jsx的框架越来越多,但是联系最深的还是react。那么jsx到底是什么呢?又是如何变成dom进行页面渲染的呢?接下来一探究竟!

jsx是什么

jsx是javascript的一种语法扩展,和模板语言很接近,但是它充分具备javascript的能力。

简单来说就是把js的语法做了一定的扩展,让其支持编写html的语法,但是对于浏览器解析来说,并不能识别这个jsx,那么就需要通过工具进行转译,这个工具就是babel

转译的jsx变成了什么

首先来看一个babel转译的例子:

image.png
从图片可以看出,jsx通过babel转译之后会变成通过createElement方法创建react组件。那么由此可以简单理解为jsx是createElement方法的语法糖,通过jsx的方式可以更加方便的写出react组件代码。那么简而言之,你也可以不使用jsx而是选择直接使用react提供的createElement方法来创建组件等,很显然笔者断定你不会这个样子做的,因为我懂你(其实是因为太麻烦),看下这个复杂的例子:

image.png

image.png
如果业务开发中,你选择crateElement来编写组件,那我相信你将拥有无限加班的可能,这就是为什么选择jsx的原因

所以jsx的好处是代码层次分明、嵌套关系清晰,而不像createElement代码一样让人感觉浑乱,读写都不够友好。,其实它是最大的简化开发流程,让开发者们编码体验更好。

为什么老版本的react中需要引入react,而新版本不用?

其实通过babel转译代码这个层面,我想大家也看出了一点问题所在,其实原因就是在于,在转译完成之后,jsx会变成React.createElement的形式,那么毫无疑问你必须要引入react。

但是作为新版本的react引入了react/jsx-runtime,编译工具会自动到React模块中获取并将代码编译,而不是以前的React.createElement

import {jsx as _jsx} from 'react/jsx-runtime'

function App() {
    return _jsx('h1',{children:'hello water'})

}

所以在react新版本中不再需要显示引入react了

jsx转译后的元素样子

通过react.createElement把jsx转换成react element,对应的有自己的转换规则和类型

jsx元素类型 react.createElement 转换后 type 属性
element元素类型 react element类型 标签字符串,例如 div
fragment类型 react element类型 symbol react.fragment类型
文本类型 直接字符串
数组类型 返回数组结构,里面元素被react.createElement转换
组件类型 react element类型 组件类或者组件函数本身
三元运算 / 表达式 先执行三元运算,然后按照上述规则处理 看三元运算返回结果
函数执行 先执行函数,然后按照上述规则处理 看函数执行返回结果

在react的底层,react element对象的每一个节点都会形成一个与之对应的fiber对象,然后fiber通过sibling、return、child等指针将每一个fiber对象联系起来。

  • sibling: 一个fiber指向下一个兄弟fiber的指针
  • return: 一个子级fiber指向父级fiber的指针
  • child:一个子级fiber指向子级fiber的指针

通过源码初探react dom的转换

首先扒一扒createElement方法的源码,看都做了什么

/**
 React的创建元素方法
 */
export function createElement(type, config, children) {
  // propName 变量用于储存后面需要用到的元素属性
  let propName; 
  // props 变量用于储存元素属性的键值对集合
  const props = {}; 
  // key、ref、self、source 均为 React 元素的属性,此处不必深究
  let key = null;
  let ref = null; 
  let self = null; 
  let source = null; 

  // config 对象中存储的是元素的属性
  if (config != null) { 
    // 进来之后做的第一件事,是依次对 ref、key、self 和 source 属性赋值
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    // 此处将 key 值字符串化
    if (hasValidKey(config)) {
      key = '' + config.key; 
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 接着就是要把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面
    for (propName in config) {
      if (
        // 筛选出可以提进 props 对象里的属性
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName) 
      ) {
        props[propName] = config[propName]; 
      }
    }
  }
  // childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度
  const childrenLength = arguments.length - 2; 
  // 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了
  if (childrenLength === 1) { 
    // 直接把这个参数的值赋给props.children
    props.children = children; 
    // 处理嵌套多个子元素的情况
  } else if (childrenLength > 1) { 
    // 声明一个子元素数组
    const childArray = Array(childrenLength); 
    // 把子元素推进数组里
    for (let i = 0; i < childrenLength; i++) { 
      childArray[i] = arguments[i + 2];
    }
    // 最后把这个数组赋值给props.children
    props.children = childArray; 
  } 

  // 处理 defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) { 
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  // 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}


这个函数有3个参数分别是type,config,children

  • type: 用于标识节点的类型
  • config:以对象的形式传入,组件所有的属性都会以键值对的形式存储在config对象中
  • children:以对象形式传入,主要记录组件标签之间嵌套的内容(子节点元素)

找个例子你可能更好理解

image.png

image.png

通过createElement的源码阅读可以描绘出一个函数执行所做事情的流程图

image.png

从中可以看出,createElement方法就像是一个react element的处理器,通过开发者传入的一些数据作为依据,构建出一个react element。

构建element时会去调用ReactElement方法,然后返回一个根据入参配置生成的element,源码如下

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    // 内置属性赋值
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 记录创造该元素的组件
    _owner: owner,
  };

  // 
  if (__DEV__) {
    // 这里是一些针对 __DEV__ 环境下的处理,对于大家理解主要逻辑意义不大,此处我直接省略掉,以免混淆视听
  }

  return element;
};


整体理解为调用createElement方法,然后再调用reactElement方法最后生成element元素,返回渲染

小结

总体而言jsx就是js的一个扩展,然后react通过babel工具将jsx转译成react的语法,通过调用createElement和reactElement方法创建并返回出一个element元素。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容