React虚拟dom原理(看源码啦~)

学习虚拟dom之前,当然的知道jsx是干嘛用的,就像我们吃饭,吃西餐时得学习用刀叉,刚开始会吃饭时得学会怎么用筷子。

JSX

1、 什么是JSX

JSX就是大家所说的语法糖,React 使用 JSX 来替代常规的 JavaScript。此外,JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

2、为什么需要JSX
  • 开发效率:使用 JSX 编写模板简单快速。
  • 执行效率:JSX编译为 JavaScript 代码后进行了优化,执行更快。
  • 类型安全:在编译过程中就能发现错误。
3、原理

babel-loader会预编译JSX为React.createElement(...),后面会展开说明。

4、与vue的不同之处
  • react中虚拟dom+jsx的设计一开始就有,vue则是演进过程中才出现的
  • jsx本来就是js扩展,转义过程简单直接的多。而vue把template编译为render函数的过程需要
    复杂的编译器转换字符串-ast-js函数字符串
5、举个栗子,看看JSX编译前后有什么不一样

编译前:

import React from "react"; 
import ReactDOM from "react-dom"; 
import "./index.css"; 
class ClassCmp extends React.Component { 
	render() { 
		return ( 
			<div className='app'> Hello {this.props.name} </div> 
			); 
	}
}
function FuncCmp(props) { 
	return <div>name: {props.name}</div>; 
}
const jsx = ( 
	<div> 
		<p>我是内容</p> 
		<FuncCmp name="我是function组件" /> 
		<ClassCmp name="我是class组件" /> 
	</div> 
);
ReactDOM.render( jsx, document.getElementById('hello-example') );

编译后:

class ClassCmp extends React.Component { 
render() { 
	return React.createElement( 
		"div", 
		{ "class": "app" }, 
		"Hello ", 
		this.props.name 
	); 
} }
function FuncCmp(props) { 
	return React.createElement( 
		"div", 
		null, 
		"name: ", 
		props.name 
	); 
}
const jsx = React.createElement( 
	"div", 
	null, 
	React.createElement( 
		"p", 
		null,
		 "我是内容" 
	 ),
	 React.createElement(FuncCmp, { name: "我是function组件" }),
	 React.createElement(ClassCmp, { name: "我是class组件" }) );
ReactDOM.render(jsx, document.getElementById('hello-example'));

React核心api

核心精简后:

const React = { createElement, Component }

最核心的api:

  • React.createElement:创建虚拟DOM
  • React.Component:实现自定义组件
  • ReactDOM.render:渲染真实DOM

render()

ReactDOM.render(element, container[, callback])

当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 DOM 差分算法(DOM diffing algorithm)进行高效的更新。

如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。

实现React.createElement、ReactDom.render、Component

1、CreateElement

首先,我们将传入的节点定义转换为vdom。

注意节点类型:文本节点、HTML标签节点、function组件、class组件、fragment、其他如portal等节点。

创建src/index.js

import React from "./kreact/";
import ReactDOM from "./kreact/react-dom"; 
import Component from "./kreact/Component"; 
import "./index.css";

class ClassComponent extends Component{
	render(){
		return 
			<div ClassName="border">{this.props.name}</div>
	}
}
function FunctionComponent({name}){
	return(
		<div className="border">
			{name}
			<button onClick={()=>console.log("hello")}>click</button>
		</div>
	);
}
const Com2 = React.cloneElement(
	<input/>,
	{
	placeholder:"this is placeholder",
	value: '',
	onChange:()=>{}
});

const jsx = (
	<div classname="border">
		<p>world</p>
		<FunctionComponent name="函数组件"/>
		<ClassComponent name="class组件"/>
		{Com2}
		<>
			<h1>文字1</h1>
			<h2>文字2</h2>
		</>

		{[1,2,3].map(item)=>(
			<div key={item}>文字{item}
		))}
		</div>
);
ReactDOM.render(jsx, document.getElementById("root"));
console.log("version", React.version); //sy-log

创建./src/kreact/index.js,它需要包含createElement方法

import {TEXT} from "./const";
function createElement(type, config, ...children) {
	if (config) {
		delete config.__self; 
		delete config.__source;
	}
	const props = { 
		...config, 
		children: 
		children.map(child => 
		typeof child === "object" ? child : createTextNode(child) 
	) };
	return { 
		type, 
		props 
	}; 
}
function createTextNode(text) { 
	return { 
		type: TEXT,
		props: { 
			children: [],
			nodeValue: text 
		} 
	}; 
}
export default { createElement };
2、ReactDOM.render
import {TEXT} from "./const";
function render(vnode, container) {
 	console.log("vnode", vnode); //sy-log 
 	// vnode _> node 
 	const node = createNode(vnode, container);
 	container.appendChild(node); 
}
// 返回真实的dom节点
function createNode(vnode, parentNode) {
	let node = null; 
	const {type, props} = vnode;
	if (type === TEXT) {
		node = document.createTextNode(""); 
	} else if (typeof type === "string") {
		node = document.createElement(type);
	} else if (typeof type === "function") {
		node = type.isReactComponent
		? updateClassComponent(vnode, parentNode) 
		: updateFunctionComponent(vnode, parentNode);
	} else { 
		node = document.createDocumentFragment(); 
	}
	reconcileChildren(props.children, node);
 	updateNode(node, props);
 	return node;
}

function reconcileChildren(children, node) {
	for (let i = 0; i < children.length; i++) {
		let child = children[i]; 
		if (Array.isArray(child)) {
			for (let j = 0; j < child.length; j++) { 
				render(child[j], node); } 
		} else{ 
				render(child, node); 
		}
	}
}
function updateNode(node, nextVal) {
	Object.keys(nextVal)
		.filter(k => k !== "children")
		.forEach(k => {
			if (k.slice(0, 2) === "on") {
				let eventName = k.slice(2).toLocaleLowerCase();
				node.addEventListener(eventName, nextVal[k]);
			} else {
				node[k] = nextVal[k]; 
			} 
	}); 
}
function updateFunctionComponent(vnode, parentNode) { 
	const {type, props} = vnode; 
	let vvnode = type(props); 
	const node = createNode(vvnode, parentNode); 
	return node; 
}

function updateClassComponent(vnode, parentNode) { 
	const {type, props} = vnode; 
	let cmp = new type(props); 
	const vvnode = cmp.render(); 
	const node = createNode(vvnode, parentNode); 
	return node; 
}
export default { render };
3、Component
class Component { 
	static isReactComponent = {};
	constructor(props) { 
		this.props = props; 
	} 
}
export default Component;

总结

  1. webpack+babel编译时,替换JSX为React.createElement(type,props,...children)
  2. 所有React.createElement()执行结束后得到一个JS对象即vdom,它能够完整描述dom结构
  3. ReactDOM.render(vdom, container)可以将vdom转换为dom并追加到container中
  4. 实际上,转换过程需要经过一个diff过程。

你可能感兴趣的:(React,源码分享,react,虚拟dom,react源码)