Vue3新特性一篇搞懂(下)

简介: Vue3新特性一篇搞懂

通过Jest深度了解源码


现在准备向原理源码进军了。有个小问题先要处理一下。就是研究一下如何把Vue3的单元测试跑起来。毕竟光读代码不运行是没有灵魂的。歪歪一下中国的球迷是不是就是光看不踢。


Vue3代码是基于Jest进行测试,我们先简单看看什么是jest


Jest简介


Jest 是Facebook的一个专门进行Javascript单元测试的工具,适合JS、NodeJS使用,具有零配置、内置代码覆盖率、强大的Mocks等特点。


总之目前来讲JS界Jest是一套比较成体系的测试工具。为什么这么说呢比如拿以前的测试框架Mocha对比 他只是一个测试框架,如果你需要断言还需要专门的断言库比如assert shoud expect等等 如果需要Mock还需要住啊们的库来支持很不方便。不过Jest基本上可以一次性搞定。


目录文件名约定


Jest测试代码和逻辑代码是遵从约定优于配置(convention over configuration)其实这个也是目前编程世界普遍接受的原则。


Jest的测试代码是基于以下约定


  • 测试文件名要以spec结果


  • 测试文件后缀为js,jsx,ts,tsx


  • 测试文件需要放在tests/unit/目录下或者是/tests/目录下


只要满足这三个要求的测试文件,使用运行jest时就会自动执行。


其实这个规定类似于Maven对于测试代码和逻辑代码的约定只是test目录换成了__tests__


下面我们具体看一下Vue3源码的目录结构



其实逻辑代码和测试代码对应放置还是很方便的 我们再看看另外一个reactive这个包



运行全量测试


package.json文件中已经配置好了jest



npm run test



覆盖率


我们增加一个参数把覆盖率跑出来


npx jest --coverage



实际上跑覆盖率的时候是有错的 我们先不去管他 我们先解析一下这个报告怎么看,如果大家学过软件工程会知道一般从理论上讲覆盖率包括


  • 语句覆盖


  • 节点覆盖


  • 路径覆盖


  • 条件组合覆盖


但是一般来讲不同框架理解不一样 在Jest这里大概是这样分解的


  • %stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?


  • %Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?


  • %Funcs函数覆盖率(function coverage):是不是每个函数都调用了?


  • %Lines行覆盖率(line coverage):是不是每一行都执行了?


单独运行一个测试


比如我们看看vue的index这个测试



有两种方法进行单独测试


// 全局安装
npm i jest -g
jest index
// 或者更更简便一点
npx jest index



index.spec.ts


import { createApp } from '../src'
it('should support on-the-fly template compilation', () => {
  const container = document.createElement('div')
  const App = {
    template: `{{ count }}`,
    data() {
      return {
        count: 0
      }
    }
  }
  createApp().mount(App, container)
  // 断言 
  expect(container.innerHTML).toBe(`0`)
})


声明中说为了确认模板编译可以生效,这个就是一个简单的数据绑定 最后 断言也是看了一下 count是否为 0 这个例子其实除了断言部分其实直接拷贝到第一次讲的那个html文件里面是可以运行的。


响应式Reactive的单元测试



看一下每个包对应的测试代码都放在__tests__文件件中


npx jest reactive --coverage



好了后面我们就可以开始向源码进军了


代码结构



源码位置是在package文件件内,实际上源码主要分为两部分,编译器和运行时环境。


  • 编译器


  • compiler-core 核心编译逻辑


  • 基本类型解析
  • AST


  • compiler-dom 针对浏览器的编译逻辑


  • v-html
  • v-text
  • v-model
  • v-clock


  • 运行时环境


  • runtime-core 运行时核心


  • inject
  • 生命周期
  • watch
  • directive
  • component


  • runtime-dom 运行时针对浏览器的逻辑


  • class
  • style


  • runtime-test 测试环境仿真


主要为了解决单元测试问题的逻辑 在浏览器外完成测试比较方便


  • reactivity 响应式逻辑


  • template-explorer 模板解析器 可以这样运行


