Vue 3.0 的变化

  • 源码组织方式的变化
    • 采用 Typescript重写
    • 采用Monoorpo管理项目结构
  • composition API
    • Options API包含一个描述组件的选项(data、methods、props等)的对象
    • Options API 开发复杂组件,同一个功能逻辑代码会被拆分到不同选项
    • Vue 3.0 新增一组基于函数的 API,可以更灵活的组织组件的逻辑
  • 性能提升
    • 响应式系统升级
      • Vue.js 2.0 x 中响应式系统的核心 defineProperty
      • Vue.js 3.0 x 中使用 Proxy 对象重写响应式系统
    • 编译优化
      • Vue.js 2.0 x 中通过标记静态节点,优化 diff 的过程
      • Vue.js 3.0 x 中标记和提升所有的静态根节点,diff 的时候只需要对比动态内容
        • Fragments(升级 Vuter 插件)
        • 静态提升
        • Patch flag
        • 缓存事件处理函数
    • 源码体积优化
      • Vue.js 3.0 x 中移除了一些不常用的 API
        • 例如: inline-template filter 等
      • Tree-shaking
  • Vite

    • 在开发模式下不需要打包就可以直接运行
    • 开发模式下必须对项目打包才能运行
    • 特点
      • 快速冷启动
      • 按需编译
      • 模块热更新

        ES Module

  • 现代浏览器都支持 ES Module (IE不支持)

  • 通过下面的方式加载模块

    1. <script type="module" src="..."></script>
  • 支持模块的script默认延时加载

    • 类似 script 标签设置 defer
    • 在文档解析完成后,触发 DOMContentLoaded 事件前执行

      Vite as Vue-CLI

  • Vite 在生产环境使用 Rollup 打包

    • 基于 ES Module 的方式打包
  • Vue-CLI使用Webpack打包

    Vite 创建项目

  • Vite创建项目

    1. npm init vite-app <project-name>
    2. cd <project-name>
    3. npm install
    4. npm run dev
  • 基于模板创建项目

    1. npm init vite-app --template react
    2. npm init vite-app --template preact

    Composition API

    生命周期钩子函数

    image.png

setup()是在beforeCreate和created之间执行的,不需要这两个生命周期函数

reactive/toRefs/ref

  • reactive 将一个对象变成响应式对象
  • toRefs 将 reactive 返回的响应式对象所有属性添加为响应式
  • ref将数据添加为响应式(无论时对象或者值)

    computed

  • 需要传入一个函数 ```javascript import { createApp, reactive, computed } from ‘./node_modules/vue/dist/vue.esm-browser.js’ const data = [ { text: ‘看书’, completed: false }, { text: ‘敲代码’, completed: false }, { text: ‘约会’, completed: true } ]

createApp({ setup () { const todos = reactive(data)

  1. const activeCount = computed(() => {
  2. return todos.filter(item => !item.completed).length
  3. })
  4. return {
  5. activeCount,
  6. push: () => {
  7. todos.push({
  8. text: '开会',
  9. completed: false
  10. })
  11. }
  12. }

} }).mount(‘#app’)

  1. <a name="1XGxi"></a>
  2. ### Watch
  3. - Watch 的三个参数
  4. - 第一个参数:要监听的数据(ref/reactive 的返回值)
  5. - 第二个参数:监听到数据变化的执行函数,这个函数有两个参数分别是新值和旧值
  6. - 第三个参数:选项对象,deep 和 immediate
  7. - Watch 的返回值
  8. - 取消监听函数
  9. <a name="rjFCt"></a>
  10. ### WatchEffect
  11. - 是 Watch 函数的简化版本,也用来监听数据的变化
  12. - 接收一个函数作为参数,监听函数内响应式数据的变化
  13. <a name="6Upsw"></a>
  14. ## 响应式原理
  15. <a name="EnlV9"></a>
  16. ### reactive
  17. - 接收一个参数,判断这个参数是否为对象
  18. - 创建拦截器对象 handler,设置 get、set、deleteProperty
  19. - 返回 Proxy 对象
  20. ```javascript
  21. const isObject = val => val !== null && typeof val === 'object'
  22. const convert = target => isObject(target) ? reactive(target) : target
  23. const hasOwnProperty = Object.prototype.hasOwnProperty
  24. const hasOwn = (target, key) => hasOwnProperty.call(target, key)
  25. export function reactive (target) {
  26. if (!isObject(target)) return target
  27. const handler = {
  28. get (target, key, receiver) {
  29. // 收集依赖
  30. track(target, key)
  31. const result = Reflect.get(target, key, receiver)
  32. return convert(result)
  33. },
  34. set (target, key, value, receiver) {
  35. const oldValue = Reflect.get(target, key, receiver)
  36. let result = true
  37. if (oldValue !== value) {
  38. result = Reflect.set(target, key, value, receiver)
  39. // 触发更新
  40. trigger(target, key)
  41. }
  42. return result
  43. },
  44. deleteProperty (target, key) {
  45. const hadKey = hasOwn(target, key)
  46. const result = Reflect.deleteProperty(target, key)
  47. if (hadKey && result) {
  48. // 触发更新
  49. trigger(target, key)
  50. }
  51. return result
  52. }
  53. }
  54. return new Proxy(target, handler)
  55. }

