Vue3 系列基础笔记

Vue3 系列基础笔记

    • 一. Vue3 快速上手
        • 1. 认识 Vue3
            • 1.1 了解相关信息
            • 1.2 性能提升
            • 1.3 源码升级
            • 1.3 新增特性
        • 2. 创建 Vue3 项目
            • 2.1 使用 vue-cli 创建
            • 2.2 使用 vite 创建
            • 2.3 分析工程结构
    • 二. Composition API
        • 1. Composition API (常用部分)
            • 1.1 setup
            • 1.2 setup 细节
            • 1.3 ref
            • 1.4 reactive
            • 1.5 reactive 与 ref- 细节
            • 1.6 比较 Vue2 与 Vue3 的响应式
            • 1.7 计算属性与监视
            • 1.8 生命周期
            • 1.9 自定义hook函数
            • 1.10 toRefs
            • 1.11 ref 获取元素
        • 2. Composition API (其他部分)
            • 2.1 shallowReactive 与 shallowRef
            • 2.2 readonly 与 shallowReadonly
            • 2.3 toRaw 与 markRaw
            • 2.4 toRef
            • 2.5 customRef
            • 2.6 provide 与 inject
            • 2.7 响应式数据的判断
        • 3. 手写组合 API
            • 3.1 shallowReactive 与 reactive
            • 3.2 shallowRef 与 ref
            • 3.3 shallowReadonly 与 readonly
            • 3.4 isRef, isReactive 与 isReadonly
        • 4. Composition API VS Option API
            • 4.1 Option API的问题
            • 4.2 使用 Compisition API
    • 三. 其他新组合和 API
        • 1. 新组件
            • 1.1 Fragment(片断)
            • 1.2 Teleport(瞬移)
            • 1.3 Suspense(不确定的)
        • 2. 其他新的 API
            • 2.1 全新的全局API
            • 2.2 将原来的全局API转移到应用对象
            • 2.3 模板语法变化
    • 四. 总结

一. Vue3 快速上手

1. 认识 Vue3

1.1 了解相关信息
  • Vue.js 3.0 “One Piece” 正式版在2020年9月发布
  • 2年多开发,100+位贡献者,2600+次提交,30+个RFC,600+次PR,99位贡献者
  • 官方文档:https://github.com/vuejs/vue-next/releases/tag/v3.0.0
  • Vue3 支持 Vue2 的大多数特性
  • 更好的支持 Typescript
1.2 性能提升
  • 打包大小减少41%
  • 初次渲染快55%, 更新渲染快133%
  • 内存减少54%
1.3 源码升级
  • 使用 Proxy 代替 defineProperty 实现数据响应式
  • 重写虚拟 DOM 的实现和 Tree-Shaking
1.3 新增特性
  • Composition (组合) API
    • setup配置
    • refreactive
    • watchwatchEffect
    • 新的生命周期函数
    • provideinject
  • 新的内置组件
    • Fragment文档碎片
    • Teleport瞬移组件的位置
    • Suspense异步加载组件的 loading 界面
  • 其它 API 更新
    • 全局 API 的修改
    • 将原来的全局 API 转移到应用对象
    • 模板语法变化
    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除 keyCode 支持作为 v-on 的修饰符

2. 创建 Vue3 项目

2.1 使用 vue-cli 创建
  • 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version

## 安装或者升级@vue/cli
npm install -g @vue/cli

## 创建项目
vue create my-project
2.2 使用 vite 创建
  • 官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
  • vite 官网:https://vitejs.cn
    • vite 是新一代前端构建工具由原生 ESM 驱动的 Web 开发构建工具
    • 优点:
      • 在开发环境下基于浏览器原生 ES imports 开发
      • 快速的冷启动,不需要等待打包操作
      • 即时的热模块更新,替换性能和模块数量的解耦让更新飞起
      • 真正的按需编译,不再等待整个应用编译完成
    • 传统构建与 vite 构建对比图

Vue3 系列基础笔记_第1张图片
Vue3 系列基础笔记_第2张图片

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
2.3 分析工程结构
  • main.ts
// 程序的主入口文件,ts 文件,是 main.ts
// 引入 createApp 函数,创建对应的应用,产生应用的实例对象
import { createApp } from 'vue'
// 引入App组件(所有组件的父级组件)
import App from './App.vue'
// 创建App应用返回对应的实例对象,调用 mount 方法进行挂载
createApp(App).mount('#app')
  • App.vue
<template>
  
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
template>

<script lang="ts">
// 这里可以使用 TypeScript 代码
import { Options, Vue } from 'vue-class-component';
import HelloWorld from './components/HelloWorld.vue';