yarn dev template-explorer




然后打开index.html



  • vue 代码入口


整合编译器和运行时



  • server-renderer 服务器端渲染(TODO)


  • share 公用方法


Vue2和Vue3响应方式对比


Vue2响应式是什么


首先我们说说什么是响应式。通过某种方法可以达到数据变了可以自由定义对应的响应就叫响应式。


具体到我们MVVM中 ViewModel的需要就是数据变了需要视图作出响应。 如果用Jest用例便表示就是这样


it('测试数据改变时 是否被响应', () => {
        const data = reactive({
            name: 'abc',
            age: {
                n: 5
            }
        })
        // Mock一个响应函数
        const fn = jest.fn()
        const result = fn()
        // 设置响应函数
        effect(fn)
        // 改变数据
        data.name = 'efg'
        // 确认fn生效
        expect(fn).toBeCalled()
    })


假定我们需要的是数据data变化时可以触发fn函数也就是作出相应,当然相应一般是触发视图更新当然也可以不是。我们这里面用jest做了一个Mock函数来检测是否作出相应。


最后代码expect(fn).toBeCalled()有效即代表测试通过也就是作出了相应


Vue2的解决方案


下面展示的是vue2的实现方式是通过Object.defineProperty来重新定义getter,setter方法实现的。


let effective
function effect(fun) {
    effective = fun
}
function reactive(data) {
    if (typeof data !== 'object' || data === null) {
        return data
    }
    Object.keys(data).forEach(function (key) {
        let value = data[key]
        Object.defineProperty(data, key, {
            emumerable: false,
            configurable: true,
            get: () => {
                return value
            },
            set: newVal => {
                if (newVal !== value) {
                    effective()
                    value = newVal
                }
            }
        })
    })
    return data
}
module.exports = {
    effect, reactive
}


当然还有两个重要的问题需要处理 第一个就是这样做只能做浅层响应 也就是如果是第二层就不行了。


it('测试多层数据中改变时 是否被响应', () => {
        const data = reactive({
            age: {
                n: 5
            }
        })
        // Mock一个响应函数
        const fn = jest.fn()
        // 设置响应函数
        effect(fn)
        // 改变多层数据
        data.age.n = 1
        // 确认fn生效
        expect(fn).toBeCalled()
    })


比如以下用例 就过不去了 当然解决的办法是有的 递归调用就好了



当然这样也递归也带来了性能上的极大损失 这个大家先记住。


然后是数组问题 数组问题我们可以通过函数劫持的方式解决


const oldArrayPrototype = Array.prototype
const proto = Object.create(oldArrayPrototype);
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
    // 函数劫持
    proto[method] = function(){
        effective()
        oldArrayPrototype[method].call(this,...arguments)
    }
})
// 数组通过数据劫持提供响应式
if(Array.isArray(data)){
    data.__proto__ = proto
}


Vue3


新版的Vue3使用ES6的Proxy方式来解决这个问题。之前遇到的两个问题就简单的多了。首先Proxy是支持数组的也就是数组是不需要做特别的代码的。对于深层监听也不不必要使用递归的方式解决。当get是判断值为对象时将对象做响应式处理返回就可以了。大家想想这个并不不是发生在初始化的时候而是设置值得时候当然性能上得到很大的提升。


function reactive(data) {
    if (typeof data !== 'object' || data === null) {
        return data
    }
    const observed = new Proxy(data, {
        get(target, key, receiver) {
            // Reflect有返回值不报错
            let result = Reflect.get(target, key, receiver)
            // 多层代理
            return typeof result !== 'object' ? result : reactive(result) 
        },
        set(target, key, value, receiver) {
            effective()
            // proxy + reflect
            const ret = Reflect.set(target, key, value, receiver)
            return ret
        },
        deleteProperty(target,key){
            const ret = Reflect.deleteProperty(target,key)
            return ret
        }
    })
    return observed
}


当然目前还是优缺点的缺点,比如兼容性问题目前IE11就不支持Proxy。不过相信ES6的全面支持已经是不可逆转的趋势了,这都不是事。


