• 大约 3 分钟

目标

  • 组件化原理
  • 手写vue2

组件化机制

组件声明:Vue.component()

initAssetRegisters(Vue) src/cord/global-api/assets.js

组件注册使用extend方法将配置转换为构造函数并添加到components选项

  • 全局声明 Vue.component()
  • 局部声明 components:{}

组件实例创建和挂载

观察生成的渲染函数

"with(this){return _c('div',{attrs:{"id":"demo"}},[
 _c('h1',[_v("虚拟DOM")]),_v(" "),
 _c('p',[_v(_s(foo))]),_v(" "),
 _c('comp') // 对于组件的处理并⽆特殊之处
],1)}"

整体流程

首先创建的是根实例,首次_render()时,会得到整棵树的VNode结构,其中自定义组件相关的主要有:createComponent()

组件VNode创建

createComponent()

创建组件实例并挂载,VNode转换为dom

整体流程:new Vue() -> $mount() -> vm_render() -> createElement() -> createComponent() -> vm._update() -> patch() -> createEle() -> createComponent()

创建组件VNode

_createElement实际执行VNode创建的函数,由于传入tag是非保留标签,因此判定为自定义组件通过createComponent去创建

createComponent创建VNode,保存了上一步处理得到的组件构造函数,props events等

创建组件实例

根组件执行更新函数时,会递归创建子元素和子组件,入口createElm

createEle首次执行_update()时,patch()会通过createEle()创建根元素,子元素创建研究从这里开始

if (isDef(i = i.hook) && isDef(i = i.init)) {
 i(vnode, false /* hydrating */) }
if (isDef(vnode.componentInstance)) {
 // 元素引⽤指定vnode.elm,元素属性创建等
 initComponent(vnode, insertedVnodeQueue)
 // 插⼊到⽗元素
 insert(parentElm, vnode.elm, refElm)
 if (isTrue(isReactivated)) {
 reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
 }
 return true
}

手写Vue2

vue2.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>kvue2</title>
</head>
<body>
<div id="app">
  <h1>{{counter}}</h1>
</div>
<script src="./kvue2.js"></script>
<script>
  const app = new Kvue({
    el: '#app',
    data: {
      counter: 1
    },
    render(h) {
      // 真实dom 全量更新
      // const h1 = document.createElement('h1');
      // h1.innerText = this.counter + '';
      // return h1;

      // vnode方案
      // return h('h1', {}, this.counter + '');
      return h('h1', {}, [
        h('p', {}, this.counter + ''),
        h('p', {}, this.counter * 2 + ''),
        h('p', {}, this.counter * 3 + ''),
      ]);
    }
  })
  setInterval(() => {
    app.counter++;
  }, 1000);
</script>
</body>
</html>

vue2.js

function defineReactive(obj, key, val) {
  observe(val);
  // 创建一个dep实例
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key);
      // 依赖收集
      Dep.target && dep.addDep(Dep.target);
      return val;
    },
    set(v) {
      if (v !== val) {
        val = v;
        observe(v);
        console.log('set', key, val);
        // update();
        dep.notify();
      }
    },

  })
}

function observe(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}

function proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(val) {
        vm.$data[key] = val;
      }
    })
  })
}

class Kvue {
  constructor(options) {
    // 1.保存选项
    this.$options = options;
    this.$data = options.data;
    // 2.对data选项做响应式处理
    observe(this.$data);
    // 2.5代理
    proxy(this);
    // 3.挂载
    if (options.el) {
      this.$mount(options.el);
    }
  }

  // 添加$mount
  $mount(el) {
    this.$el = document.querySelector(el);
    // 1.声明updateComponent
    const updateComponent = () => {
      // 真实dom方案
      // // 渲染获取视图结构
      // const nEl = this.$options.render.call(this);
      // // 结果追加
      // const parent = this.$el.parentElement;
      // parent.insertBefore(nEl, this.$el.nextSibling);
      // parent.removeChild(this.$el);
      // this.$el = nEl;

      // vnode方案
      const vnode = this.$options.render.call(this, this.$createElement);
      this._update(vnode);
    }
    // 2.new Watcher
    new Watcher(this, updateComponent);
  }

  // 返回vnode
  $createElement(tag, props, children) {
    return {
      tag,
      props,
      children
    }
  }

  // 接受vnode,返回dom
  _update(vnode) {
    const prevNode = this._vnode;
    if (!prevNode) {
      // init
      this.__patch__(this.$el, vnode);
    } else {
      // update:diff
      this.__patch__(prevNode, vnode);
    }
    this._vnode = vnode;
  }

  __patch__(oldVnode, vnode) {
    if (oldVnode.nodeType) {
      // init
      const parent = oldVnode.parentElement;
      const refElm = oldVnode.nextSibling;
      // 递归创建dom结构
      const el = this.createEle(vnode);
      parent.insertBefore(el, refElm);
      parent.removeChild(oldVnode);
    } else {
      // update
      const el = vnode.el = oldVnode.el;
      // diff
      // sameVnode
      if (oldVnode.tag === vnode.tag) {
        // todo: props
        // children
        const oldCh = oldVnode.children;
        const newCh = vnode.children;
        if (typeof newCh === 'string') {
          if (typeof oldCh === 'string') {
            // text update
            if (newCh !== oldCh) {
              el.textContent = newCh;
            }
          } else {
            el.textContent = newCh;
          }
        } else {
          if (typeof oldCh !== 'string') {
            // updateChildren
            this.updateChildren(el, oldCh, newCh);
          } else {
            // replace text with elements
            // 先清空
            el.textContent = '';
            // 再转换children
            // 再追加
            // todo:。。。
          }
        }
      } else {
        // replace
      }
    }
  }

  // 递归创建dom树
  createEle(vnode) {
    const el = document.createElement(vnode.tag);
    // todo: props
    // children
    if (vnode.children) {
      if (typeof vnode.children === 'string') {
        // text
        el.textContent = vnode.children;
      } else {
        // array children
        vnode.children.forEach(n => {
          const child = this.createEle(n);
          el.appendChild(child);
        })
      }
    }

    // 保存真实dom用于更新
    vnode.el = el;

    return el;
  }

  // 不优化,不考虑key
  updateChildren(parentElm, oldCh, newCh) {
    // 这里暂且直接patch对应索引1的两个节点
    const len = Math.min(oldCh.length, newCh.length);
    for (let i = 0; i < len; i++) {
      this.__patch__(oldCh[i], newCh[i]);
      // newCh若是更长的哪个,说明有新增
      if (newCh.length > oldCh.length) {
        newCh.slice(len).forEach((child) => {
          const el = this.createEle(child);
          parentElm.appendChild(el);
        });
      } else if (newCh.length < oldCh.length) {
        // oldCh若是更长的那个,说明有删减
        oldCh.slice(len).forEach((child) => {
          parentElm.removeChild(child.el);
        })
      }
    }
  }
}

// 负责具体更新任务的watcher
class Watcher {
  constructor(vm, updateFn) {
    this.$vm = vm;
    this.getter = updateFn;
    this.get();
  }

  get() {
    // 触发依赖收集
    Dep.target = this;
    this.getter.call(this.vm);
    Dep.target = null;
  }

  update() {
    this.get();
  }
}

// 和data中响应式的key之间是一一对应关系
class Dep {
  constructor() {
    this.deps = new Set();
  }

  addDep(dep) {
    this.deps.add(dep);
  }

  notify() {
    this.deps.forEach(dep => dep.update());
  }
}
上次编辑于:
贡献者: haozg