写点什么

我从 Vuejs 中学到了什么(一)

作者:真嗣
  • 2022 年 7 月 31 日
  • 本文字数:2899 字

    阅读完需:约 10 分钟

我从Vuejs中学到了什么(一)

Vuejs 的框架设计

设计一个框架有哪些注意事项

框架设计是一个比较复杂的过程,并不是说把代码写完就算完成了,框架本身还需要提供给开发者更好的开发体验。下面简单总结一下除了框架本身的代码之外,还有哪些注意事项:

  1. 提升用户开发体验:比如开发者代码有误,框架层在浏览器展示的错误提示

  2. 控制框架整体的代码体积,比如代码中的__DEV__常量控制

  3. 框架本身要做好 tree shaking

  4. 控制好输出产物:ESM、CJS、IIFE 等

  5. 特性开关及,比如哪些功能开发者用不到,可以在配置阶阶段选择关闭,比如说__FEATURE_OPTIONS_API__,可以通过 rollup 预定义的常量来看是否来开启 optional api 的选项等

  6. 错误处理,比如 vuejs 自带的 callWithErrorHandling函数

  7. 良好的 TypeScript 支持

  8. ...

关于第 3、4、5 点来说,vuejs 通过 rollup 就可以很好的做到了这些。

Vuejs 是什么样的框架

作为 vue 的使用者,我们在学习框架的时候要从全局的角度对框架的设计拥有清晰的认知,否则会很容易被细节困住,看不清全貌。

编程范式

视图层的框架一般分为命令式和声明式,最早的 jQuery 是命令式框架的代表,命令式框架的特点就是关注过程:

// jquery实现$("#app") // 获取div    .text("hello jquery") // 给div设置文本内容    .on("click", () => {console.log("hello jquery")}) // 给div绑定点击事件  // 原生实现    const app = document.getElementById("app") app.innerText = "hellol jquery" app.addEventListener("click", () => {console.log("hello jquery")})
复制代码


在我们编写 js 代码的大部分时间,我们都是来编写命令式的代码。

声明式框架(范式)是不关注过程但关注结果的。以 vuejs 为例:

<div @click="() => {console.log('clicked')}">hello vuejs</div>
复制代码

针对上面的代码来说,其实是整个 vuejs 的底层帮助开发者封装了过程,也就是说,vuejs 底层一定是命令式的,暴露给开发者的是声明式的。

需要做个简单的总结来说,命令式代码的性能是优于声明式代码的。

虚拟 DOM

我们在讨论虚拟 DOM 的时候总是在讨论虚拟 DOM 的性能好坏,总会觉得用了虚拟 DOM 后,框架就会变快。但实际上采用了虚拟 DOM 更新技术的性能理论上是不会比你直接写原生 JS 的性能要快的,以数据说话:dom-benchmark 可以来这个网站测一测各大框架以及 naive vanilla js 的性能比较。

生成一个 dom 节点,我们从心智负担、维护性和性能方面来做一下比较:



// innerHTML 直接操作domconst html = `    <div><span>hello vuejs</span></div>`div.innerHTML = html
复制代码


// 虚拟DOMconst html = {    tag: 'div',    children: [        tag: 'span',        children: 'hello vuejs'    ]}
// vue的声明式UI描述<template> <div><span>hello vuejs</span></div></template>
复制代码


// naive vanalla jsconst div = document.createElement('div')const span = document.createElement('span')div.appendChildren(span.appendChildren('hello vuejs'))
复制代码
编译时 vs 运行时

设计框架的时候一般会采用三种选择:纯运行时、纯编译时、运行时+编译时。具体的框架实现,要根据目标框架的特征、期望等做出适当的决策。


以纯运行时

const obj = {    tag: 'div',    children: [        {tag: 'span', children: 'hello vuejs'}    ]}const render = (obj, root) => {    const el = document.createElement(obj.tag) {        if (typeof obj.children === 'string') {            const text = document.createTextNode(obj.children)            el.append(text)        } else {            obj.children.forEach((child) => render(child, el))        }    }    root.appendChildren(el)}
复制代码