// 利用传入的配置项,暴露出一个定义好的组件
@Options({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {}
script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
style>
  • shims-due.d.ts
/* eslint-disable */
declare module '*.vue' {
  // defineComponent 函数,目的是定义一个组件,内部可以传入一个配置对象
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

二. Composition API

1. Composition API (常用部分)

  • 官方文档: https://composition-api.vuejs.org/zh/api.html
1.1 setup
  • Vue3 中新的 option 配置项,值为一个函数,所有的组合API函数都在此使用, 只在初始化时执行一次
  • 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
<template>
  <div>{{number}}div>
template>

<script lang="ts">
  // defineComponent 函数,目的是定义一个组件,内部可以传入一个配置对象
  import { defineComponent } from 'vue'

  // 暴露出去一个定义好的组件
  export default defineComponent({
    // 当前组件的名字是 App
    name: 'App',
    setup() {
      const number = 10
      return { 
        number
      }
    }
  })
script>
1.2 setup 细节
  • setup 执行的时机
    • beforeCreate 之前执行(一次), 此时组件对象还没有创建
    • this 是 undefined, 不能通过 this 来访问 data / computed / methods / props
    • 其实所有的 composition API 相关回调函数中也都不可以
<template>
  <h2>Child子级组件h2>
  <h3>msg: {{msg}}h3>
template>

<script lang="ts">
  import { defineComponent } from 'vue'
  export default defineComponent({
    name: 'Child',
    props: ['msg'],
    // setup 细节问题
    // setup 是在 beforeCreate 生命周期回调之前就执行了,而且就执行一次
    // 由此可以推断出: setup 在执行的时候,当前的组件还没有创建出来,也就意味着: 组件实例对象 this 根本就不能用
    // this 是 undefined, 不能通过 this 来调用 data / computed / methods / props 中相关内容了
    // 其实所有的 composition API 相关回调函数中也都不可以
    
    // 数据初始化的生命周期回调
    beforeCreate() {
        console.log('beforeCreate 执行了')
    },
    // 界面渲染完毕
    // mounted() {

    // },
    setup() {
        console.log('setup 执行了');
        return {
            // setup 中一般都是返回一个对象,对象中的属性和方法都可以在 html 模版中直接使用
        }  
    }
  })
script>
  • setup 的返回值
    • 一般都返回一个对象
      • 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性 / 方法
    • 返回对象中的属性会与 data 函数返回对象的属性合并成为组件对象的属性
    • 返回对象中的方法会与 methods 中的方法合并成功组件对象的方法
    • 如果有重名, setup 优先
    • 注意:
      • 一般不要混合使用
        • methods 中可以访问 setup 提供的属性和方法, 但在 setup 方法中不能访问 data 和 methods
      • setup 不能是一个 async 函数
        • 因为返回值不再是 return 的对象, 而是 promise, 模板看不到 return 对象中的属性数据
<template>
  <h2>Child子级组件h2>
  <h3>msg: {{msg}}h3>
template>

<script lang="ts">
  import { defineComponent } from 'vue'
  export default defineComponent({
    name: 'Child',
    props: ['msg'],
    // setup 细节问题
    // setup 中的返回值是一个对象,内部的属性和方法是给 html 模版使用的
    // setup 中的对象内部的属性和 data 函数中的 return 对象的属性都可以在 html 模版中使用
    // setup 中的对象中的属性和 data 函数中的对象中的属性会合并为组件对象的属性
    // setup 中的对象中的方法和 methods 对象中的方法会合并为组件对象的方法
    // 在 Vue3 中尽量不要混合的使用 data 和 setup 及 methods 和 setup
    // 一般不要混合使用: methods 中可以访问 setup 提供的属性和方法, 但在 setup 方法中不能访问 data 和 methods
    // setup 不能是一个 async 函数: 因为返回值不再是 return 的对象,而是 promise,模版看不到 return 对象中的属性数据

    // 界面渲染完毕
    mounted() {
        console.log(this)
    },

    setup() {
        console.log('setup 执行了');
        const showMsg1 = () => {
            console.log('showMsg1方法')
        }
        return {
            // setup 中一般都是返回一个对象,对象中的属性和方法都可以在 html 模版中直接使用
            showMsg1
        }  
    },
    
    data () {
        return {
            count: 10
        }
    },
    
    methods: {
        showMsg2() {
            console.log('showMsg方法')
        }
    }
  })
script>
  • setup 的参数
    • setup(props, context) / setup(props, {attrs, slots, emit})
    • props
      • 包含 props 配置声明且传入了的所有属性的对象
    • attrs
      • 包含没有在 props 配置中声明的属性的对象, 相当于 this.$attrs
    • slots
      • 包含所有传入的插槽内容的对象, 相当于 this.$slots
    • emit
      • 用来分发自定义事件的函数, 相当于 this.$emit
1.3 ref
  • 作用: 定义一个数据的响应式
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用 (reference) 对象
    • js 中操作数据: xxx.value
    • 模板中操作数据: 不需要 .value
  • 一般用来定义一个基本类型的响应式数据
<template>
  <h3>{{ count }}h3>
  <button @click="updateCount">更新数据button>
template>

<script lang="ts">
  import { defineComponent, ref } from 'vue'
  export default defineComponent({
    name: 'App',
    // 需求:页面打开后可以直接看到一个数据,点击按钮后,该数据可以发生变化
    // vue2 的方式实现
    // data() {
    //   return {
    //     count: 0, // 属性
    //   }
    // },
    // methods: {
    //   updateCount() { // 方法
    //     this.count++
    //   }
    // }

    // vue3 的方式实现
    // setup 是组合API的入口函数

    setup() {
      // 变量
      // let count = 0 // 此时的数据并不是响应式的数据(响应式数据: 数据变化,页面跟着渲染变化)
      // ref 是一个函数,作用: 定义一个响应式的数据,返回的是一个 Ref 对象,对象中有一个 value 属性,
      // 如果需要对数据进行操作,需要使用该 Ref 对象调用 value 属性的方式进行数据的操作
      // html 模版中是不需要使用 .value 属性的写法
      // 一般用来定义一个基本类型的响应式数据
      // count 是 Ref 类型
      const count = ref(0)

      // 方法
      function updateCount() {
        // 报错的原因: count 是一个 Ref 对象,对象是不能进行 ++ 的操作
        // count++
        count.value++
      }
      // 返回的是一个对象
      return {
        // 属性
        count,
        // 方法
        updateCount
      }
    }
  })
script>
1.4 reactive
  • 作用: 定义多个数据的响应式
  • const proxy = reactive(obj)
    • 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”
    • 会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现
    • 通过代理对象操作源对象内部数据都是响应式的
<template>
  <h2>name: {{user.name}}h2>
  <h2>age: {{user.age}}h2>
  <h2>{{user.gender}}h2>
  <h2>son: {{user.son}}h2>
  <button @click="updateUser">更新数据button>
template>

<script lang="ts">
  import { defineComponent, reactive } from 'vue'
  export default defineComponent({
    name: 'App',
    // 需求:显示用户的相关信息,点击按钮,可以更新用户的相关信息数据

    /**
     * 作用: 定义多个数据的响应式
     * const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
     * 响应式转换是“深层的”: 会影响对象内部所有嵌套的属性
     * 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
     */

    setup() {
      // const obj: any = {
      const obj = {
        name: '张三',
        age: 28,
        sex: 'man',
        son: {
          name: '李四',
          age: 10
        }
      }
      // 把数据变成响应式的数据
      // 返回的是一个 proxy 的代理对象,被代理的目标对象就是 obj 对象
      // user 现在是代理对象,obj 是目标对象
      // user 对象的类型是 Proxy
      const user = reactive<any>(obj)
      
      // 方法
      const updateUser = () => {
        // 直接使用目标对象的方式来更新目标对象中的成员的值,是不可能的,只能使用代理对象的方式来更新数据(响应式数据)
        // user.name = '王五',
        // user.son.name = '王小五'

        // user 代理对象,obj 目标对象
        // user 对象或者 obj 对象添加一个新的属性,哪一种方式会影响界面的更新
        // obj.gender = '男' // 这种方式,界面没有更新渲染
        // user.gender = '男' // 这种方式,界面可以更新渲染,而且这个数据最终也添加到了 obj 对象上了
        // user 对象或者 obj 对象中移除一个已经存在的属性,哪一种方式会影响界面的更新
        // delete obj.age // 界面没有更新渲染,obj中确实没有了 age 这个属性
        // delete user.age // 界面更新渲染了,obj中确实没有了 age 这个属性

        // 如果操作代理对象,目标对象中的数据也会随之变化,同时如果想要在操作数据的时候,界面也要跟着重新更新渲染,
        // 那么也是操作代理对象
      }

      return {
        user,
        updateUser
      }
    }
  })
script>
1.5 reactive 与 ref- 细节
  • reactiveref 是Vue3 的 composition API 中2个最重要的响应式API
    • ref 用来处理基本类型数据, reactive 用来处理对象(递归深度响应式)
      • 如果用 ref 对象/数组, 内部会自动将对象/数组转换为 reactive 的代理对象
      • ref 内部: 通过给 value 属性添加 getter / setter 来实现对数据的劫持
      • reactive 内部: 通过使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据
      • ref 的数据操作: 在 js 中要 .value, 在模板中不需要(内部解析模板时会自动添加 .value)
<template>
  <h2>Apph2>
  <p>m1: {{m1}}p>
  <p>m2: {{m2}}p>
  <p>m3: {{m3}}p>
  <button @click="update">更新button>
template>

<script lang="ts">
	import { reactive, ref } from 'vue'
	export default {
	  setup () {
	    const m1 = ref('abc')
	    const m2 = reactive({x: 1, y: {z: 'abc'}})
	
	    // 使用ref处理对象  ==> 对象会被自动reactive为proxy对象
	    const m3 = ref({a1: 2, a2: {a3: 'abc'}})
	    console.log(m1, m2, m3)
	    console.log(m3.value.a2) // 也是一个proxy对象
	
	    function update() {
	      m1.value += '--'
	      m2.x += 1
	      m2.y.z += '++'
	
	      m3.value = {a1: 3, a2: {a3: 'abc---'}}
	      m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
	      console.log(m3.value.a2)
	    }
	
	    return {
	      m1,
	      m2,
	      m3,
	      update
	    }
	  }
	}
script>
1.6 比较 Vue2 与 Vue3 的响应式
  • Vue2 的响应式
    • 核心
      • 对象
        • 通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
      • 数组
        • 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})
  • 问题

    • 对象直接新添加的属性或删除已有属性, 界面不会自动更新
    • 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
  • Vue3 的响应式

    • 核心:
      • 通过 Proxy(代理)
        • 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
      • 通过 Reflect(反射)
        • 动态对被代理对象的相应属性进行特定的操作
    • 文档
      • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
      • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, {
	// 拦截读取属性值
    get (target, prop) {
    	return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
    	return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
    	return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom'  
DOCTYPE html>
<html lang="en">
	<head>
	  <meta charset="UTF-8">
	  <meta name="viewport" content="width=device-width, initial-scale=1.0">
	  <title>Proxy 与 Reflecttitle>
	head>
	<body>
	  <script>
	  	// 目标对象
	    const user = {
	      name: "John",
	      age: 12,
	      son: {
			name: 'mark'
		  }
	    };
	    /* 
	    proxyUser是代理对象, user是被代理对象
	    后面所有的操作都是通过代理对象来操作被代理对象内部属性
	    */
	    // 把目标对象变为代理对象
	    // 参数1: user --> target 目标对象
	    // 参数2: handler --> 处理器对象,用来监视数据,及数据的操作
	    const proxyUser = new Proxy(user, {
	      // 获取目标对象的某个属性值
	      get(target, prop) {
	        console.log('劫持get()', prop)
	        return Reflect.get(target, prop)
	      },
	      // 修改目标对象的属性值/为目标对象添加新的属性
	      set(target, prop, val) {
	        console.log('劫持set()', prop, val)
	        return Reflect.set(target, prop, val); // (2)
	      },
	      // 删除目标对象上的某个属性
	      deleteProperty (target, prop) {
	        console.log('劫持delete属性', prop)
	        return Reflect.deleteProperty(target, prop)
	      }
	    });
	    // 通过代理对象获取目标对象中的某个属性值
	    // 读取属性值
	    console.log(proxyUser===user)
	    console.log(proxyUser.name, proxyUser.age)
	    // 通过代理对象更新目标对象上的某个属性值
	    proxyUser.name = 'bob'
	    proxyUser.age = 13
	    console.log(user)
	    // 通过代理对象向目标对象中添加一个新的属性
	    proxyUser.sex = '男'
	    console.log(user)
	    // 删除目标对象上的某个属性
	    delete proxyUser.sex
	    console.log(user)
	    // 更新目标对象中的某个属性对象中的属性值
	    proxyUser.son.name = 'david'
	    console.log(user)  
	  script>
	body>
html>
1.7 计算属性与监视
  • computed 函数
    • 与 computed 配置功能一致
    • 只有 getter
    • 有 getter 和 setter
  • watch 函数
    • 与 watch 配置功能一致
    • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
    • 默认初始时不执行回调, 但可以通过配置 immediate 为 true, 来指定初始时立即执行第一次
    • 通过配置 deep 为 true, 来指定深度监视
  • watchEffect 函数
    • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
    • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
    • 监视数据发生变化时回调
<template>
  <h2>计算属性和监视h2>
  <fieldset>
    <legend>姓名操作legend>
    姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName" /><br/>
    名字:<input type="text" placeholder="请输入名字" v-model="user.lastName" /><br/>
  fieldset>
  <fieldset>
    <legend>计算属性和监视的演示legend>
    姓名:<input type="text" placeholder="显示姓名" v-model="fullName1" /><br/>
    姓名:<input type="text" placeholder="显示姓名" v-model="fullName2" /><br/>
    姓名:<input type="text" placeholder="显示姓名" v-model="fullName3" /><br/>
  fieldset>
template>

<script lang="ts">
  import {computed, defineComponent, reactive, ref, watch, watchEffect} from 'vue'
  export default defineComponent({
    name: 'App',
    setup() {
      // 定义一个响应式对象
      const user = reactive({
        // 姓氏
        firstName: '东方',
        // 名字
        lastName: '不败'
      })
      // 通过计算属性的方式,实现第一个姓名的显示
      // vue3 中的计算属性
      // 计算属性的函数中如果只传入一个回调函数,表示的是 get

      // 第一个姓名:
      // 返回的是一个 Ref 类型的对象
      const fullName1 = computed(() => {
        return user.firstName + '_' + user.lastName
      })
      // 第二个姓名:
      const fullName2 = computed({
        get() {
          return user.firstName + '_' + user.lastName
        },
        set(val: string) {
          // console.log('=======', val)
          const names = val.split('_')
          user.firstName = names[0]
          user.lastName = names[1]
        }
      })

      // 第三个姓名:
      const fullName3 = ref('')
      // 监视 --- 监视指定的数据
      watch(user, ({firstName, lastName}) => {
        fullName3.value = firstName + '_' + lastName
      },{immediate: true, deep: true})
      // immediate 默认会执行一次 watch,deep 深度监视

      // 监视,不需要配置 immediate, 本身默认就会进行监视,(默认执行一次)
      // watchEffect(() => {
      //   fullName3.value = user.firstName + '_' + user.lastName
      // })

      // 监视fullName3的数据,改变firstName和lastName
      watchEffect(() => {
        const names = fullName3.value.split('_')
        user.firstName = names[0]
        user.lastName = names[1]
      })

      // watch -- 可以监视多个数据的
      // watch([user.firstName, user.lastName,fullName3],() => {
      //   // 这里的代码就没有执行,fullName3 是响应式的数据,但是 user.firstName, user.lastName 不是响应式的数据
      //   console.log('===');
      // })
      // 当我们使用 watch 监视非响应式的数据的时候,代码需要改一下
      watch([() => user.firstName,() => user.lastName, fullName3], () => {
        // 这里代码就没有执行,fullName3 是响应式的数据,但是,user.firstName, user.lastName 不是响应式的数据
        console.log('====');
      })
      
      return {
        user,
        fullName1,
        fullName2,
        fullName3
      }
    }

  })
script>
<template>
  <h2>Apph2>
  fistName: <input v-model="user.firstName"/><br>
  lastName: <input v-model="user.lastName"/><br>
  fullName1: <input v-model="fullName1"/><br>
  fullName2: <input v-model="fullName2"><br>
  fullName3: <input v-model="fullName3"><br>

template>

<script lang="ts">
	/*
	计算属性与监视
	1. computed函数: 
	  与computed配置功能一致
	  只有getter
	  有getter和setter
	2. watch函数
	  与watch配置功能一致
	  监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
	  默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
	  通过配置deep为true, 来指定深度监视
	3. watchEffect函数
	  不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
	  默认初始时就会执行第一次, 从而可以收集需要监视的数据
	  监视数据发生变化时回调
	*/

	import {
	  reactive,
	  ref,
	  computed,
	  watch,
	  watchEffect
	} from 'vue'
	
	export default {
	
	  setup () {
	    const user = reactive({
	      firstName: 'A',
	      lastName: 'B'
	    })
	
	    // 只有getter的计算属性
	    const fullName1 = computed(() => {
	      console.log('fullName1')
	      return user.firstName + '-' + user.lastName
	    })
	
	    // 有getter与setter的计算属性
	    const fullName2 = computed({
	      get () {
	        console.log('fullName2 get')
	        return user.firstName + '-' + user.lastName
	      },
	
	      set (value: string) {
	        console.log('fullName2 set')
	        const names = value.split('-')
	        user.firstName = names[0]
	        user.lastName = names[1]
	      }
	    })
	
	    const fullName3 = ref('')
	
	    /* 
	    watchEffect: 监视所有回调中使用的数据
	    */
	    /* 
			watchEffect(() => {
			  console.log('watchEffect')
			  fullName3.value = user.firstName + '-' + user.lastName
			}) 
	    
			// 监视fullName3的数据,改变firstName和lastName
			// watchEffect(() => {
			//   const names = fullName3.value.split('_')
			//   user.firstName = names[0]
			//   user.lastName = names[1]
			// })
	    */
	
	    /* 
	    使用watch的2个特性:
	      深度监视
	      初始化立即执行
	    */
	    watch(user, () => {
	      fullName3.value = user.firstName + '-' + user.lastName
	    }, {
	      immediate: true,  // 是否初始化立即执行一次, 默认是false
	      deep: true, // 是否是深度监视, 默认是false
	    })
	
	    /* 
	    watch一个数据
	      默认在数据发生改变时执行回调
	    */
	    watch(fullName3, (value) => {
	      console.log('watch')
	      const names = value.split('-')
	      user.firstName = names[0]
	      user.lastName = names[1]
	    })
	
	    /* 
	    watch多个数据: 
	      使用数组来指定
	      如果是ref对象, 直接指定
	      如果是reactive对象中的属性,  必须通过函数来指定
	    */
	    watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
	      console.log('监视多个数据', values)
	    })
	
	    return {
	      user,
	      fullName1,
	      fullName2,
	      fullName3
	    }
	  }
	}
script>
1.8 生命周期
  • vue2.x 的生命周期

Vue3 系列基础笔记_第3张图片

  • vue3 的生命周期

Vue3 系列基础笔记_第4张图片

  • 与 2.x 版本生命周期相对应的组合式 API
    • beforeCreate -> 使用 setup()
    • created -> 使用 setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeDestroy -> onBeforeUnmount
    • destroyed -> onUnmounted
    • errorCaptured -> onErrorCaptured
  • 新增的钩子函数
    • 组合式 API 还提供了以下调试钩子函数:
      • onRenderTracked
      • onRenderTriggered
<template>
<div class="about">
  <h2>msg: {{msg}}h2>
  <hr>
  <button @click="update">更新button>
div>
template>

<script lang="ts">
import {
  ref,
  onMounted,
  onUpdated,
  onUnmounted, 
  onBeforeMount, 
  onBeforeUpdate,
  onBeforeUnmount
} from "vue"

export default {
  beforeCreate () {
    console.log('beforeCreate()')
  },

  created () {
    console.log('created')
  },

  beforeMount () {
    console.log('beforeMount')
  },

  mounted () {
    console.log('mounted')
  },

  beforeUpdate () {
    console.log('beforeUpdate')
  },

  updated () {
    console.log('updated')
  },

  beforeUnmount () {
    console.log('beforeUnmount')
  },

  unmounted () {
     console.log('unmounted')
  },
  
  setup() {
    const msg = ref('abc')
    const update = () => {
      msg.value += '--'
    }

    onBeforeMount(() => {
      console.log('--onBeforeMount')
    })

    onMounted(() => {
      console.log('--onMounted')
    })

    onBeforeUpdate(() => {
      console.log('--onBeforeUpdate')
    })

    onUpdated(() => {
      console.log('--onUpdated')
    })

    onBeforeUnmount(() => {
      console.log('--onBeforeUnmount')
    })

    onUnmounted(() => {
      console.log('--onUnmounted')
    })
    
    return {
      msg,
      update
    }
  }
}
script>
<template>
  <h2>Apph2>
  <button @click="isShow=!isShow">切换button>
  <hr>
  <Child v-if="isShow"/>
template>

<script lang="ts">
import Child from './Child.vue'
export default {
  data () {
    return {
      isShow: true
    }
  },
  components: {
    Child
  }
}
script>
1.9 自定义hook函数
  • 使用 Vue3 的组合 API 封装的可复用的功能函数
  • 自定义 hook 的作用类似于 vue2 中的 mixin 技术
  • 自定义 hook 的优势: 很清楚复用功能代码的来源, 更清楚易懂
    • 需求1: 收集用户鼠标点击的页面坐标
      • hooks / useMousePosition.ts
import { ref, onMounted, onUnmounted } from 'vue'
/* 
收集用户鼠标点击的页面坐标
*/
export default function useMousePosition () {
  // 初始化坐标数据
  const x = ref(-1)
  const y = ref(-1)

  // 用于收集点击事件坐标的函数
  const updatePosition = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  // 挂载后绑定点击监听
  onMounted(() => {
    document.addEventListener('click', updatePosition)
  })

  // 卸载前解绑点击监听
  onUnmounted(() => {
    document.removeEventListener('click', updatePosition)
  })

  return {x, y}
}
<template>
<div>
  <h2>x: {{x}}, y: {{y}}h2>
div>
template>

<script>
	import { ref } from "vue"
	/* 
	在组件中引入并使用自定义hook
	自定义hook的作用类似于vue2中的mixin技术
	自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
	*/
	import useMousePosition from './hooks/useMousePosition'
	
	export default {
	  setup() {
	    const {x, y} = useMousePosition()
	    return {
	      x,
	      y,
	    }
	  }
	}
script>
  • 利用TS泛型强化类型检查
    • 需求2: 封装发 ajax 请求的 hook 函数
      • hooks / useRequest.ts
import { ref } from 'vue'
import axios from 'axios'

/* 
使用axios发送异步ajax请求
*/
export default function useUrlLoader<T>(url: string) {
  // 加载的状态
  const loading = ref(true)
  // 请求成功的数据
  const result = ref<T | null>(null)
  // 错误信息
  const errorMsg = ref(null)

  axios.get(url)
    .then(response => {
      // 改变加载状态
      loading.value = false
      result.value = response.data
    })
    .catch(e => {
      // 改变加载状态
      loading.value = false
      errorMsg.value = e.message || '未知错误'
    })

  return {
    loading,
    result,
    errorMsg,
  }
}
<template>
<div class="about">
  <h2 v-if="loading">LOADING...h2>
  <h2 v-else-if="errorMsg">{{errorMsg}}h2>
  

  <ul v-for="p in result" :key="p.id">
    <li>id: {{p.id}}li>
    <li>title: {{p.title}}li>
    <li>price: {{p.price}}li>
  ul>
  
div>
template>

<script lang="ts">
import {
  watch
} from "vue"
import useRequest from './hooks/useRequest'

// 定义接口,约束对象的类型
// 地址数据接口
interface AddressResult {
  id: number;
  name: string;
  distance: string;
}

// 产品数据接口
interface ProductResult {
  id: string;
  title: string;
  price: number;
}

export default {
  setup() {

    // const {loading, result, errorMsg} = useRequest('/data/address.json')
    const {loading, result, errorMsg} = useRequest<ProductResult[]>('/data/products.json')

    watch(result, () => {
      if (result.value) {
        console.log(result.value.length) // 有提示
      }
    })

    return {
      loading,
      result, 
      errorMsg
    }
  }
}
script>
1.10 toRefs
  • 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
  • 应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
  • 问题: reactive 对象取出的所有属性值都是非响应式的
  • 解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
<template>
  <h2>Apph2>
  <h3>foo: {{foo}}h3>
  <h3>bar: {{bar}}h3>
  <h3>foo2: {{foo2}}h3>
  <h3>bar2: {{bar2}}h3>
template>

<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
  将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
  应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
        这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default {
  
  setup () {

    const state = reactive({
      foo: 'a',
      bar: 'b',
    })
	
	// toRefs 可以把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
    const stateAsRefs = toRefs(state)
    
	// 定时器,更新数据(如果数据变化了,界面也会随之变化,肯定是响应式的数据)
    setTimeout(() => {
      state.foo += '++'
      state.bar += '++'
    }, 2000);

    const {foo2, bar2} = useReatureX()

    return {
      // ...state,
      ...stateAsRefs,
      foo2, 
      bar2
    }
  },
}

function useReatureX() {
  const state = reactive({
    foo2: 'a',
    bar2: 'b',
  })

  setTimeout(() => {
    state.foo2 += '++'
    state.bar2 += '++'
  }, 2000);

  return toRefs(state)
}

script>
1.11 ref 获取元素
  • 利用 ref 函数获取组件中的标签元素
  • 功能需求: 让输入框自动获取焦点
<template>
  <h2>Apph2>
  <input type="text">---
  <input type="text" ref="inputRef">
template>

<script lang="ts">
import { onMounted, ref } from 'vue'
/* 
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
  setup() {
    const inputRef = ref<HTMLElement|null>(null)

    onMounted(() => {
      inputRef.value && inputRef.value.focus()
    })

    return {
      inputRef
    }
  },
}
script>

2. Composition API (其他部分)

2.1 shallowReactive 与 shallowRef
  • shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
  • shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
  • 什么时候用浅响应式呢?
    • 一般情况下使用 ref 和 reactive 即可
    • 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    • 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
<template>
  <h2>Apph2>

  <h3>m1: {{m1}}h3>
  <h3>m2: {{m2}}h3>
  <h3>m3: {{m3}}h3>
  <h3>m4: {{m4}}h3>

  <button @click="update">更新button>
template>

<script lang="ts">
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
/* 
  shallowReactive 与 shallowRef
  shallowReactive: 只处理了对象内最外层属性的响应式(也就是浅响应式)
  shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
总结:
  reactive与ref实现的是深度响应式, 而shallowReactive与shallowRef是浅响应式
  什么时候用浅响应式呢?
    一般情况下使用ref和reactive即可,
    如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
*/

export default {

  setup () {

    const m1 = reactive({a: 1, b: {c: 2}})
    const m2 = shallowReactive({a: 1, b: {c: 2}})

    const m3 = ref({a: 1, b: {c: 2}})
    const m4 = shallowRef({a: 1, b: {c: 2}})

    const update = () => {
      // m1.b.c += 1
      // m2.b.c += 1

      // m3.value.a += 1
      m4.value.a += 1
    }

    return {
      m1,
      m2,
      m3,
      m4,
      update,
    }
  }
}
script>
2.2 readonly 与 shallowReadonly
  • readonly:
    • 深度只读数据
    • 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
    • 只读代理是深层的:访问的任何嵌套 property 也是只读的。
  • shallowReadonly
    • 浅只读数据
    • 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
  • 应用场景:
    • 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
<template>
  <h2>Apph2>
  <h3>{{state}}h3>
  <button @click="update">更新button>
template>

<script lang="ts">
import { reactive, readonly, shallowReadonly } from 'vue'
/*
readonly: 深度只读数据
  获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
  只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly: 浅只读数据
  创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 
应用场景: 
  在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
*/

export default {

  setup () {

    const state = reactive({
      a: 1,
      b: {
        c: 2
      }
    })

    // const rState1 = readonly(state)
    const rState2 = shallowReadonly(state)

    const update = () => {
      // rState1.a++ // error
      // rState1.b.c++ // error

      // rState2.a++ // error
      rState2.b.c++
    }
    
    return {
      state,
      update
    }
  }
}
script>
2.3 toRaw 与 markRaw
  • toRaw
    • 返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。
    • 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
  • markRaw
    • 标记一个对象,使其永远不会转换为代理。返回对象本身
    • 应用场景:
      • 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
      • 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
<template>
  <h2>{{state}}h2>
  <button @click="testToRaw">测试toRawbutton>
  <button @click="testMarkRaw">测试markRawbutton>
template>

<script lang="ts">
/* 
toRaw: 得到reactive代理对象的目标数据对象
*/
import {
  markRaw,
  reactive, toRaw,
} from 'vue'
export default {
  setup () {
    const state = reactive<any>({
      name: 'tom',
      age: 25,
    })

    const testToRaw = () => {
      const user = toRaw(state)
      user.age++  // 界面不会更新

    }

    const testMarkRaw = () => {
      const likes = ['a', 'b']
      // state.likes = likes
      state.likes = markRaw(likes) // likes数组就不再是响应式的了
      setTimeout(() => {
        state.likes[0] += '--'
      }, 1000)
    }

    return {
      state,
      testToRaw,
      testMarkRaw,
    }
  }
}
script>
2.4 toRef
  • 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
  • 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
  • 应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
<template>
  <h2>Apph2>
  <p>{{state}}p>
  <p>{{foo}}p>
  <p>{{foo2}}p>

  <button @click="update">更新button>

  <Child :foo="foo"/>
template>

<script lang="ts">
/*
toRef:
  为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
  区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
  应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
*/

import {
  reactive,
  toRef,
  ref,
} from 'vue'
import Child from './Child.vue'

export default {

  setup () {

    const state = reactive({
      foo: 1,
      bar: 2
    })

    const foo = toRef(state, 'foo')
    const foo2 = ref(state.foo)

    const update = () => {
      state.foo++
      // foo.value++
      // foo2.value++  // foo和state中的数据不会更新
    }

    return {
      state,
      foo,
      foo2,
      update,
    }
  },

  components: {
    Child
  }
}
script>
<template>
  <h2>Childh2>
  <h3>{{foo}}h3>
  <h3>{{length}}h3>
template>

<script lang="ts">
import { computed, defineComponent, Ref, toRef } from 'vue'

const component = defineComponent({
  props: {
    foo: {
      type: Number,
      require: true
    }
  },

  setup (props, context) {
    const length = useFeatureX(toRef(props, 'foo'))

    return {
      length
    }
  }
})

function useFeatureX(foo: Ref) {
  const lenth = computed(() => foo.value.length)

  return lenth
}

export default component
script>
2.5 customRef
  • 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
  • 需求: 使用 customRef 实现 debounce 的示例
<template>
  <h2>Apph2>
  <input v-model="keyword" placeholder="搜索关键字"/>
  <p>{{keyword}}p>
template>

<script lang="ts">
/*
customRef:
  创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制

需求: 
  使用 customRef 实现 debounce 的示例
*/

import {
  ref,
  customRef
} from 'vue'

export default {

  setup () {
    const keyword = useDebouncedRef('', 500)
    console.log(keyword)
    return {
      keyword
    }
  },
}

/* 
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: number
  return customRef((track, trigger) => {
    return {
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      set(newValue: T) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 告诉Vue去触发界面更新
          trigger()
        }, delay)
      }
    }
  })
}

script>
2.6 provide 与 inject
  • provide 和 inject 提供依赖注入,功能类似 2.x 的 provide / inject
  • 实现跨层级组件(祖孙)间通信
<template>
  <h1>父组件h1>
  <p>当前颜色: {{color}}p>
  <button @click="color='red'">button>
  <button @click="color='yellow'">button>
  <button @click="color='blue'">button>
  
  <hr>
  <Son />
template>

<script lang="ts">
import { provide, ref } from 'vue'
/* 
- provide` 和 `inject` 提供依赖注入,功能类似 2.x 的 `provide/inject
- 实现跨层级组件(祖孙)间通信
*/

import Son from './Son.vue'
export default {
  name: 'ProvideInject',
  components: {
    Son
  },
  setup() {
    
    const color = ref('red')

    provide('color', color)

    return {
      color
    }
  }
}
script>
<template>
  <div>
    <h2>子组件h2>
    <hr>
    <GrandSon />
  div>
template>

<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
  components: {
    GrandSon
  },
}
script>
<template>
  <h3 :style="{color}">孙子组件: {{color}}h3>
