vue3的基本使用

1、Vue3简介

 

1.1、Vue3带来了什么

1.性能的提升

Vue3与Vue 2相比,在包大小(使用 tree-shaking 时减轻多达 41%)、初始渲染(速度提高多达 55%)、更新(多达 133% 更快)和内存使用(最多减少 54%)。

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

    ......

2.源码的升级

  • 使用Proxy代替defineProperty实现响应式

  • 重写虚拟DOM的实现和Tree-Shaking

    ......

3.拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4.新的特性

  1. Composition API(组合API)

    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
    • ......
  2. 新的内置组件

    • Fragment
    • Teleport
    • Suspense
  3. 其他改变

    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符
    • ......

具体可查看:https://github.com/vuejs/core/releases?q=3.0.0&expanded=true

 

2、创建vue3工程

2.1、使用 vue-cli 创建工程

从 vue-cli 的 v4.5.0 版本开始,vue-cli 提供了内置选项,可在创建新项目时选择 Vue 3。所以通过 vue-cli 创建 vue3项目需要保证 vue-cli 版本 >= v4.5.0。

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 当版本过低时,安装或者升级你的@vue/cli
npm install -g @vue/cli

在更新 vue-cli 时,如果遇到以下错误:

则可能是 node.js 版本太低的原因,先更新 node.js 版本,比如升级至 v12.11.0 版本,然后卸载 vue-cli ,再重新安装即可。

 

创建 vue3 项目,命令如下,当遇到选择 vue3 还是 vue2 时,选择 vue3 即可。

## 创建
vue create vue3_test
## 启动项目
cd vue3_test
npm run serve  

可以看到运行结果如下,跟 vue2 没有什么差别:

 

2.2、使用 vite 创建工程

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
  • 传统构建 与 vite构建对比图

   

 

创建工程命令如下:

## 创建工程
npm init vite-app vue3_test_vite
## 进入工程目录
cd vue3_test_vite
## 安装依赖
npm install
## 运行
npm run dev

运行效果如下:

 

3、组合式API(Composition API)

3.1、composition和options的对比

options API 开发出来的vue应用如左图所示,它的特点是理解容易,因为各个选项都有固定的书写位置,比如响应式数据就写到data选择中,操作方法就写到methods配置项中等,应用大了之后,相信大家都遇到过来回上下找代码的困境

composition API开发的vue应用如右图所示,它的特点是特定功能相关的所有东西都放到一起维护,比如功能A相关的响应式数据,操作数据的方法等放到一起,这样不管应用多大,都可以快读定位到某个功能的所有相关代码,维护方便,设置如果功能复杂,代码量大,我们还可以进行逻辑拆分处理

  • 特别注意:
  1. 选项式api和组合式api俩种风格是并存的关系 并不是非此即彼
  2. 需要大量的逻辑组合的场景,可以使用compition API进行增强

 

4、组合式api之setup 组件选项

setup() 函数是vue3中专门新增的方法,可以理解为Composition Api的入口。

setup 函数的两种返回值:

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

使用示例:

<template>
    <h1>一个人的信息</h1>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h3>工作种类:{{job.name}}</h3>
    <h3>工作薪水:{{job.salary}}</h3>
    <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {ref} from 'vue'
export default {
    name: "App",
    setup(props) {
        //数据
        let sex = ""  // 直接赋值的变量并不具备响应式功能
        let name = ref("张三");     //需通过ref定义响应式数据
        let age = ref(18);
        let job = ref({
            name: "前端工程师",
            salary: "30K",
        });

        //方法
        function changeInfo() {
            console.log(sex, name, age)
            sex = ""  // 修改sex的值页面无法得到实时更新,sex不是响应式的
            name.value = '李四'
            age.value = 48

            console.log(job.value);
            job.value.name = 'UI设计师'
            job.value.salary = '60K'
        }

        //返回一个对象(常用)
        return {
            name,
            age,
            job,
            changeInfo,
        };
    },
};
</script>

效果如下:

 

注意:

  1. setup() 虽然可以和 vue2 中的一些配置(如data、methods等)共存,但尽量不要与Vue2.x配置一起使用。当 setup() 和 Vue2 中的配置(data、methos、computed...)同时存在时,在vue2 中的配置项可以访问到setup中的属性、方法。但在setup中不能访问到Vue2的配置(data、methos、computed...)。并且如果两者之间有同名的属性值,则页面中优先使用 setup 中的属性值。
  2. setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

 

setup 函数是一个新的组件选项,作为组件中组合式API 的起点(入口)。该函数有以下特性:

  1. setup() 函数是在 props 解析之后,beforeCreate() 执行之前执行的,并且 setup函数只会在组件初始化的时候执行一次。
  2. setup 的调用发生在 data 属性、computed 属性、 methods 等等属性被解析之前,所以它们无法在 setup 中被获取。
  3. 在 setup() 中你应该避免使用 this,因为它不会找到组件实例,在 setup() 中 this 指向的是 undefined。

 

