下面来实现的是渲染器的跨平台能力
先从渲染一个普通的标签开始,可以用如下vnode对象来描述一个
标签
const vnode = {
type: 'h1',
children: 'hello'
}
上面的vnode对象,使用type属性来描述vnode类型,当type属性时字符串类型值时,可以认为其描述的是普通标签,并使用该type属性的字符串值作为标签的名称。这样我们可以使用render函数来渲染,如下面代码所示:
const vnode = {
type: 'h1',
children: 'hello'
}
// 创建一个渲染器
const renderer = createRenderer()
// 调用render函数渲染该vnode
renderer.render(vnode, document.querySelector('#app'))
为了完成渲染工作,需要补充patch函数
function createRenderer(){
function patch(n1,n2,container){
// 如果n1不存在,意味着挂载,则调用mountElement函数完成挂载
if(!n1){
mountElement(n2,container)
}else{
// n1存在,意味着打补丁,暂时省略
}
}
// 省略其他代码
}
当n1不存在时,意味着没有旧vnode,此时只需要执行挂载即可。这时调用mountElement完成挂载,其实现如下:
function mountElement(vnode,container){
// 创建DOM元素
const el = document.createElement(vnode.type)
// 处理子节点,如果子节点是字符串,代表元素具有文本节点
if(typeof vnode.children === 'string'){
// 因此值需要设置元素的textContent属性即可
el.textContent = vnode.children
}
// 将元素添加到容器中
container.appendChild(el)
}
知识扩展
createElement() 方法通过指定名称创建一个元素
document.createElement(nodename)
nodename是创建元素的名称。
textContent 属性设置或者返回指定节点的文本内容。
如果设置了 textContent 属性, 任何的子节点会被移除及被指定的字符串的文本节点替换。
上面的代码实现了挂载一个普通标签元素的工作,但是我们的目的是设计一个不依赖于浏览器平台的通用渲染器,而mountElement函数内调用了大量依赖于浏览器的api,所以第一步要做的就是将这些浏览器特有的api抽离。具体可以将这些操作DOM的API作为配置项,该配置项可以作为createRenderer的参数,如下面代码
// 创建renderer时传入配置项
const renderer = createRenderer({
// 用于创建元素
createElement(tag){
return document.createElement(tag)
},
// 用于设置元素的文本节点
setElementText(el,text){
el.textContent = text
},
// 用于在给定的parent下添加指定元素
insert(el,parent,anchor = null){
parent.insertBefore(el,anchor)
}
})
这里直接把用于操作DOM的API封装为一个对象,并将其传递给createRenderer函数。这样在mountElement等函数内就可以通过配置项来取得操作DOM的API了
function createRenderer(options){
// 通过options得到操作DOM的API
const{
createElement,
insert,
setElementText
} = options
// 在这个作用域内定义的函数都可以访问那些API
function mountElement(vnode, container){
// ...
}
function patch(n1, n2, container){
// ...
}
function patch(n1, n2, container){
// ...
}
return {
render
}
}
接着从配置项取出得到的API重新实现mountElement函数:
function mountElement(vnode, container){
// 调用createElement函数创建元素
const el = createElement(vnode.type)
// 处理子节点,如果子节点是字符串,代表元素具有文本节点
if(typeof vnode.children === 'string'){
// 调用setElementText设置元素的文本节点
setElement(el,vnode.children)
}
// 调用insert函数将元素插入到容器内
insert(el,container)
}
这样通过传入不同的配置项,就能够完成非浏览器环境下的渲染工作。