template>

<script lang="ts">
import { inject } from 'vue'
export default {
  setup() {
    const color = inject('color')

    return {
      color
    }
  }
}
script>
2.7 响应式数据的判断
  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

3. 手写组合 API

3.1 shallowReactive 与 reactive
const reactiveHandler = {
  get (target, key) {

    if (key==='_is_reactive') return true

    return Reflect.get(target, key)
  },

  set (target, key, value) {
    const result = Reflect.set(target, key, value)
    console.log('数据已更新, 去更新界面')
    return result
  },

  deleteProperty (target, key) {
    const result = Reflect.deleteProperty(target, key)
    console.log('数据已删除, 去更新界面')
    return result
  },
}

/* 
自定义shallowReactive
*/
function shallowReactive(obj) {
  return new Proxy(obj, reactiveHandler)
}

/* 
自定义reactive
*/
function reactive (target) {
  if (target && typeof target==='object') {
    if (target instanceof Array) { // 数组
      target.forEach((item, index) => {
        target[index] = reactive(item)
      })
    } else { // 对象
      Object.keys(target).forEach(key => {
        target[key] = reactive(target[key])
      })
    }

    const proxy = new Proxy(target, reactiveHandler)
    return proxy
  }

  return target
}


/* 测试自定义shallowReactive */
const proxy = shallowReactive({
  a: {
    b: 3
  }
})

