Skip to content

3. Vue.js 3 的设计思路

3.1  声明式地描述 UI

在 Vue.js 中,哪怕是事件,都有与之对应的描述方式。用户不需要手写任何命令式代码,这就是所谓的声明式地描述 UI。

js
// 模版写法
<h1 @click="handler"><span></span></h1>


// 渲染函数写法
import { h } from 'vue'

export default {
  render() {
    return h('h1', { onClick: handler },{ h('span')}) //  h函数的返回值就是虚拟 DOM
  }
}

// 虚拟DOM
const title = {
  // 标签名称
  tag: 'h1',
  // 标签属性
  props: {
    onClick: handler
  },
  // 子节点
  children: [
    { tag: 'span' }
  ]
}

虚拟 DOM 要比模板更加灵活,但模板要比虚拟 DOM 更加直观

3.2  初识渲染器

渲染器的作用就是把虚拟 DOM 渲染为真实 DOM

js
function mountElement(vnode, container) {
  // 使用 vnode.tag 作为标签名称创建 DOM 元素
  const el = document.createElement(vnode.tag);
  // 遍历 vnode.props,将属性、事件添加到 DOM 元素
  for (const key in vnode.props) {
    if (/^on/.test(key)) {
      // 如果 key 以字符串 on 开头,说明它是事件
      el.addEventListener(
        key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
        vnode.props[key] // 事件处理函数
      );
    }
  }

  // 处理 children
  if (typeof vnode.children === "string") {
    // 如果 children 是字符串,说明它是元素的文本子节点
    el.appendChild(document.createTextNode(vnode.children));
  } else if (Array.isArray(vnode.children)) {
    // 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
    vnode.children.forEach((child) => renderer(child, el));
  }

  // 将元素添加到挂载点下
  container.appendChild(el);
}

3.3  组件的本质

  • 组件就是一组 DOM 元素的封装
  • 组件的返回值也是虚拟 DOM,它代表组件要渲染的内容
  • 虚拟 DOM 其实就是用来描述真实 DOM 的普通 JavaScript 对象
js
// 创建组件
const MyComponent = function () {
  return {
    tag: "div",
    props: {
      onClick: () => alert("hello"),
    },
    children: "click me",
  };
};

// 创建虚拟DOM
const vnode = {
  tag: MyComponent,
};

// 渲染组件
function mountComponent(vnode, container) {
  // 调用组件函数,获取组件要渲染的内容(虚拟 DOM)
  const subtree = vnode.tag();
  // 递归地调用 renderer 渲染 subtree
  renderer(subtree, container);
}

// 渲染函数
function renderer(vnode, container) {
  if (typeof vnode.tag === "string") {
    // 说明 vnode 描述的是标签元素
    mountElement(vnode, container);
  } else if (typeof vnode.tag === "function") {
    // 说明 vnode 描述的是组件
    mountComponent(vnode, container);
  }
}

3.4  模板的工作原理

模板内容会通过编译器编译成渲染函数并添加到 <script> 标签块的组件对象上

vue
<template>
  <div @click="handler">click me</div>
</template>

<script>
export default {
  data() {
    /* ... */
  },
  methods: {
    handler: () => {
      /* ... */
    },
  },
};
</script>
vue
// 编译后的文件
<script>
export default {
  data() {/* ... */},
  methods: {
    handler: () => {/* ... */}
  }
  render() {
    return h('h1', { onClick: handler }, 'click me' ) // 虚拟 DOM
  }
}
</script>

无论是使用模板还是直接手写渲染函数,对于一个组件来说,它要渲染的内容最终都是通过渲染函数产生的,然后渲染器再把渲染函数返回的虚拟 DOM 渲染为真实 DOM,这就是模板的工作原理

3.5   Vue.js 是各个模块组成的有机整体

  1. 渲染器的作用之一就是寻找并且只更新变化的内容,当变量 cls 的值发生变化时,渲染器会自行寻找变更点

  2. 编译器能识别出哪些是静态属性,哪些是动态属性,在生成代码的时候附带这些信息

  3. 编译器和渲染器之间是存在信息交流的,它们互相配合使得性能进一步提升,而它们之间交流的媒介就是虚拟 DOM 对象

vue
<div id="foo" :class="cls"></div>
js
render() {
  // 为了效果更加直观,这里没有使用 h 函数,而是直接采用了虚拟 DOM 对象
  // 下面的代码等价于:
  // return h('div', { id: 'foo', class: cls })
  return {
    tag: 'div',
    props: {
      id: 'foo',
      class: cls
    }
    patchFlags: 1 // 假设数字 1 代表 class 是动态的
  }
}

上次更新于: