Vue3学习笔记(尚硅谷)

lxf2023-05-05 16:33:01

Vue3文档

Vue3带来了什么

1. 性能的提升

  • 打包大小减少41%
  • 初次渲染快55%,更新渲染快133%
  • 内存减少54%……

2. 源码的升级

  • 使用Proxy代替defineProperty实现响应式
  • 重写虚拟DOM的实现和Tree-Shaking……

3. 拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4. 新的特性

……

一、创建Vue3.0工程

1.使用 vue-cli创建

官方文档:cli.vuejs.org/zh/guide/cr…

##查看 @vue/cli版本,确保 @vue/cli版本在4.5.0以上
vue --version 或 vue -V

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

##创建
vue create vue_project

##启动
cd vue_project
npm run serve

Vue3学习笔记(尚硅谷)

Vue3学习笔记(尚硅谷)

这个运行项目的速度明显比使用vite创建要慢,因为这种方式会先进行打包。

2.使用vite创建

官方文档:v3.cn.vuejs.org/guide/insta…

vite官网:vitejs.cn

vite是新一代的前端构建工具。 优势如下:

  • 开发环境中,无需打包操作,可快速的冷启动。
  • 轻量快速的热重载。
  • 真正的按需编译,不再等待整个应用编译完成。

搭建第一个vite项目:

提醒:Vite 需要 Node.js 版本 >= 12.0.0

npm init vite@latest vue3_test 或者 npm init vite-app vue3_test Vue3学习笔记(尚硅谷)

Vue3学习笔记(尚硅谷)

切换到文件夹下 npm i 安装依赖:
Vue3学习笔记(尚硅谷) Vue3学习笔记(尚硅谷)

运行项目:
Vue3学习笔记(尚硅谷)

3.分析工程结构

Vue3学习笔记(尚硅谷)

二、常用Composition API

1. 拉开序幕的setup

(1)Vue3.0中一个新的配置项,值为一个函数。

(2)setup是所有Composition API(组合API)“表演的舞台”。

(3)组件中所用到的:数据、方法等,均要配置在setup中。

(4)setup函数的两种返回值:

  • 若返回一个对象,则对象中的属性、方法,在模板中均可以直接输用。(重点关注)
  • 若返回一个渲染函数:则可以自定义渲染内容。(了解)

(5)注意点

a. 尽量不要与Vue2.x配置混用

  • Vue2.x配置(data、methods、computed…)中可以访问到setup中的属性、方法。
  • 但在setup中不能访问到Vue2.x配置
  • 如果有重名,setup优先。

b. setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件配合)

<template>
	<h2>My Information</h2>
	<p>姓名:{{ name }}</p>
	<p>年龄:{{ age }}</p>
	<button @click="sayHello">sayHello</button>
</template>

<script>
    export default {
	name: "App",
	setup() {
            // 数据(在模板中不用调用value)
            let name = "Daming";
            let age = 18;
            // 函数
            function sayHello() {
                alert(`我的名字是${name},年龄是${age}`);
            }
            return {
                name,
                age,
                sayHello,
            };
	},
    };
</script>

2. ref函数

作用:定义一个响应式的数据。

语法const xxx = ref() //const定义的对象可以改变属性的值,但不能再赋值

  • 创建一个包含响应式数据的引用对象(reference对象)
  • JS中操作数据:xxx.value
  • 模板中读取数据:不需要.value,直接 {{ xxx }}

备注:

  • 接收的数据可以是:基本类型、也可以是对象类型
  • 基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
  • 对象类型的数据:内部'求助'了Vue3.0中的一个新的函数——reactive函数。
    // 在模板中不用写name.value 会自动调用value
    <p>姓名:{{ name }}</p>
    
    // 引入函数
    import { ref } from 'vue'
    
    // setup函数中要这样写
    let name = ref('Daming')
    function sayHello() {
        console.log(name);
        console.log(age);
        // 重新赋值要调用value属性
        name.value = "cheche";
        alert(`我的名字是${name.value},年龄是${age.value}`);
    }
    
    return {
        name,
        age,
        sayHello, // 这里不写按钮没反应
    };