proxy.a = {b: 4} // 劫持到了
proxy.a.b = 5 // 没有劫持到


/* 测试自定义reactive */
const obj = {
  a: 'abc',
  b: [{x: 1}],
  c: {x: [11]},
}

const proxy = reactive(obj)
console.log(proxy)
proxy.b[0].x += 1
proxy.c.x[0] += 1
3.2 shallowRef 与 ref
/*
自定义shallowRef
*/
function shallowRef(target) {
  const result = {
    _value: target, // 用来保存数据的内部属性
    _is_ref: true, // 用来标识是ref对象
    get value () {
      return this._value
    },
    set value (val) {
      this._value = val
      console.log('set value 数据已更新, 去更新界面')
    }
  }

  return result
}

/* 
自定义ref
*/
function ref(target) {
  if (target && typeof target==='object') {
    target = reactive(target)
  }

  const result = {
    _value: target, // 用来保存数据的内部属性
    _is_ref: true, // 用来标识是ref对象
    get value () {
      return this._value
    },
    set value (val) {
      this._value = val
      console.log('set value 数据已更新, 去更新界面')
    }
  }

  return result
}

/* 测试自定义shallowRef */
const ref3 = shallowRef({
  a: 'abc',
})
ref3.value = 'xxx'
ref3.value.a = 'yyy'