4.1、setup() 函数的参数

setup() 函数有两个参数:props 和 context。

  • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
  • context:上下文对象。context 是一个普通的 JavaScript 对象,它具有以下三个属性:
  1. attrs:值为对象,包含了组件外部传递过来但没有在 props 配置中声明的属性,相当于 vue2 中的 this.$attrs
  2. slots:组件收到的插槽内容,相当于vue2 中的 this.$slots
  3. emit:用于触发自定义事件的函数,相当于vue2 中的 this.$emit

 

示例:

子组件 Demo 定义如下:

<template>
    <button @click="test">测试触发一下Demo组件的Hello事件</button>
</template>

<script>
    export default {
        name: 'Demo',
        props:['msg1'],
        emits:['hello'],
        setup(props,context){
            console.log('props:',props)
            console.log('context:',context)
            console.log('context.attrs:',context.attrs) //相当与Vue2中的$attrs
            console.log('context.emit:',context.emit) //触发自定义事件的。
            console.log('context.slots:',context.slots) //插槽
            //方法
            function test(){
                context.emit('hello',666)
            }

            //返回一个对象(常用)
            return {
                test
            }
        }
    }
</script>

父组件如下:

<template>
    <Demo @hello="showHelloMsg" msg1="信息1" msg2="信息2" >
        <template v-slot:slot01>
            <span>插槽1</span>
        </template>
        <template v-slot:slot02>
            <span>插槽2</span>
        </template>
    </Demo>
</template>

<script>
import Demo from "./components/Demo";

