# 第 3 章 Vue.js 3 的设计思路

# 1. 声明式地描述 UI

用模版描述 UI:

<h1 @click="handler">
  <span></span>
</h1>

用 JS 对象(虚拟DOM)描述 UI:

const title = {
  tag: 'h1',
  props: {
    onClick: handler,
  },
  children: [
    { tag: 'span' }
  ]
};

通过渲染函数描述:

import { h } from 'vue';

export default {
  render() {
    return h('h1', { onClick: handler })
  }
}

# 2. 初识渲染器

虚拟 DOM:

  • 用 JS 对象来描述真实的 DOM 结构

渲染器:

  • 将 虚拟 DOM 渲染为 真实 DOM

自定义渲染器:

function renderer(vnode, container) {
  const {
    tag,
    props,
    children,
  } = vnode;

  const el = document.createElement(tag);

  for (const prop in props) {
    if (/^on/.test(prop)) {
      const eventName = prop.substr(2).toLowerCase();
      const handler = props[prop];

      el.addEventListener(eventName, handler);
    }
  }

  if (typeof children === 'string') {
    const text = document.createTextNode(children);
    el.appendChild(text);
  } else if (Array.isArray(children)) {
    children.forEach((child) => renderer(child, el));
  }

  container.appendChild(el);
}

// 使用
renderer({
  tag: 'div',
  props: {
    onClick: () => alert('hello'),
  },
  children: [
    { tag: 'span', children: '点我' }
  ]
}, document.body);

# 3. 组件的本质

说明:

  • 组件就是一组 DOM 元素的封装,也是虚拟 DOM

组件:

// 函数形式
const MyComponent = function() {
  return {
    tag: 'div',
    props: {
      onClick: () => alert('hello'),
    },
    children: [
      { tag: 'span', children: '点我' }
    ]
  };
};

// 对象形式
const MyComponent = {
  render: function() {
    return {
      tag: 'div',
      props: {
        onClick: () => alert('hello'),
      },
      children: [
        { tag: 'span', children: '点我' }
      ]
    };
  }
};

渲染器:

function renderer(vnode, container) {
  const { tag } = vnode;

  if (typeof tag === 'string') {
    mountElement(vnode, container);
  } else if (typeof tag === 'function') {
    mountComponent(vnode, container);
  }
}

function mountElement(vnode, container) {
    const {
    tag,
    props,
    children,
  } = vnode;

  const el = document.createElement(tag);

  for (const prop in props) {
    if (/^on/.test(prop)) {
      const eventName = prop.substr(2).toLowerCase();
      const handler = props[prop];

      el.addEventListener(eventName, handler);
    }
  }

  if (typeof children === 'string') {
    const text = document.createTextNode(children);
    el.appendChild(text);
  } else if (Array.isArray(children)) {
    children.forEach((child) => renderer(child, el));
  }

  container.appendChild(el);
}

function mountComponent(vnode, container) {
  const subtree = vnode.tag(); // 获取 vnode
  renderer(subtree, container);
}

使用:

const vnode = {
  tag: MyComponent,
};

renderer(vnode,  document.body)

# 4. 模板的工作原理

编译器:

  • 将模板编译为渲染函数

示例:

  • 模板:

    <div @click="handler">
      click me
    </div>
    
  • 渲染函数:

    render() {
      return h('div', { onClick: handler }, 'click me');
    }
    

对于 .vue 文件,编译器会把 <template> 里的内容编译为 render() 函数,然后添加到组件对象上去

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

Vue.js 各个模块之间相互关联、制约

编译器与渲染器:

  • 编译器在编译阶段标记动态内容
本章目录