Skip to content

从JSX语法说起

前言

什么是JSX语法?

它是一种类似HTML的语法,是一种JavaScript的语法扩展,它最终会被编译成JavaScript代码。

浏览器不能直接编译JSX语法的,所以需要使用Babel来将JSX语法编译成JavaScript代码,JSX语法才能在浏览器中正常运行。

Babel官方提供了一个包:@babel/preset-react,它可以将JSX语法编译成React.createElement这种形式的代码。

createElement做的事情很简单,它接收三个参数,type, props, children,然后它会返回一个React元素,这个React元素就是一个普通的JavaScript对象,这个普通的JavaScript对象就是我们所说的虚拟DOM。

有了这些虚拟DOM,React就可以通过这些虚拟DOM来进行渲染真实的DOM,而当我们修改了虚拟DOM之后,React也会通过新旧虚拟DOM的对比来计算出最小的修改,然后再去修改真实的DOM,这个过程就是React的核心算法虚拟DOM Diff算法

__DEV__是什么?

它是一个全局变量,用来表明当前是否是开发环境,如果是开发环境,那么它的值就是true,如果是生产环境,那么它的值就是false。

而源码中大量使用了__DEV__,这是因为我们在开发环境中,经常有代码调试的需求,而为了满足这些需求,React就需要在开发环境中提供一些额外的功能,例如在开发环境中,React会对一些常见的错误进行警告,由于开发环境存在了这些额外的功能,所以它的体积会比生产环境大很多,而性能也会比生产环境差一些

如果只是为了了解React的原理,那么我们在阅读源码的时候,可以直接忽略包裹在__DEV__中的代码,这样可以让我们更加容易理解源码。

createElement

createElement源码,可以看到它接收三个参数,type, props, children。

这三个参数分别是什么呢?

  • type:元素的类型。
  • props:元素的属性。
  • children:元素的子元素。

type

它可以是一个字符串,也可以是一个函数,如果是一个字符串,那么它就是一个普通的HTML标签,如果是一个函数,那么它就是一个组件,这个组件可以是一个类组件,也可以是一个函数组件。

如果是一个类组件,那么它必须继承自React.Component,如果是一个函数组件,那么它必须是一个函数,这个函数接收一个参数props,然后返回一个React元素。

它还有一些特殊的值,例如FragmentStrictModePortalContextProfilerSuspenseLazy,这些值都是React中的特殊类型,它们都是React中的组件。

在源码中,这些特殊类型都是通过Symbol来进行标识的,例如Fragment的值是Symbol.for("react.fragment"),具体可以直接看ReactSymbols源码

props

传入的props中有两个特殊的属性:keyref

至于这两个属性的作用,我们后面再慢慢讲。

children

对于children,它是该元素的子元素,它可以是一个字符串,也可以是一个React元素,也可以是一个数组,也可以是一个函数,也可以是一个布尔值,也可以是一个数字,也可以是一个对象,也可以是一个Symbol,也可以是一个undefined,也可以是一个null。

在源码中,通过判断arguments的长度,来判断children的类型,如果长度为3,那么直接将children赋值给props,如果长度大于3,那么就将children赋值给一个数组,然后将这个数组赋值给props。

在经过一系列的处理之后,会调用ReactElement方法,这个方法会返回一个React元素。

React.createElement

当然,你也可以直接使用React.createElement进行创建子元素。

import React from 'react';
export default function TestComponent() {
return React.createElement(
'h1',
null,
'这是使用React.createElement创建的元素',
);
}

$$typeof

需要值得注意的是,这个函数中有一个$$typeof属性,这个属性是用来标识这个对象是一个React元素的,它的值是Symbol.for("react.element")

$$typeof它的作用是用来标识这个对象是一个React元素的,所以React需要通过这个属性来标识这个对象是一个React元素,其实它是为了防止XSS攻击,因为JSON中是不支持Symbol类型的,所以React会检测element.$$typeof,如果元素丢失或者无效,会拒绝处理该元素。

这里就得提到React提供的另一个全局API React.isValidElement

export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}

可以发现,React在验证一个元素是不是合法元素时,会去验证$$typeof这个值。

React 17

React17使用了的babel包中,JSX 不会再被直接转换为 React.createElement

那么新的JSX转换器又会将JSX语句编译成什么呢?

如下所示:

function App() {
return <h1>Hello World</h1>;
}

编译结果:

// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}

根据文档中所说:jsx这个函数的属性即参数如下:

function jsx(type, props, key) {
return {
$$typeof: ReactElementSymbol,
type,
key,
props,
};
}

与React.createElement最大的不同是,现在children需要放在第二个参数,也就是props中。

至于这部分内容,React官方在介绍全新的 JSX 转换中有详细的讲解。

了解了上面这些内容后,我们就可以开始了解React中的一个重要的概念Fiber。

参考资料: