引言
我们平时使用react开发一般都是这个结构:
import React from 'react';
import ReactDOM from 'react-dom';
const dom = <div>test source</div>;
ReactDOM.render(dom, document.getElementById("root"));
dom:JSX代码,需要用babel解析成虚拟dom,其实就是babel里调用了react的createElement方法,将jsx解析成虚拟dom(可能描述不准确,只是个人浅见)
ReactDOM.render: 将虚拟dom转换成真实dom并挂到根节点root上
先介绍一下react的相关包,我们平时引入的
React都是引自react.js,主要就是关于react的一些函数,比如React、Component、Context等;我们class调用的render()引用自react-dom顾名思义,就是操作dom相关的都在这里。 所以我们主要就是实现React的createElement和ReactDOM的render()。
代码实现
测试用例:
// import React from 'react';
// import ReactDOM from 'react-dom';
// 调用自己写的方法
import React from "./wReact";
import ReactDOM from "./wReact/ReactDOM";
const dom = (
<div>
<p>111111</p>
</div>
);
ReactDOM.render(dom, document.getElementById("root"));
实现createElement(type, props, ...children)
有人可能要问,我怎么没看到哪里调用了这个方法? 我当时也有这个疑问,于是我看了很多文章,发现,我们写的代码里确实没有调用痕迹,但是其实是调用了,在babel编译jsx的时候,调用了React的createElement方法,生成虚拟dom,生成的虚拟dom再由render函数渲染到真实dom节点。
function createElement(type, props, ...children) {
// type: 必填; 表示元素的类型,比如:h1, div 等
// props: 必填;表示该元素上的属性,使用 JavaScript 对象方式表示
// children: 非必填;表示该元素内部的内容,可以是文字,可以继续嵌套另外一个React.createElement
let defaultProps = {};
// 处理默认属性,props优先级高于defaultProps
if(type && type.defaultProps){
defaultProps = type.defaultProps;
}
return {
type,
props: {
...defaultProps,
...props,
// child(item) 可能是数组或者字符串,此处只是简单处理
children: children.map(item => typeof item === 'object' ? item : createTextNode(item))
}
}
}
function createTextNode(value) {
return {
type: 'TEXT',
props: {
nodeValue: value,
children: []
},
}
}
export default {
createElement
}
实现 ReactDOM.render(vnode)
// vnode => node
function render(vnode, container) {
const node = createNode(vnode);
container.appendChild(node)
}
// 创建节点
function createNode(vnode){
const {type, props} = vnode;
let node;
if(typeof type === 'function'){
node = type.isReactComponent ? updateClassComponent(vnode) : updateFunctionComponent(vnode);
}else if(type === 'TEXT'){
// 创建文本节点
node = document.createTextNode("");
}else if(type){
// 通过指定名称创建一个元素
node = document.createElement(type);
} else {
// 创建了一虚拟的节点对象,节点对象包含所有属性和方法。
// 就是创建一个空的节点,渲染的时候渲染此节点的所有子孙节点
node = document.createDocumentFragment();
}
// 处理defaultProps
if(type && type.defaultProps){
const defaultProps = type.defaultProps;
for (const key in defaultProps) {
if(props[key] === undefined){
props[key] = defaultProps[key]
}
}
}
updateNode(node, props);
reconcilerChildren(props.children, node);
return node;
}
// 对vnode的children进行操作
function reconcilerChildren(children, node){
for (let i = 0; i < children.length; i++) {
const child = children[i];
// 遍历 创建元素
// 判读child类型 当前child 可能是数组和字符串
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(item => item !== 'children').forEach(key => {
// 以on开头 说明是个事件
if(key.slice(0, 2) === 'on'){
const eventName = key.slice(2).toLocaleLowerCase();
node.addEventListenter(eventName, nextVal[key]);
}else{
node[key] = nextVal[key];
}
});
}
// class组件返回node
function updateClassComponent(vnode){
// type: class本身
const {type, props} = vnode;
const cmp = new type(props); // 实例化
const vvnode = cmp.render(); // class 的render函数里返回了jsx(虚拟dom)
const node = createNode(vvnode); // vnode 转换为 node
return node;
}
// function组件返回node
function updateFunctionComponent(vnode){
const {type, props} = vnode;
const vvnode = type(props);
const node = createNode(vvnode);
return node;
}
export default {
render
}
简单实现,欢迎指正