1.Vue中模板被解析成虚拟Dom的形式
下面这段模板经过vue的解析会成为这样的一个虚拟Dom对象
<template>
<div onClick="alert('hello')">
<span>span</span>
<p>p</p>
</div>
</template>
//解析为虚拟Dom
const myVnode = {
tag:'div',
props:{
onClick:()=>alert('hello'),
},
children:[{tag:'span',children:'span'},{tag:'p',children:'p'}]
}
2.根据虚拟Dom的数据结果编写简单的渲染器实现基本功能
function myRender (vnode,container){
const el = document.createElement(vnode.tag) //创建真实dom节点
//遍历props所有方法,如果是以on开头,就是dom事件,为该节点添加事件
for(const key in vnode.props){
if(/^on/.test(key)){
el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
}
}
if(typeof vnode.children==='string'){
//判断children是否为字符串,如果是,创建文字节点并添加
//这里为什么不用innerHtml,因为如果字符串为<strong>aa</strong>,会被解析成加粗的节点
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
//如果children是数组,这个数组是当前节点的所有子节点的虚拟Dom,循环递归怎么渲染器方法
vnode.children.forEach(item=>myRender(item,el))
}
//将创建的真实节点加入已经有的真实节点
container.appendChild(el)
}
所有代码
<div id="app"></div>
<script>
const app = document.querySelector('#app')
function myRender (vnode,container){
const el = document.createElement(vnode.tag)
for(const key in vnode.props){
if(/^on/.test(key)){
el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
}
}
if(typeof vnode.children==='string'){
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
vnode.children.forEach(item=>myRender(item,el))
}
container.appendChild(el)
}
const myVnode = {
tag:'div',
props:{
onClick:()=>alert('hello'),
},
children:[{tag:'span',children:'span'},{tag:'p',children:'p'}]
}
myRender(myVnode,app)
</script>
运行结果
3.渲染组件功能
只渲染模板肯定的不行的,vue最突出的特色就是组件化开发,怎样才能渲染组件,首先就要理解什么是组件, 用vue官方的话来说,组价就是一组Dom元素的封装,所以我们就可以定义一个函数来代表组件,实质返回的也是虚拟dom对象
//封装的组件
const MyComponent = function(){
return {
tag:'div',
props:{
onClick:()=>alert('hello')
},
children:'component'
}
}
const vnode={ //虚拟Dom存储组件
tag:MyComponent
}
现在为了可以渲染组件,需要改造renderer函数
function renderer(vnode,container){
if(typeof vnode.tag==='string'){ //如果描述是普通标签元素,调用mountElement完成渲染
mountElement(vnode,container)
}else if(typeof vnode.tag==="function"){ //是组件的话走渲染组件的方法
mountComponent(vnode,container)
}
}
mountElement方法和上面渲染普通标签的方法一样
function mountElement(vnode,container){
const el = document.createElement(vnode.tag)
for(const key in vnode.props){
if(/^on/.test(key)){
el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
}
}
if(typeof vnode.children==="string"){
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
vnode.children.forEach(item=>mountElement(item,el))
}
container.appendChild(el)
}
mountComponent是处理组件的方法
function mountComponent(vnode,container){
const subtree = vnode.tag()
//执行封装组件的函数获得组件内的标签模板,就是获得组件的虚拟Dom对象
renderer(subtree,container)
//执行rendere渲染函数,因为组件里面可能有普通标签,或者还有组件
}
完整代码
const MyComponent = function(){
return {
tag:'div',
props:{
onClick:()=>alert('hello')
},
children:'component'
}
}
const vnode={
tag:MyComponent
}
function renderer(vnode,container){
if(typeof vnode.tag==='string'){
mountElement(vnode,container)
}else if(typeof vnode.tag==="function"){
mountComponent(vnode,container)
}
}
function mountElement(vnode,container){
const el = document.createElement(vnode.tag)
for(const key in vnode.props){
if(/^on/.test(key)){
el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
}
}
if(typeof vnode.children==="string"){
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
vnode.children.forEach(item=>mountElement(item,el))
}
container.appendChild(el)
}
function mountComponent(vnode,container){
const subtree = vnode.tag()
renderer(subtree,container)
}
renderer(vnode,app)
执行结果