为了对比理解Vue2、3的响应式实现的不同我把两种实现都写了一下,并且配上了jest测试。大家可以参考一下 github.com/su37josephx…


// clone代码
yarn
npx jest reactivity-demo




自定义渲染器


这个自定义渲染器和React的Render很类似。 可以根据需要定义各种各样的渲染器


  • App端 Vue-Native


  • 小程序端 后续应该会有很多这样的库



具体内容近期更新


新工具vite


我们知道ES6语法中 import在浏览器中完全可用。可以用于加载后端资源只不过这个特性一直被我们忽略。可能是由于webpack搞得太好了。我们一直忽略了他的存在。 Vite正是利用这个特性,只不过又更进一步,提供了对于vue文件的支持。不过可能是有点前卫。


  • 简易Http服务器


  • 无需webpack


  • Vue文件直接渲染


  • 热更新


相关文章
|
6天前
|
缓存 监控 UED
升级 Vue3 时,如何减少打包体积的增加?
升级 Vue3 时,如何减少打包体积的增加?
85 59
|
5天前
|
JavaScript
在vue3中(vite)引入unocss,安装配置unocss
在vue3中(vite)引入unocss,安装配置unocss
|
6天前
|
缓存 JavaScript 前端开发
「offer来了」从基础到进阶原理,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系
该文章全面覆盖了Vue.js从基础知识到进阶原理的48个核心知识点,包括Vue CLI项目结构、组件生命周期、响应式原理、Composition API的使用等内容,并针对Vue 2与Vue 3的不同特性进行了详细对比与讲解。
「offer来了」从基础到进阶原理,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系
|
6天前
|
API UED
如何实现Vue2项目升级Vue3?
如何实现Vue2项目升级Vue3?
14 1
|
8天前
|
JavaScript 前端开发 API
vue3的传送门teleport究竟有多神奇?suspense发起异步请求有多简约?
该文章介绍了Vue3中新特性Teleport和Suspense的使用方法,演示了如何使用Teleport进行DOM节点的非父子关系传送,以及Suspense在处理异步组件加载时的优雅展示和错误处理技巧。
|
8天前
|
JavaScript
particles 粒子背景插件在vue3中的使用
本文介绍了如何在Vue 3项目中使用`particles.vue3`库来创建粒子背景特效。文章提供了粒子背景插件的概述、安装步骤、配置参数说明,并展示了粒子特效的实现效果。同时,提供了在main.js中进行全局配置、在Vue组件中使用粒子背景组件的示例代码,以及完整代码的下载链接。
|
6天前
|
API UED
升级 Vue3 后,项目的打包体积会有什么变化?
升级 Vue3 后,项目的打包体积会有什么变化?
92 57
|
7天前
|
JavaScript API
再不学vue3就没有人要你了!速来围观vue3
这篇技术文章介绍了作者从最初对学习 Vue3 抵触到最终决定投入学习的心路历程,展示了 Vue3 相较于 Vue2 的诸多改进和新特性,如更优的性能、更小的代码体积及更佳的 TypeScript 支持。文章重点阐述了 Vue3 中 createApp 的使用变化、emits 机制、多事件处理、Fragment 的引入等重要功能升级。此外,还深入探讨了 Composition API 和 Options API 的区别,强调 Composition API 在代码组织和逻辑复用方面的优势,并给出了在不同项目规模中选择合适 API 的建议。
20 0
|
8天前
|
JavaScript 前端开发 UED
组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList
该文章详细介绍了如何使用Vue3结合TypeScript来开发全局Header组件和列表数据渲染组件ColumnList,并提供了从设计到实现的完整步骤指导。
|
8天前
|
JavaScript API
模块化妙用!用vue3实现一个鼠标追踪器和异步加载组件
该文章展示了如何使用Vue3的Composition API实现鼠标追踪器功能,并介绍了创建异步加载组件的方法,利用TS泛型增强了组件的灵活性与可维护性。
下一篇
无影云桌面