/* 测试自定义ref */
const ref1 = ref(0)
const ref2 = ref({
  a: 'abc',
  b: [{x: 1}],
  c: {x: [11]},
})
ref1.value++
ref2.value.b[0].x++
console.log(ref1, ref2)
3.3 shallowReadonly 与 readonly
const readonlyHandler = {
  get (target, key) {
    if (key==='_is_readonly') return true

    return Reflect.get(target, key)
  },

  set () {
    console.warn('只读的, 不能修改')
    return true
  },

  deleteProperty () {
    console.warn('只读的, 不能删除')
    return true
  },
}

/* 
自定义shallowReadonly
*/
function shallowReadonly(obj) {
  return new Proxy(obj, readonlyHandler)
}

/* 
自定义readonly
*/
function readonly(target) {
  if (target && typeof target==='object') {
    if (target instanceof Array) { // 数组
      target.forEach((item, index) => {
        target[index] = readonly(item)
      })
    } else { // 对象
      Object.keys(target).forEach(key => {
        target[key] = readonly(target[key])
      })
    }
    const proxy = new Proxy(target, readonlyHandler)

    return proxy 
  }

  return target
}

/* 测试自定义readonly */
/* 测试自定义shallowReadonly */
const objReadOnly = readonly({
  a: {
    b: 1
  }
})
const objReadOnly2 = shallowReadonly({
  a: {
    b: 1
  }
})