export default {
    name: "App",
    components: { Demo },
    setup() {
        function showHelloMsg(value) {
            alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`);
        }
        return {
            showHelloMsg,
        };
    },
};
</script>

执行完成后,可以看到子组件的输出如下:

 

可以看到,子组件的 setup() 函数中,输出的参数 props 只包含了在子组件中已通过 props 配置项接收了的属性值,而 context.attrs 只包含了未通过 props 配置接收的属性值;context.emit 相当于 this.$emit,可用于触发给子组件绑定了的自定义事件;context.slots 可获取插槽内容。

 

5、组合式api之ref() 函数(返回 RefImpl 对象)

ref() 函数接受一个简单类型或者复杂类型的传入,并返回一个响应式且可变的 ref 实例对象(reference对象,简称ref对象)。当 ref 接收对象(或数组)类型数据时,实际上它的内部会自动通过 reactive 来监听这些对象,实际上相当于使用了 reactive() 来监听这些对象。

使用步骤:

  1. 从vue框架中导入ref函数
  2. 在setup函数中调用ref函数并传入数据(简单类型或者复杂类型),把ref函数的返回值以对象的形式返回
  3. 注意:在setup函数中使用ref结果,需要通过.value 访问,模板中使用不需要加.value

示例如下:

<template>
  <div>{{ money }}</div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    let money = ref(100)
    let obj = ref({
        name: 'wen'
    })
    console.log(11, money)
    console.log(22, obj)
    return {
      money
    }
  }
}
</script>

输出如下:

ref() 函数接收的数据可以是基本类型和对象类型。基本类型数据的响应式依然是靠Object.defineProperty()getset完成的;而对象类型的数据在内部通过Vue3.0中的一个新函数 reactive函数来实现响应式。

使用 ref() 函数时,修改返回的响应式对象需要通过 value 属性值进行修改,如:xxx.value = 'xxx'。但是在页面模板中读取数据时不需要 value,直接使用即可,如:<div>{{xxx}}</div>。

 

6、组合式api之reactive() 函数(返回 proxy 对象)

reactive 函数接收一个普通的对象传入,把对象数据转化为响应式对象并返回。

使用步骤:

  1. 从vue框架中导入reactive函数
  2. 在setup函数中调用reactive函数并将对象数据传入,把reactive函数调用完毕之后的返回值以对象的形式返回

示例:

<template>
  <div>{{ state.name }}</div>
  <div>{{ state.age }}</div>
  <button @click="state.name = 'pink'">改值</button>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup () {
    const state = reactive({
      name: 'cp',
      age: 18
    })
console.log(11, state)
function changeInfo(){ state.name = '李四' state.age = 48 } return { state, changeInfo } } } </script>

输出如下:

reactive 不能接收基本类型作为参数。reactive 返回一个代理对象(Proxy的实例对象,简称proxy对象),reactive定义的响应式数据是“深层次的”,内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

 

6.1、reactive 和 ref 的对比

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据、对象类型。(ref 定义对象(或数组)类型数据时,它的内部会自动通过reactive转为代理对象。)
    • reactive用来定义:对象(或数组)类型数据
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

 

7、组合式api之 computed() 函数

computed() 函数与Vue2.x中 computed 配置项功能基本一致。computed() 函数也可以像 computed 配置项一样设置它的 setter。

使用示例:

<template>
    <span>全名:{{person.fullName}}</span>
    全名:<input type="text" v-model="person.fullName">
</template>

<script>
    import {reactive,computed} from 'vue'
    export default {
        name: 'Demo',
        setup(){
            //数据
            let person = reactive({
                firstName:'',
                lastName:''
            })
            //计算属性——简写(没有考虑计算属性被修改的情况)
            /* person.fullName = computed(()=>{
                return person.firstName + '-' + person.lastName
            }) */

            //计算属性——完整写法(考虑读和写)
            person.fullName = computed({
                get(){
                    return person.firstName + '-' + person.lastName
                },
                set(value){
                    const nameArr = value.split('-')
                    person.firstName = nameArr[0]
                    person.lastName = nameArr[1]
                }
            })
return { person } } } </script>

 

8、组合式api之 watch() 函数

watch() 函数与Vue2.x中 watch 配置项功能基本一致。

使用方法:首先需在 vue 中引入 watch,然后在 setup 中直接通过 watch() 函数监听响应式数据。

语法如下:

// 第一个参数是监视的属性名,可以监听单个属性,也可以一次性监听多个属性,此时该参数为数组。第二个参数为监视回调。第三个参数可选,可配置监视的一些配置,比如immediate、deep,类似于vue2的使用
watch(监视的属性名, ()=> {
    ...
}, {}),

 

8.1、watch() 监听 ref 实例对象

当 watch 监听 ref 实例对象 refImpl 时,实际上监听的是实例对象的 value 属性值。

使用示例如下:

setup(){
    //数据
    let sum = ref(0)
    let msg = ref('你好啊')//监视ref所定义的一个响应式数据
    /* watch(sum,(newValue,oldValue)=>{
        console.log('sum变了',newValue,oldValue)
    },{immediate:true}) */

    //可一次性监视ref所定义的多个响应式数据。当然,也可以直接调用多个watch函数也行
    watch([sum,msg],(newValue,oldValue)=>{
        console.log('sum或msg变了',newValue,oldValue)
    },{immediate:true})

    //返回一个对象(常用)
    return {
        sum,
        msg
    }
}

 

当 ref() 函数参数为对象时,此时该实例对象的 value 值为一个 proxy 代理对象,所以如果此时监听该实例对象时,实际上只有当整个实例对象发生改变时监听回调函数才会触发。如下:

<template>
    <h2>年龄:{{person.age}}</h2>
    <!-- 此时点击按钮并不会触发监听的回调函数 -->
    <button @click="person.age++">修改person.age的值</button>
</template>

<script>
    import {reactive, ref, watch} from 'vue'
    export default {
        name: 'Demo',
        props:['msg1'],
        emits:['hello'],
        setup(props,context){
            let person = ref({
                name: 'aa',
                age: 12
            })

            watch(person, (newValue,oldValue)=>{
                console.log('person发生了改变', newValue, oldValue)
            })

            return {
                person
            }
        }
    }
</script>

此时点击按钮修改 person.age 的值并不会触发监听的回调函数,只有当给 person 重新赋值时才会触发监听的回调函数。

如果我们想要监听 person 下的属性值的改变时,此时可以将 watch 函数的参数改为 person.value,即监听 proxy 代理对象,或者在 watch 函数的第三个参数添加 deep 参数。如下:

<template>
    <h2>年龄:{{person.age}}</h2>
    <!-- 此时点击按钮并不会触发监听的回调函数 -->
    <button @click="person.age++">修改person.age的值</button>
</template>

<script>
    import {reactive, ref, watch} from 'vue'
    export default {
        name: 'Demo',
        props:['msg1'],
        emits:['hello'],
        setup(props,context){
            let person = ref({
                name: 'aa',
                age: 12
            })

            //直接监听 value 值
            watch(person.value, (newValue,oldValue)=>{
                console.log('person发生了改变', newValue, oldValue)
            })

            // 添加deep配置
            watch(person, (newValue,oldValue)=>{
                console.log('person发生了改变', newValue, oldValue)
            },{
                deep: true
            })

            return {
                person
            }
        }
    }
</script>            

 

 8.2、watch 监听 reactive  响应式对象

通过 watch() 函数可以监听 reactive() 返回的 proxy 响应式对象。

需要注意,如果监听的是一个对象的话,监听的回调函数无法正确获取到 oldvalue 的值,输出 oldvalue 会得到跟 newvalue 相同的值。

  • 如果监听的是 reactive 返回的整个对象的话,此时会默认开启深度监听,并且无法关闭深度监听,另外还有点小问题,就是此时无法正确获取 oldvalue 的值,输出 oldvalue 会得到跟 newvalue 相同的值。
    • 需要注意的是,如果监听的是 ref() 函数以对象作为参数所返回的响应式数据,也同样有上述问题。
  • 如果想要监听 reactive 返回的对象的某个属性值的话,此时 watch() 函数的第一个参数应该是一个函数,并且返回值为需要监听的对象的属性。但此时并不会自动开启深度监听,也就是说,假如我们监听的这个属性值是对象的话,修改这个对象的属性是不会触发监听回调函数的。
    • 如果监听的这个 reactive 返回的对象的属性是字符串的话,则此时能正确获取到 oldvalue 的值
    • 如果监听的这个 reactive 返回的对象的属性是对象的话,此时就无法正确获取到 oldvalue 的值
setup(){
    //数据
    let sum = ref(0)
    let msg = ref('你好啊')
    let person = reactive({
        name:'张三',
        age:18,
        job:{
            j1:{
                salary:20
            }
        }
    })

    /* 
        监视reactive所定义的一个响应式数据的全部属性
           1.注意:此处无法正确的获取oldValue,输出的oldvalue的值总是等于newvalue的值
           2.注意:此时会强制开启深度监视(并且无法通过deep配置关闭深度监听)
    */
    watch(person,(newValue,oldValue)=>{
        console.log('person变化了',newValue,oldValue)
    },{deep:false}) //此处的deep配置无效

    //可以直接监视reactive所定义的一个响应式数据中的某个属性。此时可以正确获取oldvalue值的情况
    watch(()=>person.name,(newValue,oldValue)=>{
        console.log('person的name变化了',newValue,oldValue)
    })  

    //一次性监视多个reactive返回的响应式对象的属性
    watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
        console.log('person的name或age变化了',newValue,oldValue)
    })  

    //特殊情况,监听的属性值是一个对象,此时也无法获取oldvalue的值
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效 */


    //返回一个对象(常用)
    return {
        sum,
        msg,
        person
    }
}

 

9、组合式api之 watchEffect() 函数

watchEffect() 函数不用指明监视哪个属性,监视的回调中用到哪写属性,那就会监视哪些属性,当这些属性值发生改变时,回调函数就会执行。

watchEffect有点像computed,但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值,而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

示例如下:

 

export default {
    name: 'Demo',
    setup(){
        //数据
        let sum = ref(0)
        let msg = ref('你好啊')
        let person = reactive({
            name:'张三',
            age:18,
            job:{
                j1:{
                    salary:20
                }
            }
        })

        watchEffect(()=>{
            const x1 = sum.value
            const x2 = person.job.j1.salary
            console.log('watchEffect所指定的回调执行了')
        })

        //返回一个对象(常用)
        return {
            sum,
            msg,
            person
        }
    }
}

则只要 sum 或者 person.job.j1.salary 的值发生改变,watchEffect() 函数就会执行。

 

10、vue3的生命周期

Vue3 和 Vue2 中的生命周期钩子的工作方式类似,名称也差不多,只是有两个钩子函数的名称改了而已,实际上他们的触发时机都是一样的。

vue3 的生命周期如下:

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeupdate
  6. updated
  7. beforeUnmount(相当于vue2中的beforeDestroy)
  8. unmounted(相当于vue2中的destroyed)

 

 

使用示例:

export default {
    name: 'Demo',
    
    setup(){
        console.log('---setup---')
        //数据
        let sum = ref(0)

        //返回一个对象(常用)
        return {sum}
    },
    //通过配置项的形式使用生命周期钩子
    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---')
    }
}

 

10.1、组合式api形式的生命周期钩子

Vue3.0 中提供了 Composition API 形式的生命周期钩子,你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

  • beforeCreate===> setup()
  • created =====> setup()
  • beforeMount ===> onBeforeMount
  • mounted =====> onMounted
  • beforeUpdate===> onBeforeUpdate
  • updated  ====>onUpdated
  • beforeUnmount ==>onBeforeUnmount
  • unmounted =====>onUnmounted

因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义这两个钩子。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

使用示例:

import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'

export default {
    name: 'Demo',
    
    setup(){
        console.log('---setup---')
        //数据
        let sum = ref(0)

        //通过组合式API的形式去使用生命周期钩子
        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 {sum}
    }
}

实际上,组合式 api 形式的生命周期钩子和配置项形式的钩子可以同时存在,当同时存在时,组合式 api 形式的执行要先于对应的相同的配置项的钩子。但我们不建议同时使用组合式 api 和配置项的钩子,没什么必要。

 

11、Composition API 的优势

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。使用 Composition API 我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

 

posted @ 2022-05-29 17:21  wenxuehai  阅读(1878)  评论(0编辑  收藏  举报
//右下角添加目录