收集依赖

image.png

effect && track

  1. let activeEffect = null
  2. export function effect (callback) {
  3. activeEffect = callback
  4. callback() // 访问响应式对象属性,去收集依赖
  5. activeEffect = null
  6. }
  7. let targetMap = new WeakMap()
  8. export function track (target, key) {
  9. if (!activeEffect) return
  10. let depsMap = targetMap.get(target)
  11. if (!depsMap) {
  12. targetMap.set(target, (depsMap = new Map()))
  13. }
  14. let dep = depsMap.get(key)
  15. if (!dep) {
  16. depsMap.set(key, (dep = new Set()))
  17. }
  18. dep.add(activeEffect)
  19. }

trigger

  1. export function trigger (target, key) {
  2. const depsMap = targetMap.get(target)
  3. if (!depsMap) return
  4. const dep = depsMap.get(key)
  5. if (dep) {
  6. dep.forEach(effect => {
  7. effect()
  8. })
  9. }
  10. }

ref

  1. export function ref (raw) {
  2. // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
  3. if (isObject(raw) && raw.__v_isRef) {
  4. return
  5. }
  6. let value = convert(raw)
  7. const r = {
  8. __v_isRef: true,
  9. get value () {
  10. track(r, 'value')
  11. return value
  12. },
  13. set value (newValue) {
  14. if (newValue !== value) {
  15. raw = newValue
  16. value = convert(raw)
  17. trigger(r, 'value')
  18. }
  19. }
  20. }
  21. return r
  22. }

reactive vs ref

  • ref 可以将基本数据类型转换为响应式对象
  • ref 返回对象,重新赋值成对象也是响应式的
  • reactive 返回对象,重新赋值丢失响应式
  • reactive 返回的对象不可以解构

    toRefs

    ```javascript export function toRefs (proxy) { const ret = proxy instanceof Array ? new Array(proxy.length) : {}

    for (const key in proxy) { ret[key] = toProxyRef(proxy, key) }

    return ret }

function toProxyRef (proxy, key) { const r = { __v_isRef: true, get value () { return proxy[key] }, set value (newValue) { proxy[key] = newValue } } return r }

  1. <a name="Z4Aos"></a>
  2. ### computed
  3. ```javascript
  4. export function computed (getter) {
  5. const result = ref()
  6. effect(() => (result.value = getter()))
  7. return result
  8. }

Vite

概念

  • Vite是一个面向现代化浏览器的一个更轻、更快的Web应用开发工具
  • 它基于ECMAScript标准原生模块系统(ES Modules)实现

    项目依赖

  • Vite

  • @vue/compiler-scf

    Vite实现原理

    静态Web服务器(Koa) ```javascript

    !/usr/bin/env node

    const path = require(‘path’) const { Readable } = require(‘stream’) const Koa = require(‘koa’) const send = require(‘koa-send’) const compilerSFC = require(‘@vue/compiler-sfc’)

const app = new Koa()

const streamToString = stream => new Promise((resolve, reject) => { const chunks = [] stream.on(‘data’, chunk => chunks.push(chunk)) stream.on(‘end’, () => resolve(Buffer.concat(chunks).toString(‘utf-8’))) stream.on(‘error’, reject) })

const stringToStream = text => { const stream = new Readable() stream.push(text) stream.push(null) return stream }

// 3. 加载第三方模块 app.use(async (ctx, next) => { // ctx.path —> /@modules/vue if (ctx.path.startsWith(‘/@modules/‘)) { const moduleName = ctx.path.substr(10) const pkgPath = path.join(process.cwd(), ‘node_modules’, moduleName, ‘package.json’) const pkg = require(pkgPath) ctx.path = path.join(‘/node_modules’, moduleName, pkg.module) } await next() })

// 1. 静态文件服务器 app.use(async (ctx, next) => { await send(ctx, ctx.path, { root: process.cwd(), index: ‘index.html’ }) await next() })

// 4. 处理单文件组件 app.use(async (ctx, next) => { if (ctx.path.endsWith(‘.vue’)) { const contents = await streamToString(ctx.body) const { descriptor } = compilerSFC.parse(contents) let code if (!ctx.query.type) { code = descriptor.script.content // console.log(code) code = code.replace(/export\s+default\s+/g, ‘const script = ‘) code += ` import { render as render } from “${ctx.path}?type=template” script.render = render export default __script ` } else if (ctx.query.type === ‘template’) { const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content }) code = templateRender.code } ctx.type = ‘application/javascript’ ctx.body = stringToStream(code) } await next() })

// 2. 修改第三方模块的路径 app.use(async (ctx, next) => { if (ctx.type === ‘application/javascript’) { const contents = await streamToString(ctx.body) // import vue from ‘vue’ // import App from ‘./App.vue’ ctx.body = contents .replace(/(from\s+[‘“])(?![.\/])/g, ‘$1/@modules/‘) .replace(/process.env.NODE_ENV/g, ‘“development”‘) } })

app.listen(3000) console.log(‘Server running @ http://localhost:3000‘)

```