objReadOnly.a = 1
objReadOnly.a.b = 2
objReadOnly2.a = 1
objReadOnly2.a.b = 2
3.4 isRef, isReactive 与 isReadonly
/* 
判断是否是ref对象
*/
function isRef(obj) {
  return obj && obj._is_ref
}

/* 
判断是否是reactive对象
*/
function isReactive(obj) {
  return obj && obj._is_reactive
}

/* 
判断是否是readonly对象
*/
function isReadonly(obj) {
  return obj && obj._is_readonly
}

/* 
是否是reactive或readonly产生的代理对象
*/
function isProxy (obj) {
  return isReactive(obj) || isReadonly(obj)
}

/* 测试判断函数 */
console.log(isReactive(reactive({})))
console.log(isRef(ref({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))

4. Composition API VS Option API

4.1 Option API的问题
  • 在传统的 Vue OptionsAPI 中,新增或者修改一个需求,就需要分别在 data,methods,computed 里修改 ,滚动条反复上下移动


4.2 使用 Compisition API
  • 我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起



Vue3 系列基础笔记_第5张图片

三. 其他新组合和 API

1. 新组件

1.1 Fragment(片断)
  • 在 Vue2 中: 组件必须有一个根标签
  • 在 Vue3 中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用
<template>
    <h2>aaaah2>
    <h2>aaaah2>
template>
1.2 Teleport(瞬移)
  • Teleport 提供了一种干净的方法, 让组件的 html 在父组件界面外的特定标签(很可能是body)下插入显示
  • ModalButton.vue
<template>
  <button @click="modalOpen = true">
      Open full screen modal! (With teleport!)
  button>

  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        I'm a teleported modal! 
        (My parent is "body")
        <button @click="modalOpen = false">
          Close
        button>
      div>
    div>
  teleport>
template>

<script>
import { ref } from 'vue'
export default {
  name: 'modal-button',
  setup () {
    const modalOpen = ref(false)
    return {
      modalOpen
    }
  }
}
script>


<style>
.modal {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background-color: rgba(0,0,0,.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: white;
  width: 300px;
  height: 300px;
  padding: 5px;
}
style>
  • App.vue
<template>
  <h2>Apph2>
  <modal-button>modal-button>
template>

<script lang="ts">
import ModalButton from './ModalButton.vue'

export default {
  setup() {
    return {
    }
  },

  components: {
    ModalButton
  }
}
script>
1.3 Suspense(不确定的)
  • 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
<template>
  <Suspense>
    <template v-slot:default>
      <AsyncComp/>
      
    template>

    <template v-slot:fallback>
      <h1>LOADING...h1>
    template>
  Suspense>
template>

<script lang="ts">
/* 
异步组件 + Suspense组件
*/
// import AsyncComp from './AsyncComp.vue'
import AsyncAddress from './AsyncAddress.vue'
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
  setup() {
    return {
     
    }
  },

  components: {
    AsyncComp,
    AsyncAddress
  }
}
script>
  • AsyncComp.vue
<template>
  <h2>AsyncComp22h2>
  <p>{{msg}}p>
template>

<script lang="ts">

export default {
  name: 'AsyncComp',
  setup () {
    // return new Promise((resolve, reject) => {
    //   setTimeout(() => {
    //     resolve({
    //       msg: 'abc'
    //     })
    //   }, 2000)
    // })
    return {
      msg: 'abc'
    }
  }
}
script>
  • AsyncAddress.vue
<template>
<h2>{{data}}h2>
template>

<script lang="ts">
import axios from 'axios'
export default {
  async setup() {
    const result = await axios.get('/data/address.json')
    return {
      data: result.data
    }
  }
}
script>

2. 其他新的 API

2.1 全新的全局API
  • createApp()
  • defineProperty()
  • defineAsyncComponent()
  • nextTick()
2.2 将原来的全局API转移到应用对象
  • app.component()
  • app.config()
  • app.directive()
  • app.mount()
  • app.unmount()
  • app.use()
2.3 模板语法变化
  • v-model 的本质变化
    • prop:value -> modelValue;
    • event:input -> update:modelValue;
  • .sync 修改符已移除, 由 v-model 代替
  • v-if 优先 v-for 解析

四. 总结

  • 学习 Vue3,记个笔记

你可能感兴趣的:(#,Vue相关,Vue,Vue3)