vue 3.0

13 篇文章 0 订阅

vue3.0 的变化

  • 源码组织方式的变化
    • 采用 Typescript 重写
    • 采用 Monorepo 管理项目结构
  • composition API
    • 包含一个描述组件的选项(data、methods、props等)的对象。
    • Options API 开发复杂组件,同一功能逻辑代码会被拆分到不同选项
    • Vue 3.0 新增了一组 API
    • 一组基于函数的 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 不支持)
  • 通过下面的方式加载模块
    <script type="module" src="..."></script>
    
  • 支持模块的 script 默认延时加载
    • 类似 script 标签设置 defer
    • 在文档解析完成后,触发 DOMContentLoaded 事件前执行

Vite as Vue-CLI

  • Vite 在生产环境使用 Rollup 打包
    • 基于 ES Module 的方式打包
  • Vue-CLI 使用 Webpack 打包

Vite 创建项目

  • vite 创建项目

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

    npm init vite-app --template react
    npm init vite-app --template preact
    

Composition Api

生命周期钩子函数
在这里插入图片描述
reactive / toRefs / ref

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

computed

  • 需要传入一个函数
    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)
    
            const activeCount = computed(() => {
              return todos.filter(item => !item.completed).length
            })
    
            return {
              activeCount,
              push: () => {
                todos.push({
                  text: '开会',
                  completed: false
                })
              }
            }
          }
        }).mount('#app')
    

Watch

  • Watch 的三个参数
    • 第一个参数:要监听的数据(ref / reactive 的返回值)
    • 第二个参数:监听到数据变化的执行函数,这个函数有两个参数分别是新值和旧值
    • 第三个参数:选项对象,deep 和 immediate
  • Watch 的返回值
    • 取消监听的函数

WatchEffect

  • 是 Watch 函数的简化版本,也用来监听数据的变化
  • 接收一个函数作为参数,监听函数内响应式数据的变化

自定义指令

  • Vue 2.x

    Vue.directives('editingFocus', {
      bind(el, binding, vnode, prevVnode) {},
      inserted() {},
      update() {}, // remove
      componentUpdated() {},
      unbind() {}
    })
    
    • 实战
      Vue.directives('editingFocus', (el, binding) => {
        binding.value && el.focus()
      })
      
  • Vue 3.0

    app.directives('editingFocus', {
      beforeMount(el, binding, vnode, prevVnode) {},
      mounted() {},
      beforeUpdate() {}, // new
      update() {},
      beforeUnmounted() {}, // new
      unmounted() {}
    })
    
    • 实战
      app.directives('editingFocus', (el, binding) => {
        binding.value && el.focus()
      })
      

ToDoList 功能

响应式原理

reactive

  • 接收一个参数,判断这个参数是否为对象
  • 创建拦截器对象 handler, 设置 get、set、deleteProperty
  • 返回 Proxy 对象
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)

export function reactive (target) {
  if (!isObject(target)) return target

  const handler = {
    get (target, key, receiver) {
      // 收集依赖
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true
      if (oldValue !== value) {
        result = Reflect.set(target, key, value, receiver)
        // 触发更新
        trigger(target, key)
      }
      return result
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key)
      const result = Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        // 触发更新
        trigger(target, key)
      }
      return result
    }
  }

  return new Proxy(target, handler)
}

收集依赖
在这里插入图片描述
effect && track

let activeEffect = null
export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象属性,去收集依赖
  activeEffect = null
}

let targetMap = new WeakMap()

export function track (target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

trigger

export function trigger (target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => {
      effect()
    })
  }
}

ref

export function ref (raw) {
  // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
  if (isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw)
  const r = {
    __v_isRef: true,
    get value () {
      track(r, 'value')
      return value
    },
    set value (newValue) {
      if (newValue !== value) {
        raw = newValue
        value = convert(raw)
        trigger(r, 'value')
      }
    }
  }
  return r
}

reactive vs ref

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

toRefs

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
}

computed

export function computed (getter) {
  const result = ref()

  effect(() => (result.value = getter()))

  return result
}

Vite

vite 概念

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

项目依赖

  • Vite
  • @vue/compiler-sfc

基础使用

  • vite server
  • vite build

vite server
在这里插入图片描述
vue-cli-service server
在这里插入图片描述
HMR

  • Vite HMR
    • 立即编译当前所修改的文件
  • Webpack HMR
  • 会自动以这个文件为入口重新 build 一次,所有的涉及到的依赖也都会被加载一遍

Build

  • Vite build
    • Rollup
    • Dynamic import
      • Polyfill

打包 or 不打包

  • 使用 Webpack 打包的2个原因:
    • 浏览器环境并不支持模块化
    • 零散的模块文件会产生大量的 HTTP 请求

浏览器对 ES Module 的支持
在这里插入图片描述
开箱即用

  • TypeScript - 内置支持
  • less / sass / stylus / postcss 内置支持 (需要单独安装)
  • JSX
  • Web Assembly

Vite 特性

  • 快速冷启动
  • 模块热更新
  • 按需编译
  • 开箱即用

Vite 实现原理

静态 Web 服务器 (koa)

#!/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')
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值