我们用react来编写组件的时候,都是以jsx的形式写编写视图层,除此之外,还可以通过createElement这个方法来编写组件,createElement是react库核心的API之一。
createElement
在没有MVVM框架之前,我们想改变页面的状态达到我们想要的结果,通常情况下,我们都是直接操作dom元素的时候,对性能的消耗是非常大的。后来有了虚拟dom(virtua lDom)这一概念。虚拟dom主要是一个对象,用来描述dom节点的状态。
我们假设这个对象几个字段
- type: 用来描述dom是哪种类型,可以是原生标签,文本节点,函数组件或者是类组件
- config: 用来描述dom原生上特有的属性
- children: 用来描述该节点下的子节点
新建一个存储常量的文件const.js,定义一个变量TEXT用来表示文本节点,这里是为了简单方便而这么做
export const TEXT = "TEXT"
复制代码
先尝试最简版实现createElement,新建一个index.js
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
}
}
复制代码
这里children为了简单方便,处理成数组,这里的文本节点需要进一步处理,定义一个函数
function createTextNode(text) {
return {
type: TEXT,
props: {
nodeValue: test,
children: []
}
}
}
复制代码
简版的createElement基本实现,这个API是核心API之一,在index.js文件下这导出
export default { createElement }
复制代码
render
在我们写的react项目中,需要一个主入口文件,需要挂载我们的根节点,这个方法react-dom库帮我们实现,暴露的render函数接收两个参数,新建一个react-dom.js文件,编写核心的render。
我们都知道react的组件类型有函数组件和类组件,为了区别是哪种组件,我们需要定义一个方法来判断,创建一个文件Component.js来简单实现
function Component(props) {
this.props = props
}
Component.prototype.isReactComponent = {}
export default Component
复制代码
简略版
import { TEXT } from './const'
function render(vnode, container) {
const node = createNode(vnode)
container.appendChild(node)
}
复制代码
render方法接收两个参数,一个是react元素,另一个是挂载的容器。
createNode方法用来创建真实dom节点,通过判断type类型来判断,当type类型为函数的时间,需要判断是类组件还是函数组件,通过原型上是否有isReactComponent来判断是不是类组件。
function createNode(vnode) {
let node
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.prototype.isReactComponent ?
updateClassComponent(vnode) :
updateFunctionComponent(vnode)
}
reconcieChildren(props.children, node)
updateNode(node, props)
return node
}
复制代码
当dom节点下面还有许多子节点,需要额外去处理,我们定义一个reconcileChildren方法,接收两个参数分别是当前的children和node节点,会自动去递归判断。
function reconcileChildren(children, node) {
for (let i = 0; i < children.length; i++) {
let child = children[i]
render(child, node)
}
}
复制代码
在创建节点的过程中,需要更节点,需要把当前node节点该有的props属性带上
function updateNode(node, nextVal) {
Object.keys(nextVal)
.filter(k => k !== 'children')
.forEach(k => {
node[k] = nextVal[k]
})
}
复制代码
函数组件和类组件分别由updateFunctionComponent和updateClassComponent这俩函数来处理,类组件上有render方法返回一个react元素,需要通过new实例化一个实例来调用render方法返回react元素。
function updateFunctionComponent(vnode) {
const { type, props } = vnode
let vvnode = type(props)
const node = createNode(vvnode)
return node
}
function updateClassComponent(vnode) {
const { type, props } = vnode
const instance = new type(props)
const vvnode = instance.render()
const node = createNode(vvnode)
return node
}
复制代码
这里只是简单的做了粗略的处理,后期有时间我会继续完善!!!