三.初次渲染视图
现在,我们已经实现了render函数,将数据渲染到页面上的操作已经进行了一半了(我们实现了将数据转成ast,再转成render函数),接下来就是将render函数进行调用,挂载到页面上。
export function initMixin(Vue) {
Vue.prototype._init = function(options){
//...
}
Vue.prototype.$mount = function(el) {
//...
mountComponent(vm, el); //组件的挂载
}
}
下面我们就开始实现这个**mountComponent(vm, el)**函数。
export function initlifeCycle() {
Vue.prototype._update = function (vnode) {
}
Vue.prototype._render = function (vnode) {
}
}
export function mountComponent(vm, el) {
vm.$el = el;
// 1.调用render方法产生虚拟节点 虚拟DOM
// vm._render()
// 2.根据虚拟DOM产生真实DOM
// vm._update(vm._render())
let updateComponent = () => {
// 将虚拟节点 渲染到页面上
vm._update(vm._render());
}
}
步骤
- 调用render方法产生虚拟节点 虚拟DOM -> vm._render() 返回虚拟DOM
- 根据虚拟DOM产生真实DOM -> vm._update() 虚拟节点产生真实DOM
- 插入到el元素中
一.调用render方法产生虚拟DOM
vm._render()
当调用_render方法时,其实我们应该调用原来我们自己生成的render方法。
并且因为我们的自定义render方法中出现了: _c, _v, _s 那么我们也需要实现这些方法。
export function initLifeCycle(Vue){
Vue.prototype._update = function(vnode){
//将vnode转换成真实dom
//...
}
Vue.prototype._v = function () {
// 创建文本
return createTextVNode(this,...arguments);
}
Vue.prototype._c = function () {
// 创建元素
return createElementVNode(this,...arguments);
}
Vue.prototype._s = function (val) {
if(typeof val !== 'object') return val
return JSON.stringify(val);
}
Vue.prototype._render = function(){
const vm = this;
// call: 让with中的this指向vm
return vm.$options.render.call(vm); //执行render函数拿到虚拟节点
}
}
上面提到的createTextVNode,createElementVNode函数我们都定义在另一个js文件中
//_c() 创建文本
export function createTextVNode(vm,text){
return vnode(vm,undefined,undefined,undefined,undefined,text)
}
//_v() 创建节点
export function createElementVNode(vm, tag, data = {
}, ...children){
if(data == null){
data = {
}
}
let key = data.key;
if(key){
//上面我们已经取出了key,所以可以删掉key
delete data.key
}
return vnode(vm,tag,key,data,children)
}
// 虚拟dom比ast功能更强大,可以增加一些自定义属性
function vnode(vm,tag,key,data,children,text){
return {
vm,
tag,
key,
data,
children,
text
}
}
export function isSameVNode(vnode1, vnode2){
return vnode1.tag === vnode2.tag && vnode1.key === vnode2.key
}
此时我们就已经产生了虚拟DOM
二.虚拟DOM产生真实DOM + 绑定到页面上
vm._update()
我们会实现一个patch方法,来将虚拟DOM转成真实DOM。
注意,patch方法既有初始化的功能,又有更新的功能。以后实现diff算法也是靠这个patch函数。
Vue.prototype._update = function(vnode){
//将vnode转换成真实dom
const vm = this;
const el = vm.$el;
//patch既有初始化的功能,又有更新的功能
vm.$el = patch(el,vnode); //将VNode虚拟节点挂载到el元素上
}
实现patch方法:
// patch(el,vnode)
export function patch(oldVNode, vnode){
const isRealElement = oldVNode.nodeType;
if(isRealElement){
//初渲染流程
const elm = oldVNode; //获取真实元素
const parentElm = elm.parentNode; //拿到父元素
let newElm = createElm(vnode); //这里就将虚拟DOM转成真实DOM: createElm函数
//此时元素已经创建好了,属性也绑定了
parentElm.insertBefore(newElm, elm.nextSibling) //插入新节点
parentElm.removeChild(elm) //删除老节点
return newElm
}else{
//diff算法
}
}
//将虚拟DOM转成真实DOM:创建真实元素
export function createElm(vnode){
let {
tag, data, children, text } = vnode;
if(typeof tag === 'string'){
//如果是标签
vnode.el = document.createElement(tag); //??? 将真实节点和虚拟节点对应起来
patchProps(vnode.el, {
}, data) //绑定属性
children.forEach(child => {
vnode.el.appendChild(createElm(child))
});
} else{
//如果是文本
vnode.el = document.createTextNode(text)
}
return vnode.el
}
//绑定属性
function patchProps(el, oldProps = {
}, props = {
}){
for(let key in newProps){
if(key === 'style'){
for(let styleName in newProps.style){
el.style[styleName] = newProps.style[styleName]
}
}else if(key === 'class'){
el.className= newProps.class
}else{
// 给这个元素添加属性 值就是对应的值
el.setAttribute(key,newProps[key]);
}
}
}