Vue3学习笔记(尚硅谷)

ref函数处理对象类型的数据:

    import { ref } from 'vue'
    
    let obj = ref({
        type: '前端开发',
        salary: '30k'
    })
    
    function changeInfo(){
        console.log(obj.value)
        obj.value.type = 'UI设计'
        obj.value.salary = '40k'
    }     

3. reactive函数

  • 作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法: const 代理对象 = reactive(源对象) 接收一个对象(或数组),返回一个代理对象(proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是"深层次的"
  • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作
    import { reactive } from 'vue'
    
    let obj = reactive({
        type: '前端开发',
        salary: '30k'
    })
    
    function changeInfo(){
        console.log(obj)
        obj.type = 'UI设计'
        obj.salary = '40k'
    }

Vue3学习笔记(尚硅谷)

4. Vue3.0中的响应式原理

Vue2.x的响应式

实现原理:

  • 对象类型:通过Object.defineProperty( )对属性的读取、修改进行拦截(数据劫持)。回顾Object.defineProperty
  • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)

存在问题:

  • 新增属性、删除属性、界面不会更新。
  • 直接通过下标修改数组,界面不会自动更新。vue2响应式缺点

Vue3.0的响应式

实现原理:

  • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
    <p v-show="info.sex">性别:{{ info.sex }}</p>
    <p>{{ info.hobby }}</p>
    
    import { reactive } from 'vue'
    export default {
	name: "App",
	setup() {		
            let info = reactive({
                job: "前端开发",
                salary: "30k",
                hobby: ["学习", "唱歌", "跳舞"],
            });
            function addSex() {
                info.sex = "女";
                info.hobby[1] = "做饭";
            }
            return {                 
                addSex,
            };
	},
    }
  • 通过Reflect(反射):对被代理对象(源对象)的属性进行操作。

模拟Vue3中实现响应式

    let person = {
        name:'张三',
        age:18
    }
    const p = new Proxy(person,{
        // 有人读取p的某个属性时调用
        get(target, propName){
            // return target[propName]
            // 用 . 的话说明直接在person中找propName属性
            // 而propName是动态的值,是变量
            return Reflect.get(target,propName)
        },
        // 有人修改或追加属性时调用
        set(target, propName, value){
            // target[propName] = value
            return Reflect.set(target,propName,value)
        },
        // 删除p的某个属性时调用
        deleteProperty(target, propName){
            // return delete target[propName]
            return Reflect.deleteProperty(target,propName)
        },       
    })
  • MDN文档中描述的Proxy与Reflect: Proxy Reflect

5. reactive对比ref

  • 从定义数据角度对比:

    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
  • 从原理角度对比:

    • ref通过Object.definProperty()的get和set来实现响应式(数据拦截)。
    • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:

    • ref定义的数据:操作数据需要 .value ,读取数据时模版中直接读取不需要 .value。
    • reactive定义的数据,操作数据与读取数据:均不需要 .value.

6. setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接受了的属性。

    • context:上下文对象。

      • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs。

      • slots:收到的插槽内容,相当于this.$slots。

      • emit:分发自定义事件的函数,相当于this.$emit。(在子组件中添加一个属性:emits:['hello'],用于通知父组件:我收到了你自定义的属性,否则控制台会有一个警告)

Vue3学习笔记(尚硅谷)

Vue3学习笔记(尚硅谷)

7. 计算属性与监视

