《Vue.js 设计与实现》读书笔记(1-3章)

第 1 章、权衡的艺术

命令式 or 声明式

命令式:关注过程
声明式:关注结果

声明式直接声明想要的结果,框架帮用户封装好命令式的代码,所以在封装的过程中要做一些其他的事情来(生成要做的事情/找出差异)生成命令式的代码,优势是可维护性强,但是性能:命令式 ≥ 声明式

框架设计要做的就是,保持可维护性,同时让性能损失更少。

虚拟 DOM

性能:innerHTML < 虚拟DOM < 原生JavaScript
心智负担/可维护性:虚拟DOM < innerHTML < 原生JavaScript

运行时和编译时

三种选择:纯运行时、运行时+编译时、纯编译时

手写vdom太麻烦,所以通过模板 or JSX 完成书写,但是这些还需要经历一次编译

Vue:运行时+编译时
Svelte: 纯编译时

第 2 章、设计框架要注意什么?

提升开发体验

用户未按要求使用时的错误提示,console 自定义 formatter 直观显示数据

控制框架代码的体积

开发环境提供良好提示,不增加生产环境体积。(通过 __DEV__ 变量判断)

Tree-Shaking

使用 ESM
如果一个函数有副作用,将不会被 Tree-Shaking 删除,通过 /*#__PURE__*/ 在打包工作( rollup/webpack )中声明没有副作用。

输出产物

IIFE:rollup format: 'iife'
ESM: format: 'esm'
CommonJS: format: 'cjs'

特性开关

用户关闭的特性,利用 Tree-Shaking 不打包到最终资源里。比如在 Vue3 中,对于 options API

处理错误

执行用户提供的函数时,做统一的错误处理(try...catch),并提供给用户错误接口来处理。(可以错误上报等。)

TypeScript类型支持

第 3 章、Vue3 的设计思路

声明式的描述 UI

模板描述,或者虚拟DOM描述

渲染器

渲染器:把虚拟DOM变成真实DOM并渲染到浏览器页面。一个简单的渲染器实现:

点击查看代码
const vnode = {
  tag: 'div',
  props: {
    onClick: () => alert('hello'),
  },
  children: 'click me',
};

function renderer(vnode, container) {
  const el = document.createElement(vnode.tag);
  for (const key in vnode.props) {
    if (/^on/.test(key)) {
      // on 开头是事件
      el.addEventListener(
        key.substr(2).toLowerCase(), // 事件名 onClick -> click
        vnode.props[key] // 事件处理函数
      );
    }
  }
  // 处理 children
  if (typeof vnode.children === 'string') {
    el.appendChild(document.createTextNode(vnode.children));
  } else if (Array.isArray(vnode.children)) {
    vnode.children.forEach((child) => {
      renderer(child, el);
    });
  }

  container.appendChild(el);
}

renderer(vnode, document.body);

组件的本质

组件就是一组 DOM 元素的封装。上面的渲染器兼容组件(包括函数写法和对象写法):

点击查看代码
const MyComponent = function() {
  return {
    tag: 'div',
    props: {
      onClick: () => alert('hello'),
    },
    children: 'click me - MyComponent',
  }
}

const MyComponent1 = {
  render() {
    return {
      tag: 'div',
      props: {
        onClick: () => alert('hello'),
      },
      children: 'click me - MyComponent1',
    }
  }
}

const vnode = {
  tag: MyComponent
};

function renderer(vnode, container) {
  if (typeof vnode.tag === 'string') {
    // 标签元素
    mountElement(vnode, container);
  } else if (typeof vnode.tag === 'function') {
    // 函数描述组件
    mountFunctionComponent(vnode, container);
  } else if (typeof vnode.tag === 'object') {
    // 对象描述组件
    mountObjectComponent(vnode, container)
  }
}

function mountElement(vnode, container) {
  const el = document.createElement(vnode.tag);
  for (const key in vnode.props) {
    if (/^on/.test(key)) {
      // on 开头是事件
      el.addEventListener(
        key.substr(2).toLowerCase(), // 事件名 onClick -> click
        vnode.props[key] // 事件处理函数
      );
    }
  }
  // 处理 children
  if (typeof vnode.children === 'string') {
    el.appendChild(document.createTextNode(vnode.children));
  } else if (Array.isArray(vnode.children)) {
    vnode.children.forEach((child) => {
      renderer(child, el);
    });
  }

  container.appendChild(el);
}

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

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

renderer(vnode, document.body);

模板的工作原理

编译器:模板编译为渲染函数

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

编译为

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

Vue 是各模块组成的有机体

模板 --[编译器]--> 渲染函数 --[渲染器]--> 真实DOM

如果在编译器中增加优化,比如静态节点的判断,就可以在渲染器减少一些工作。

posted @ 2023-01-12 20:31  我不吃饼干呀  阅读(112)  评论(0编辑  收藏  举报