上一篇介绍了JSX的概念和简单实例:React(一)JSX概念和实例_Zoie_ting的博客-CSDN博客
一,创建React和ReactDOM
创建一个新的js,笔者这里叫treact.js,将原来的import React from 'react';修改为import React from './treact.';
import ReactDOM from 'react-dom';
import React from './treact';
const Comp = (props) => {
return (
<div>
<p>{props.name}</p>
</div>
)
};
const jsx = (
<div id="demo">
<p>hello</p>
<Comp name="world"></Comp>
</div>
);
ReactDOM.render(jsx,document.getElementById('root'));
treact.js:
function createElement() {
console.log(arguments)
}
export default { createElement };
运行一下,在控制台看到输出的内容是从里到外的,且第一个参数是类型,第二个是属性,第三是孩子:
因此,先实现让 createElement 能够输出类似的结构:
treact.js:
function createElement(type,props,...children) {
props.children = children;
delete props.__self;
delete props.__source;
console.log({type,props});
return {type,props}
}
export default { createElement };
treact-dom.js:
function render(vnode,el) {
el.innerHTML = JSON.stringify(vnode)
}
export default { render }
输出如下:
同样的由内向外,最后页面的显示是我们自己写的json字符串格式。
二,虚拟节点转化为真实DOM
1、确定转换的节点类型
- html标签:type是字符串
- function组件:type是function
- class组件:type也是function,打上标签,创建的时候表明是类组件
class Component {
static isComponent = true;
constructor(type,props){
this.props = props;
this.type = type;
}
}
//...
function createElement(type,props,...children) {
//...
let vtype;//虚拟节点类型
if(typeof type === 'string'){//html标签
vtype = 1
} else if(typeof type === 'function'){
if(type.isComponent) {//类组件
vtype = 2;
} else {//函数组件
vtype = 3;
}
}
return {vtype,type,props}
}
此时看一下输出:
2、处理不同节点类型
在render函数中,初始化渲染虚拟节点,新建initVnode函数,对三种情况分别处理,但实际上最终还是回到创建html中去:
function render(vnode,el) {
let node = initVnode(vnode);
el.append(node)
}
function initVnode(vnode) {
const {vtype} = vnode;
if(!vtype) {
return document.createTextNode(vnode);
}
if(vtype === 1) {
return createElement(vnode)
}
if(vtype === 2) {
return createClassElement(vnode);
}
if(vtype === 3) {
return createFuncElement(vnode);
}
}
创建html非常简单,常规操作,创建type类型的dom,给dom加上属性即可,需要注意的是有些特别属性,react写法不同,例如class属性写作className,style使用对象,需要把对象处理成字符串,且css属性名称也要从驼峰改成连字符,这里笔者不考虑那么多情况,直接加属性了:
function createElement(vnode) {
const { type, props } = vnode;
let node = document.createElement(type);
const { children, ...rest } = props;
Object.keys(rest).forEach((attr) => {
if (attr === 'style'){
node.setAttribute('style',Object.keys(rest[attr]).map((a) => a + ':' +rest[attr][a]).join(';'));
} else {
node.setAttribute(attr,rest[attr]);
}
});
return node;
}
对于函数组件和类组件,大同小异。
类组件只需要new一个相同的类,即props中的type,例如:new type(props)。由此得到一个类,但类组件通过render函数返回JSX,所以只需要调用一下这个类的render()就可以得到虚拟节点,再把虚拟节点传入initVode()......最终回到createElement()。
函数组件只需要执行一下该函数:type(props)即可拿到JSX,再把虚拟节点传入initVode()......最终回到createElement()。
注意,一定要return!!!一定要return!!!一定要return!!!
function createClassElement(vnode) {
const { type, props } = vnode;
const component = new type(props);
const vdom = component.render();
return initVnode(vdom);
}
function createFuncElement(vnode) {
const { type, props } = vnode;
const vdom = type(props);
return initVnode(vdom);
}
由于虚拟节点的树形结构,因此,在createElement中需要递归,将返回的节点挂载到当前节点上来:
children.forEach((vdom) => {
let dom = initVnode(vdom);
dom && node.appendChild(dom);
});
总结
本篇介绍了React.createElement和ReactDOM.render的实现思路:
- 在createElement中传入节点类型、属性、子节点,通过添加vtype给节点打上虚拟节点类型标签;
- 在render时根据vtype类型将虚拟节点转为真实节点,最终挂载到dom元素上去。
感兴趣可以继续看下一篇:React(三)自定义render处理map的虚拟节点和diff算法_Zoie_ting的博客-CSDN博客