computed函数

  • 与Vue2.0中computed配置功能一致
  • 写法
    import { computed, reactive } from 'vue'
    
    setup(){
        let person = reactive({
            firstName:'张',
            lastName:'三'
        })
        
        // 计算属性简写---只读
        let fullName = computed( ()=> {
            return person.firstName + '-' +person.lastName
        })
        
        // 计算属性完整---可读可改
        let fullName = computed({
            get(){
                return person.firstName + '-' +person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })     
    }

watch函数

  • 与Vue2.0中watch配置功能一致
  • 两个‘小坑’:
    • 监视reactive定义的响应式数据时,oldValue无法正确获取,强制开启了深度监视(deep配置失效)
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效
    import { watch,ref,reactive } from 'vue' 
    setup(){
        let sum = ref(0)
        let msg = ref('你好')
        let person = reactive({
            name:'张三',
            age:18,
            job:{
                j1:{
                    salary:20
                }               
            }
        })
        
        // 情况一:监视ref所定义的一个响应式数据
        watch(sum, (newValue, oldValue) => {
            console.log('sum变化了',newValue, oldValue)
        // 配置项写在watch的第三个参数中,但deep配置无效
        },{immediate: true, deep: true})
        
        // 情况二:监视ref所定义的多个响应式数据
        watch([sum, msg], (newValue, oldValue) => {
            console.log('sum或msg变化了',newValue, oldValue)
        })
        
        // 情况三:监视reactive所定义的一个响应式数据的全部属性
        // 1.注意:此处无法正确的获取oldValue
        // 2.注意:强制开启了深度监视(deep配置无效)
        watch(person, (newValue, oldValue) => {
            console.log('person变化了',newValue, oldValue)
        })
        
        // 情况四:监视reactive所定义的一个响应式数据中的某个属性
        watch( () => person.age, (newValue, oldValue) => {
            console.log('person的age变化了',newValue, oldValue)
        })
        
        // 情况五:监视reactive所定义的一个响应式数据中的某些属性
        watch([ () => person.name, () => person.age], (newValue, oldValue) => {
            console.log('某些属性变化了',newValue, oldValue)
        })
        
        // 特殊情况
        watch(() => person.job,(newValue, oldValue) =>{
            console.log(newValue, oldValue)
        },{deep: true})
        
        return {
            sum,
            msg
        }
    }

情况二:
Vue3学习笔记(尚硅谷)

情况三:
Vue3学习笔记(尚硅谷)

watch中的value问题
Vue3学习笔记(尚硅谷)
第一个sum是基本数据类型,第二个也可以写成情况情况三,加一个deep:true

watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。
  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性。
  • watchEffect有点像computed:
    • 但computed注重的是计算出来的值(回调函数的返回值),所以必须写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    import { watchEffect} from 'vue'
    
    // watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
    setup(){
        watchEffect(()=>{
            const x1 = sum.value
            console.log('watchEffect配置的回调执行了') //用到sum监听sum
        })
    }

8. 生命周期

Vue2生命周期图示

Vue3生命周期图示

Vue3中可以继续使用Vue2中的生命周期钩子,但有两个被更名:

  • beforeDestroy改名为beforeUnmount
  • destroy改名为unmounted

Vue3也提供了组合式API形式的生命周期钩子,与Vue2中钩子对应关系如下:

  • beforeCreate ===> setup()
  • created ===> setup()
  • beforeMount ===> onBeforeMount
  • mounted ===> onMounted
  • beforeUpdate ===> onBeforeUpdate
  • updated ===> onUpdated
  • beforeUnmount ===> onBeforeUnmount
  • unmounted ===> onUnmounted
    import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
    
    setup(){
        let sum = ref(20)
        // 通过组合式API的形式去使用生命周期钩子
        onBeforeMount(()=>{
            console.log('---onBeforeMount---')
        })
        
        onMounted(()=>{
            console.log('---onMounted---')
        })
        
        onBeforeUpdate(()=>{
            console.log('---onBeforeUpdate---')
        })
        …………        
    }

9. 自定义hook函数

  • 什么是hook? —— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
  • 类似于vue2.x中mixin。
  • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂。

创建一个js文件,比如usePoint.js ,内容:

    import {reactive, onMounted, onBeforeUnmount} from 'vue'
    export default function(){
        let point = reactive({
            x:0,
            y:0
        })
        // 获取鼠标点击坐标
        function savePoint(event){
            point.x = event.pageX
            point.y = event.pageY
        }
        onMounted(()=>{
            window.addEventListener('click', savePoint)    
        })        
        onBeforeUnmount(()=>{
            window.removeEventListener('click', savePoint)
        })          
        return point
    }

在vue文件中引入userPoint.js

    <p>鼠标点击坐标 x:{{point.x}}  y:{{point.y}}<p>
    
    import usePoint from '/hook/usePoint.js' 
    
    setup(){
        let point = usePoint()
        
        return { point }
    }

10. toRef

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性。
  • 语法:const name = toRef(person, 'name')
    return {
        name: toRef(person,'name')
        salary: toRef(person.job.j1, 'salary')
    }
  • 应用:要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefs 与 toRef 功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
    // person中的salary属性用法
    <p> {{ job.j1.salary }} </p>
    return {
        // 把person属性都拆开
        ...toRefs(person)
    }

三、其它Composition API

1. shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
  • 什么时候使用?
    • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化 ===> shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef
    import { shallowReactive} from 'vue'
    
    setup(){
        let person = shallowReactive({ //只考虑第一层数据的响应式
            name:'张三',
            job:{
                j1:'软件工程师'
            }
        })
    }

2. readonly 与 shallowReadonly

  • readonly:让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景:不希望数据被修改时。

3. toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRow:
    • 作用:标记一个对象,使其永远不会再成为响应式。
    • 应用场景:
        1. 有些值不应被设置为响应式,例如复杂的第三方类库等。
        1. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4. customRef

  • 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。
  • 官网示例: 创建一个防抖 ref,只在最近一次set调用后的一段固定间隔后再调用

5. provide 与 inject

  • 作用:实现祖与后代组件间通信
  • 套路:父组件中有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据
  • 具体写法:

祖组件中:

    setup(){
        let car = reactive({name:'奔驰', price:'40万'})
        provide('car', car)
    }

孙组件(后代组件)中:

    setup(){
        const car = inject('car')
        return {
            car
        }
    }

6. 响应式数据的判断

  • isRef:检查一个值是否为一个ref对象
  • isReactive:检查一个对象是否是由reactive创建的响应式代理
  • isReadonly:检查一个对象是否是由readonly创建的只读代理
  • isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理

四、 Composition API的优势

1. Options API存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。

2. Composition API的优势

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

五、 新的组件

1. Fragment

  • 在vue2中:组件必须有一个根标签
  • 在vue3中:组件可以没有根标签,内部会将分多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存占用

2. Teleport

  • 什么是Teleport ? —— Teleport是一种能够将我们的组件html结构移动到指定位置的技术。
// to可以取值:html、body、#atguigu、.box
<teleport to='移动位置'>
    <div v-if='isShow'>
        <div>
            <h3>我是一个弹窗</h3>
            <button @click='isShow = false'>关闭弹窗</button>
        </div>
    </div>
</teleport>

3. Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
  • 使用步骤:

异步引入组件

import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

export default{
    components:{Child}
}

使用Suspense包裹组件,并配置好default与fallback

Vue3学习笔记(尚硅谷)

六、其他

1. 全局API的转移

  • Vue2有许多全局API和配置
    • 例如:注册全局组件、注册全局指令等
    // 注册全局组件
    Vue.component('MyButton',{
        data:() => ({
            count:0
        }),    
    })
    
    // 注册全局指令
    Vue.directive('focus',{
        inserted: el => el.focus()
    })
    
  • Vue3.0中对这些API做出调整:
    • 将全局API,即:Vue.xxx调整到应用实例(app)上 Vue3学习笔记(尚硅谷)

2. 其他改变

  • data选项应始终被声明为一个函数

  • 过渡类名的更改

    Vue3学习笔记(尚硅谷)
  • 移除keyCode作为v-on的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件
    • 子组件中声明自定义事件
    Vue3学习笔记(尚硅谷)
  • 移除过滤器(filter)

    过滤器虽然看起来很方便,但它需要一个自定义语法,打破大括号内表达式是“只是JavaScript”的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器