1. what?
JSX是对js语法的扩展,使我们可以用类似xml方式描述视图
2. why?
执行快、类型安全、开发简单快速
3. how?
babel-loader会预编译jsx为 React.createElement(type, props, …chilren)
4. 虚拟dom
用来描述真实dom的js对象,由createElement生成。
虚拟dom不会重新排版和重绘,多少修改虚拟dom后,可一次修改真实dom需要改的部分(即只渲染局部);因为真实dom的排版和重绘效率很低,所以比直接改变dom的执行快;且因为会在node上预编译一遍,因此类型更安全。
5. JSX原理
5.1 虚拟dom结构
<div id="demo"></div>
<span>id</span>
<Comp name="testText" />
<Comp2 />
5.2 仿写JSX code
index.js
import React, {Component} from "./kcreact";
import ReactDOM from "./kreact-dom";
const users = [{ name: "jerry", id: 1 }]
const jsx = (
<div id="demo">
<span style="color: red">id</span>
<Comp name="testText" />
<Comp2 />
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
)
function Comp(props) {
return <h2>函数组件,{props.name}</h2>;
}
class Comp2 extends Component{
render() {
return <h2>class 组件</h2>
}
}
ReactDOM.render(jsx, document.querySelector("#root"))
kreact.js
// 实现Component
import {createVNode} from "./kvdom";
export class Component {
// 区分 function 和 class 组件
static isClassComponent = true
constructor(props) {
this.props = props
this.state = {}
}
setState(state) {
// ...
}
}
function createElement(type, props, ...children) {
// 传递类型有三种:1.原生标签,2.函数式组件, 3.class组件
// 使用vtype属性表示元素类型
props.children = children
// 判断组件类型
let vtype;
if (typeof type === 'string') {
// 原生标签,如:div、span
vtype = 1
} else if (typeof type === 'function') {
if (type.isClassComponent) {
// 类组件
vtype = 2
} else {
// 函数组件
vtype = 3
}
}
return createVNode(vtype, type, props)
}
export default {createElement}
kreact-dom.js
// vnode: 虚拟dom树
import {initVNode} from "./kvdom";
function render(vnode, container) {
container.appendChild(initVNode(vnode));
}
export default {render}
kvdom.js
// 转换vdom为dom
export function initVNode(vnode) {
// vnode是虚拟dom树
const {vtype} = vnode
if (!vtype) {
// 如果没有vtype,说明是文本节点(字符串)
return document.createTextNode(vnode)
}
if (vtype === 1) {
// 原生节点
return createElement(vnode)
} else if (vtype === 2) {
// class组件
return createClassComp(vnode)
} else {
// 函数组件
return createFuncComp(vnode)
}
}
function createElement(vnode) {
const {type, props} = vnode
const node = document.createElement(type)
// 属性处理
const {key, children, ...rest} = props
Object.keys(rest).forEach(attr => {
// 特殊处理的属性:htmlFor, className
if (attr === 'className') {
node.setAttribute('class', rest[attr])
} else { // 普通属性处理
node.setAttribute(attr, rest[attr])
}
})
// 递归处理可能存在的子元素
children.forEach(c => {
// 如果c是数组,需特殊处理
if (Array.isArray(c)) {
c.forEach(n=> node.appendChild(n))
} else {
node.appendChild(initVNode(c))
}
})
return node
}
function createFuncComp(vnode) {
// 这里的type 是个函数组件
const {type, props} = vnode
// 因为type 是函数组件,所以执行后,直接返回vdom
const newNode = type(props)
return initVNode(newNode);
}
function createClassComp(vnode) {
// 这里的type 是个class 组件
const {type, props} = vnode
// 创建class 组件的实例
const component = new type(props)
// 执行class 组件的render,得到vdom
const newNode = component.render()
return initVNode(newNode);
}
// vdom 的diff算法
export function createVNode(vtype, type, props) {
// 传递类型有三种:1.原生标签,2.函数式组件, 3.class组件
// 使用vtype属性表示元素类型
const vnode = {
vtype, type, props
}
return vnode
}
5.3 执行顺序
1.webpack+babel-loader 编译时,替换 JSX 为 React.createElement
2.createElement方法中通过 vtype 来识别 type 的类型
2.然后调用 kreact-dom 的 render 函数,并传入 vnode(虚拟节点,由createElement生成)
3.调用 kvdom 的 initVNode 函数,将 vdom 转换为真实的 dom
总结
1.webpack+babel-loader编译时,替换JSX为React.createElement(…)
2.所有React.createElement(…)执行结束会得到一个JS对象树,他能完整描述dom结构,称之为虚拟DOM
3.ReactDOM.render(vdom,container)可以将vdom转换为dom追加至container中
通过遍历vdom树,根据vtype不同,执行不同逻辑:vtype为1生成原生标签,vtype为2实例化class组件并将其render返回的vdom初始化,vtype为3直接执行函数将结果初始化