MiniReact系列之实现一个简易createElement(一)
完整代码git地址:github.com/nocodig/min…
之前写了两篇文章有点水,大家别介意,原谅菜逼~今天这个菜逼又开始瞎胡扯了,可能继续很水,这个菜逼也在努力提高水平不是,大家没事点个赞,给点鼓励(✧◡✧),废话不多说,开整
一、判断是否是组件
组件与浏览器中原生节点差别在生成VirtualDOM中的type属性,组件的type属性是一个function,而浏览器原生的type属性是字符串
之前已经说到组件与浏览器的原生元素差别是VirtualDOM的type属性,所以isFunction只需要判断VirtualDOM的type属性是不是一个方法,函数组件的type就是一个方法,而类组件的type是一个类的构造函数
// src/MiniReact/isFunction.js
export default function isFunction(virtualDOM) {
return virtualDOM && typeof virtualDOM.type === 'function'
}
在之前的文章中,写到mountElement方法,在里面需要判断是组件还是原生的元素,代码需要就需要进行修改,如果是组件的话,通过mountComponet进行解析,具体实现的话我们接着往下看
// src/MiniReact/mountElement.js
export default function mountElement(virtualDOM, container) {
if (isFunction(virtualDOM)) {
// 渲染Component,代码如何实现在后面
mountComponent(virtualDOM, container)
} else {
// 渲染NativeElement
mountNativeElement(virtualDOM, container);
}
}
二、区分类组件和函数组件
渲染一个组件可能是类组件或者函数组件,二者存在差异,类组件是通过原型上调用render方法进行渲染,而函数组件是直接掉调用即可,所以需要在mountComponent方法中进一步区分是通过函数组件调用,还是通过类组件的render方法,需要实现一个isFunctionComponent用来辨别组件是否是函数组件,判断virtualDOM的type属性,是不是一个方法,并且它的原型原型上没有render方法
// src/MiniReact/isFunctionComponent.js
import isFunction from "./isFunction"
export default function isFunctionComponent(virtualDOM) {
const type = virtualDOM.type
return (
type && isFunction(virtualDOM) && !(type.prototype && type.prototype.render)
)
}
三、渲染函数组件
通过上面的方法,我们已经能够区分出来渲染是浏览器DOM元素还是组件,而且也能够区分出来是函数组件还是类组件,接下来就需要考虑一点,如何将函数组件或者类组件渲染出来,这一部分我们先只考虑如何实现函数组件的渲染,类组件的渲染我们在后面进行处理
组件的type是一个方法,那么我们直接调用type方法即可转化成我们的virtualDOM
// src/MiniReact/mountComponent.js
....
function buildFunctionComponent(virtualDom) {
return virtualDom.type()
}
....
但是,这个时候我们可能会向组件里面传递props,上面的写法是没有办法传递props的,所以我们需要将props传入到virtualDOM.type方法里面,还需要考虑的是有是props参数有时为空,我们需要做下兼容处理
// src/MiniReact/mountComponent.js
...
function buildFunctionComponent(virtualDom) {
return virtualDom.type(virtualDom.props)
}
...
处理好组件的编译,下面我们需要考虑实现mountComponet,用来渲染组件。
首先我们需要将调用buildFunctionComponent方法,转化一下virtualDOM,并且用一个局部变量保存它,此时可能存在一个问题,我们转换后的virturlDOM可能还是个组件,我们需要递归调用mountComponent方法,将转化后的virtualDOM传进去,直到所有的virtualDOM都转化成浏览器DOM元素,最后执行mountNativeElement,挂载到页面上去。
// src/MiniReact/mountComponent.js
....
export default function mountComponent(virtualDOM, container) {
// 临时存储virtual
let nextVirtualDOM = null;
if (isFunctionComponent(virtualDOM)) {
// 编译函数组件转化成virtualDOM
nextVirtualDOM = buildFunctionComponent(virtualDOM);
} else {
// 类组件
}
if (isFunction(nextVirtualDOM)) {
// 如果virtualDOM里面存在组件
mountComponent(nextVirtualDOM, container)
} else {
// 确保virtualDOM将所有的组件都转换完成,进行页面挂载
mountNativeElement(nextVirtualDOM, container)
}
}
....
四、渲染类组件
我们现在写一个类组件,用于我们后面来对我们方法进行验证
// src/index.js
class Title extends MiniReact.Component {
render() {
return <div>Hello Class</div>
}
}
上面代码中存在一个问题,就是MiniReact.Component在哪里,我们写一个Componet类,Component类后面我们在写具体内容。
我们将Component类在MiniReact导出即可
// src/MiniReact/component.js
export default class Component {
constructor() {
}
}
// src/MiniReact/index.js
import createElement from "./createElement";
import render from "./render";
import Component from "./component";
export default {
createElement,
render,
Component
};
准备工作都尊卑好,下面我们实现下类组件的渲染,
上面我们已经把渲染函数组件方法实现了,在mountComponent中,我们首先判断了组件是函数组件还是类组件,显然,此时我们走到了else里面。
export default function mountComponent(virtualDOM, container) {
// 临时存储virtual
let nextVirtualDOM = null;
if (isFunctionComponent(virtualDOM)) {
// 编译函数组件转化成virtualDOM
nextVirtualDOM = buildFunctionComponent(virtualDOM);
} else {
// 类组件
}
.....
}
我需要一个函数来编译类组件,需要注意的是,此时的type是一个我们类组件的构造函数,我们需要先对类组件进行实例化,然后调用实例的render方法。
function buildClassComponent(virtualDOM) {
const component = new virtualDOM.type()
return component.render()
}
和函数组件一样,我们调用组件时,会传递参数,参数都存在props属性中,而我们的组件又是继承了Componet类,当Componet这个父类上存在props属性时,我们能组件也能拿到props,所以我们组件的构造函数调用super时,将props传入进去,在Componet中接收props即可。我们改造下Component类。
// src/MiniReact/component.js
export default class Component {
constructor(props) {
this.props = props
}
}
我们在实例化组件时,将virtualDOM.props传入即可。
function buildClassComponent(virtualDOM) {
const component = new virtualDOM.type()
return component.render(virtualDOM.props)
}
我们在判断组件是否是函数组件的判断里面的调用上面的方法即可,代码如下
import isFunction from "./isFunction";
import isFunctionComponent from "./isFunctionComponent";
import mountNativeElement from "./mountNativeElement";
export default function mountComponent(virtualDOM, container) {
let nextVirtualDOM = null;
if (isFunctionComponent(virtualDOM)) {
// 函数组件
nextVirtualDOM = buildFunctionComponent(virtualDOM)
} else {
// 类组件
nextVirtualDOM = buildClassComponent(virtualDOM)
}
if (isFunction(nextVirtualDOM)) {
mountComponent(nextVirtualDOM, container)
} else {
mountNativeElement(nextVirtualDOM, container)
}
}
function buildFunctionComponent(virtualDom) {
return virtualDom.type()
}
function buildClassComponent(virtualDom) {
// 由于virtualDom的type是一个类,我们需要先对其进行实例化,然后调用render方法
const component = new virtualDom.type()
return component.render()
}
使用buildClassComponent将类组件转换成virtualDOM,接下来的会判断nextVirtualDOM是不是一个组件,如果是递归调用mountComponent,知道将所以的组件都转换成不是组件的virtualDOM,最后挂载到页面上。
好了,水完了,感谢大家的捧场,欢迎大家多提提意见,后面改正