从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元素。
它还有一些特殊的值,例如Fragment,StrictMode,Portal,Context,Profiler,Suspense,Lazy,这些值都是React中的特殊类型,它们都是React中的组件。
在源码中,这些特殊类型都是通过Symbol来进行标识的,例如Fragment的值是Symbol.for("react.fragment"),具体可以直接看ReactSymbols源码。
props
传入的props中有两个特殊的属性:key,ref。
至于这两个属性的作用,我们后面再慢慢讲。
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。
参考资料: