1、第一步根据生成的render函数生成虚拟节点
1.1 创建元素节点
export function createElement (vm,tag,data = {},...children) { // 返回虚拟节点 标签
return vnode(vm,tag,data,children,data.key,undefined)
}
function vnode(vm,tag,data,children,key,text) {
return {
vm,
tag,
data,
children,
key,
text
}
}
1.2 创建文本节点
export function createText (vm,val) { // 文本节点
return vnode(vm,undefined,undefined,undefined,undefined,val)
}
function vnode(vm,tag,data,children,key,text) {
return {
vm,
tag,
data,
children,
key,
text
}
}
import { isObject } from "./util"
import { createElement, createText } from "./vdom"
export function renderMixins(Vue) {
Vue.prototype._c = function () { // createElement 创建元素型节点
const vm = this;
return createElement(vm,...arguments)
}
Vue.prototype._v = function(text) {
const vm = this;
return createText(vm,text)
}
Vue.prototype._s = function(val) {
// console.log('s',arguments)
if (isObject(val)) return JSON.stringify(val)
return val
}
Vue.prototype._render = function () {
const vm = this
const { render } = vm.$options
// console.log(render.toString(),vm)
let vnode = render.call(vm)
// console.log(vnode)
return vnode
}
}
2、第二步 根据虚拟节点创建真实的节点 替换原来的节点
// 将虚拟节点变成真实节点
export function patch(el,vnode) {
// 删除老节点 根据vnode创建新节点 替换掉老节点
const elm = createElm(vnode) // 根据虚拟节点创建真实节点
const parentNode = el.parentNode
parentNode.insertBefore(elm,el.nextSibling) // el.nextSibling不存在就是null 如果为null insertBefore就是appendChild
parentNode.removeChild(el)
return elm
}
function createElm (vnode) {
let { tag, data, children,text} = vnode
if (typeof tag === 'string') { // 根据tag的类型判断是否是文本节点
vnode.el = document.createElement(tag) // 创建节点
// 如果data有属性 我们需要将data上的属性设置到元素上
updateProperties(vnode.el,data)
children.forEach(child => {
vnode.el.appendChild(createElm(child))
})
} else {
vnode.el = document.createTextNode(text) // 创建文本节点
}
// console.log('el',vnode)
return vnode.el
}
function updateProperties(el,props = {}) {
for (let key in props) {
if (key === 'style') {
for (let styleName in props[key]) {
el.style[styleName] = props[key][styleName]
}
} else if (key === 'class') {
el.className = props[key]
} else {
el.setAttribute(key,props[key])
}
}
}
3、将真实节点挂载到$el上 更新视图
import { patch } from "./vdom/patch"
export function mountComponent(vm) {
let updateComponent = () => {
vm._update(vm._render())
}
updateComponent()
}
export function lifeCycleMixin(Vue) {
Vue.prototype._update = function (vnode) {
// 采用的是 先序深度遍历 创建节点 (遇到节点就创造节点 递归创建)
const vm = this
vm.$el = patch(vm.$el,vnode)
}
}
通过$mount()挂载
import { compileToFunction } from "./compiler"
import { mountComponent } from "./liftcycle"
export function initMixins (Vue) {
Vue.prototype._init = function (options) {
const vm = this
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
Vue.prototype.$mount = function (el) {
const vm = this
const opts = vm.$options
el = document.querySelector(el) // 获取真实的元素
vm.$el = el // 页面真实的元素
if (!opts.render) {
// 模板编译
let template = opts.template
if (!template) {
template = el.outerHTML
}
let render = compileToFunction(template)
opts.render = render
}
// 这里已经获取到了一个render函数
mountComponent(vm)
}
}