本节的重点是 jsx,虚拟DOM以及源码中如何将jsx转为DOM。
- 认识jsx
- 体验虚拟DOM
- 区分新老babel转换jsx的不同之处
jsx 中的三个重点问题(面试):
- jsx 的本质是什么,它和 js 之间到底是什么关系?
- 为什么要用 jsx,不用的后果是什么?
- jsx 背后的功能模块是什么,这个功能模块都做了哪些事情?
jsx 的本质是 JavaScript 的一种语法扩展,它和模版语法很接近,但是它充分具备 JavaScript 的能力。
Facebook 公司给 jsx 的定位是:jsx 是 JavaScript 的“扩展”,而非 JavaScript 的某个版本,这就直接决定了浏览器并不会像天然支持 JavaScript 一样地支持 JSX。
自己编写的jsx代码可以通过babel转为一个JavaScript函数的调用。你可以在- babel进行在线转换代码,查看自己写的jsx转为JavaScript方法调用的结果。
参考下图:
新版转换(React17及以后)
老版转换
JSX及其本质
React17后不用再主动引入 React 而直接而使用在文件中 jsx 语法。因为新版项目中将 jsx不再转为 React.createElement,但是babel转换后的代码在浏览器中的执行结果是一样的——虚拟DOM对象。
//在React17以前,babel转换是老的写法
const babel = require('@babel/core');
const sourceCode = `
<h1>
hello<span style={{ color: 'red' }}>world</span>
</h1>
`;
const result = babel.transform(sourceCode, {
plugins: [
["@babel/plugin-transform-react-jsx", { runtime: 'classic' }]
]
});
console.log(result.code);
// 打印代码如下
React.createElement("h1", null, "hello", React.createElement("span", {
style: {
color: 'red'
}
}, "world"));
React17后的babel编译结果:
const babel = require('@babel/core');
const sourceCode = `
<h1>
hello<span style={{ color: 'red' }}>world</span>
</h1>
`;
const result = babel.transform(sourceCode, {
plugins: [
["@babel/plugin-transform-react-jsx", { runtime: 'automatic' }]
]
});
console.log(result.code);
// 打印代码如下
import { jsx } from "react/jsx-runtime";
jsx("h1", {
children: ["hello", jsx("span", {
style: {
color: 'red'
},
children: "world"
})]
});
// React.createElement=jsx
新的编译模式后,子节点直接以对象props中的children属性值存在,以前的字节点是作为第3个及其往后参数的方式传入createElement,在createElement中在将他们作为props的children属性的属性值。
之前项目的情况:
import React from 'react';
const App = (props) => {
return <div>hello world!</div>;
};
export default App;
// 上面的代码不引入React的话,在之前的项目中无法编译。
//因为jsx最后被转为React.createElement形式,
//所以看似没有依赖React,实则是有依赖的。 eslint中提示React引入而未使用的尴尬。
新版的情况:
const App = (props) => {
return <div>hello world!</div>;
};
export default App;
// 可以直接这么写,编译时会自动引入一个包 import jsx from 'jsx' 最后将jsx转为 jsx()函数调用的方式。
JSX的编译和后续执行:
包含jsxDEV函数调用的代码在发送到浏览器后,浏览器会根据源码中定义的该jsxDEV方法的代码逻辑进行执行,最后返回一个虚拟DOM。
function ReactElement(type, key, ref, props) {
return {//这就是React元素,也被称为虚拟DOM
$$typeof: REACT_ELEMENT_TYPE,
type,//h1 span
key,//唯一标识
ref,//后面再讲,是用来获取真实DOM元素
props//属性 children,style,id
}
}
jsxEDV函数在浏览器中调用后生成的结构
<h1>
hello<span style={{ color: 'red' }}>world</span>
</h1>
每个虚拟DOM节点会有一个类型属性——$$typeof
对于一个虚拟DOM节点的children属性,可能是一个字符串,数字,对象或者数组,其中数组的中的每一项元素可以是前面3中的某一种。
有一点需要注意,上面编写的jsx都是直接使用的原生的标签,如h1,span等等,所以它们生成的函数调用的第一个参数都是字符串的'h1'或者'span'等。
如果你编写的使用一个函数组件,如下代码:
function FunctionComponent(){
return (<h1>
hello <span style={{ color: 'red' }}>world!</span>
</h1>)
}
let element = <FunctionComponent number={1}></FunctionComponent>
// 编译后的结果
function FunctionComponent() {
return /*#__PURE__*/React.createElement("h1", null, "hello ", /*#__PURE__*/React.createElement("span", {
style: {
color: 'red'
}
}, "world!"));
}
var element = /*#__PURE__*/React.createElement(FunctionComponent, {
number: 1
});
读者一定要注意对于函数组件的jsx,生成的函数React.createElement调用中第一个参数就是函数本身,也就是说它是可以直接被执行的。