直接运行上面的代码就可以看到我们想要的东西。


纯编译时的框架

目前市面上也是有纯编译时的框架,比如 svelte。svelte 是一个,纯编译时+ no virtual dom + truly reactive 的框架,简单来说下原理就是将模版代码编译为命令式的 dom 操作,比如:

// 模版代码<a>{{ msg }}</a>// 编译后的代码function renderMainFragment (root, component, target) {    var a = document.createElement('a')    var text = document.createTextNode( root.msg )    a.appendChild(text)    target.appendChild(a)    return {    update: function (changed, root) {text.data = root.msg},    teardown: function (detach) {        if (detach) a.parentNode.removeChild( a )        }      }    }
复制代码

由于 svelte 没有采用 virtual dom 所以少了 patch 和 diff 操作,也就少了这部分算法的代码,所以 svelte 的打包体积会非常小,同时采用了命令式的直接操作 dom 的方式,这也就显的 svelte 的性能非常好。


编译时+运行时

vuejs 就是编译时+运行时框架的代表了,以上面纯运行的代码为例,我们不可能让开发者每次都手写所谓的 dom object,然后手动 render,这样的框架我想没人愿意去用。那能不能通过某种手段,可以通过把模版代码即 html 标签编译为 dom object,然后再去 render 的方式呢?

// 伪代码// 开发者手写模版代码const html = `    <div>        <span>hello vuejs</span>    </div>`// 实际通过编译后的代码const obj = {    tag: 'div',    children: [        tag: 'span',        children: 'hello vuejs'    ]}
// 整个过程就分为了两部分// 1. 编译const domObj = Complier(html)// 2. 渲染render(domObj, document.body)
复制代码


上面的伪代码中,字符串模版经过编译器的转换,转换为 dom object, 然后再由渲染函数 render 为真实的 dom 节点。上面的 complier 函数将字符串编译成了一个 dom 对象,那么同样可以编译成命令式的代码,那么其实我们可以不要 render 函数了,直接将 complier 和 render 函数二合一为一个 complier 即可。


以上 vuejs 就是采用了运行时+编译时的策略,是 vuejs 成为了一个即保证了代码的可维护性高,对开发者心智负担小,又拥有了不输 svelte 性能的优秀框架。

vuejs 的本质

上面我们简单的了解了一下编程范式,虚拟 DOM,以及几个别框架的区别,那么最后让我们看看 vuejs 的本质是什么:

  1. 声明式的描述 UI,即 template 模版代码

  2. 拥有编译器,将模版代码编译成可描述 dom 节点的 dom object

  3. 拥有渲染器,通过渲染器来进行虚拟 dom 比较来进行真实 dom 的创建、更新等操作

  4. DOM 元素的封装,即组件的本质

  5. 响应式的数据更新

  6. 其他额外的功能及细节处理

所以对 vuejs 框架的组成来说,看起来就像是以下公式:

vuejs = declarative UI + complier(complie + render) + reactive data
复制代码

再来简单的聊一下上面提到的编译器,看下代码示例:

<template>    <div @click="handler">click me </div></template><script>export default {    data() {/*...*/},    methods: {        handler: () => {/*...*/}    }}</script>//通过编译器最终会被编译为一个对象,而模版代码都会编译成对应的render函数//编译后export default {    data() {/*...*/},    methods: {        handler: () => {/*...*/}    },    render() {        return h('div', {onClick: handler}, 'click me')    }}
复制代码

实际上编译过程比上面要复杂很多,我们以后有机会讲一下。

发布于: 刚刚阅读数: 5
用户头像

真嗣

关注

fly me to the moon 2020.07.07 加入

nil

评论

发布
暂无评论
我从Vuejs中学到了什么(一)_前端_真嗣_InfoQ写作社区