学习虚拟dom之前,当然的知道jsx是干嘛用的,就像我们吃饭,吃西餐时得学习用刀叉,刚开始会吃饭时得学会怎么用筷子。
JSX就是大家所说的语法糖,React 使用 JSX 来替代常规的 JavaScript。此外,JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
babel-loader
会预编译JSX为React.createElement(...)
,后面会展开说明。
dom+jsx
的设计一开始就有,vue则是演进过程中才出现的编译前:
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'));
核心精简后:
const React = { createElement, Component }
最核心的api:
React.createElement
:创建虚拟DOMReact.Component
:实现自定义组件ReactDOM.render
:渲染真实DOMReactDOM.render(element, container[, callback])
当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 DOM 差分算法(DOM diffing algorithm)进行高效的更新。
如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。
首先,我们将传入的节点定义转换为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 };
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 };
class Component {
static isReactComponent = {};
constructor(props) {
this.props = props;
}
}
export default Component;
React.createElement(type,props,...children)
React.createElement()
执行结束后得到一个JS对象即vdom,它能够完整描述dom结构ReactDOM.render(vdom, container)
可以将vdom转换为dom并追加到container中