兵哥vue3

一、Vue3启程

1. 初始Vue3

<div id="app">

    <h2>姓名:{{name}}</h2>

    <h2>年龄:{{age}}</h2>

    <button @click="updateData">修改数据</button>

</div>

// Vue2中--创建实例的方式

new Vue({

    //指定挂载容器

    // el:'#app',

    //定义属性

    data() {

        return {

            name:'张三',

            age:20

        }

    },

    //定义方法

    methods: {

        updateData(){

            this.name = '李四'

            this.age = 25

        }

    },

}).$mount('#app')  //指定当前vue实例挂载的容器

// Vue3中--创建实例的方式

Vue.createApp({

    //注意:这个配置对象里面除了不能写el选项,之前怎么写,现在还可以怎么写

    //定义属性

    data() {

        return {

            name:'张三',

            age:20

        }

    },

    //定义方法

    methods: {

        updateData(){

            this.name = '李四'

            this.age = 25

        }

    },

}).mount('#app')   //只能通过mount方法指定挂载的容器,不用通过el选项指定

2. Vue2和Vue3的响应式

<div id="app">

    <h2>学生:{{stu}}</h2>

    <h2>食物:{{foods}}</h2>

    <div>

        <button @click="updateStuName">修改学生姓名</button>

        <button @click="addStuSex">添加学生性别</button>

        <button @click="delStuAge">删除学生年龄</button>

        <button @click="updateFoods2">修改第二个食物</button>

    </div>

</div>

// Vue2

new Vue({

    data() {

        return {

            //学生对象

            stu:{

                name:'张三',

                age:20

            },

            //食物数组

            foods:['榴莲','葡萄','香蕉']

        }

    },

    methods: {

        updateStuName(){

            this.stu.name = '李四'

        },

        addStuSex(){

            // 直接给对象添加的属性,不具备响应式

            // this.stu.sex = '男'

            // 如果要给对象添加属性,并且添加的属性也要具备响应式,要使用$set方法

            // 方法的第一个参数是指定的对象,第二个参数是属性名,第三个参数是属性值。

            this.$set(this.stu,'sex','男')

        },

        delStuAge(){

            // 直接删除对象身上的属性,是不具备响应式的

            // delete this.stu.age

            // 如果要删除对象身上的属性,并且还要具备响应式,要使用$delete方法

            // 方法的第一个参数是指定的对象,第二个参数是属性名

            this.$delete(this.stu,'age')

        },

        updateFoods2(){

            // 直接根据索引修改数组元素,不具备响应式

            // this.foods[1] = '西瓜'

            // 操作数组中的元素,并且还要具备响应式,只能使用数组的以下方法:

            // push unshift pop shift splice reverse sort

            // this.foods.splice(1,1,'西瓜')

            // 如果就是想通过下标去操作数组,还要具备响应式,使用$set方法

            this.$set(this.foods,1,'西瓜')

        }

    },

}).$mount('#app')

// 总结Vue2的响应式:不能直接给对象添加属性,删除对象的属性,不能直接操作数组的下标,

// 但是,Vue2同时也提供了解决这些问题的方案。

// Vue3

Vue.createApp({

    data() {

        return {

            //学生对象

            stu:{

                name:'张三',

                age:20

            },

            //食物数组

            foods:['榴莲','葡萄','香蕉']

        }

    },

    methods: {

        updateStuName(){

            this.stu.name = '李四'

        },

        addStuSex(){

            // 在Vue3中,直接给对象添加属性,新的属性依然具备响应式

            this.stu.sex = '男'

        },

        delStuAge(){

            // 在Vue3中,直接删除对象的属性,依然具备响应式

            delete this.stu.age

        },

        updateFoods2(){

            // 在Vue3中,根据下标操作数组,依然具备响应式

            this.foods[1] = '西瓜'

        }

    },

}).mount('#app')

// 总结Vue3的响应式:解决了再Vue2中的所有问题。

3. Vue2和Vue3的响应式原理

<h2 id="name"></h2>

<h2 id="age"></h2>

// Vue2的响应式原理:

// 这里的obj是源对象

let obj = {

    name:'张三',

    age:20

}

// 在页面中显示姓名和年龄

document.getElementById('name').innerText = obj.name

document.getElementById('age').innerText = obj.age

// 这里的obj2代理对象---由obj2代理obj

let obj2 = {}

// 给obj2定义name属性

Object.defineProperty(obj2,'name',{

    get(){

        return obj.name

    },

    set(value){

        obj.name = value

        document.getElementById('name').innerText = obj.name

    }

})

// 给obj2定义age属性

Object.defineProperty(obj2,'age',{

    get(){

        return obj.age

    },

    set(value){

        obj.age = value

        document.getElementById('age').innerText = obj.age

    }

})

// Vue3的响应式原理:

// 这里的obj是源对象

let obj = {

    name:'张三',

    age:20

}

// 在页面中显示姓名和年龄

document.getElementById('name').innerText = obj.name

document.getElementById('age').innerText = obj.age

// 这里的obj2代理对象---由obj2代理obj

// new Proxy(源对象,{...})的方式,创建代理对象

let obj2 = new Proxy(obj,{

    //读取属性,参数分别是:源对象,属性名

    get(target, property){

        // 直接根据源对象返回源对象身上的属性

        // return target[property]

        // 通过发射对象,发射输出源对象身上的属性

        return Reflect.get(target,property)

    },

    //设置属性,参数分别是:源对象,属性名,属性值

    set(target, property,value){

        // target[property] = value

        if(Reflect.has(target,property)){

            Reflect.set(target, property,value)

            document.getElementById(`${property}`).innerText = value

        }

    },

    //删除属性,参数分别是:源对象,属性名

    deleteProperty(target, property){

        // delete target[property]

        Reflect.deleteProperty(target, property)

    }

})

4. 引出Vue3新推出的组合式API

<div id="app">

    <div>

        <h2>学生信息</h2>

        <!-- 注意:ref对象在模板只不需要.value的方式获取里面的值 -->

        <h4>姓名:{{stuName}}</h4>

        <h4>年龄:{{stuAge}}</h4>

        <button @click="updateStu">修改学生信息</button>

    </div>

    <div>

        <h2>汽车信息</h2>

        <h4>车名:{{carName}}</h4>

        <h4>车价:{{carPrice}}</h4>

        <button @click="updateCar">修改汽车信息</button>

    </div>

    <div>

        <h2>手机信息</h2>

        <h4>名称:{{phoneName}}</h4>

        <h4>颜色:{{phoneColor}}</h4>

        <button @click="updatePhone">修改手机信息</button>

    </div>

    <div>

        <h2>食物信息</h2>

        <h4>名称:{{foodName}}</h4>

        <h4>价格:{{foodPrice}}</h4>

        <button @click="updateFood">修改食物信息</button>

    </div>

</div>

// 什么是组合式API(Composition API),就是Vue推出的一些新的方法,这个方法在setup中使用

// 从Vue身上获取ref组合式API函数

let {ref} = Vue

Vue.createApp({

    // 注意:Vue2中,Vue实例的data选项可以是一个对象,也可以是一个方法,由方法返回一个对象

    // 但是,组件中data选项必须是一个方法。

    // Vue3中,无论是Vue实例,还是组件,data选项都必须是一个方法。

    // 我们之前习惯将所有的数据放在data选项中定义,所有的方法放在methods选项中定义,

    // 所有的计算属性放在computed选项中定义,所有的侦听器放在watch选项中定义,

    // 这样就会导致一个业务的代码会拆分到多个结构中去写,如果一个页面中要操作很多个业务,代码后期维护成本会很高。

    // 所以,Vue3引入了组合式API,简化之前繁琐的过程,将相同业务的代码靠在一起写。

    /* data: function () {

        return {

            //定义学生数据

            stuName: '张三',

            stuAge: '20',

            //汽车信息

            carName: '奔驰',

            carPrice: '50W',

            //手机信息

            phoneName: 'iphone',

            phoneColor: '白色',

            //食物信息

            foodName: '汉堡',

            foodPrice: '¥20'

        }

    },

    methods: {

        //修改学生的方法

        updateStu(){

            this.stuName = '李四'

            this.stuAge = 30

        },

        //修改汽车的方法

        updateCar(){

            this.carName = '宝马'

            this.carPrice = '40W'

        },

        //修改手机的方法

        updatePhone(){

            this.phoneName = '华为'

            this.phoneColor = '蓝色'

        },

        updateFood(){

            this.foodName = '蛋糕'

            this.foodPrice = '¥30'

        }

    }, */

    // setup方法是所有组合式API的入口

    setup() {

        // 定义学生的信息

        // 在setup中,直接定义的数据是不具备响应式的,

        // 如果要使数据具备响应式,需要使用ref组合式API对数据进行包装,包装后返回的是ref对象

        let stuName = ref('张三')

        let stuAge = ref('20')

        let updateStu = () => {

            //ref对象的value属性保存的是值

            stuName.value = '李四'

            stuAge.value = 30

        }

        // 定义汽车的信息

        let carName = ref('奔驰')

        let carPrice = ref('50W')

        let updateCar = () => {

            carName.value = '宝马'

            carPrice.value = '40W'

        }

        // 定义手机的信息

        let phoneName = ref('iphone')

        let phoneColor = ref('白色')

        let updatePhone = () => {

            phoneName.value = '华为'

            phoneColor.value = '蓝色'

        }

        // 定义食物的信息

        let foodName = ref('汉堡')

        let foodPrice = ref('¥20')

        let updateFood = () => {

            foodName.value = '蛋糕'

            foodPrice.value = '¥30'

        }

        //返回模板中需要使用的数据

        return{

            stuName,

            stuAge,

            updateStu,

            carName,

            carPrice,

            updateCar,

            phoneName,

            phoneColor,

            updatePhone,

            foodName,

            foodPrice,

            updateFood

        }

    }

}).mount('#app')

5. ref和reactive

<div id="app">

    <h4>姓名:{{name}}</h4>

    <h4>学生:{{stu}}</h4>

    <button @click="updateName">修改姓名</button>

    <button @click="updateStu">修改学生</button>

</div>

let {ref,reactive} = Vue

Vue.createApp({

    setup() {

        let name = ref('张三')

        let updateName = ()=>{

            name.value = '张杰'

        }

        /* let stu = ref({

            name:'李四',

            age:20

        })

        let updateStu = ()=>{

            // 注意:修改ref对象的值,每次都要先点value

            stu.value.name = '李明'

            stu.value.age = 30

        } */

        // reactive组合式API方法,根据源对象返回一个代理对象(Proxy对象)

        let stu = reactive({

            name:'李四',

            age:20

        })

        let updateStu = ()=>{

            // Proxy对象,不需要先点value

            stu.name = '李明'

            stu.age = 30

        }

        return {

            name,

            updateName,

            stu,

            updateStu

        }

    }

}).mount('#app')

二、脚手架

1. Vue-Cli

Vue CLI 4.x以上,Node.js版本 8.9以上

npm install -g @vue/cli

# OR

yarn global add @vue/cli

# 查看版本

vue --version

# 创建项目

vue create hello-world

# 运行

npm run serve

main.js

// vue2

/* import Vue from 'vue'

import App from './App.vue'

new Vue({

    render:h=>h(App)

}).$mount("#app") */

// vue3

// 从vue中导入createApp方法,通过这个方法,创建vue实例

import { createApp } from 'vue'

// 导入App组件

import App from './App.vue'

// 通过createApp方法创建一个vue实例,渲染App组件,并将结果挂载到#app容器中。

createApp(App).mount('#app')

2. Vite

Vite 需要 Node.js版本 12.0以上

npm init @vitejs/app

# OR

yarn create @vitejs/app

# 然后按照提示操作即可

# 安装依赖

npm install

# 运行

npm run dev

3. 计算属性

<h2>计算属性</h2>

<div>姓:<input type="text" v-model="firstName">名:<input type="text" v-model="lastName"></div>

<div>姓名:{{fullName}}<input type="text" v-model="fullName"></div>

// 在Vue3中,定义计算属性,需要引入computed组合式API

import {ref,computed} from 'vue'

export default {

    // Vue2中的计算属性

    // 数据

    /* data() {

        return {

            firstName:'张',

            lastName:'杰'

        }

    },

    // 计算属性

    computed:{

        // 只读的计算属性

        // fullName(){

        //     return this.firstName+'.'+this.lastName

        // }

        // 读写计算属性

        fullName:{

            //返回计算机属性的结果

            get(){

                return this.firstName+'.'+this.lastName

            },

            //修改计算属性的值

            set(val){

                let arr = val.split('.')

                this.firstName = arr[0]

                this.lastName = arr[1]

            }

        }

    } */

    // Vue3中的计算属性

    setup() {

        let firstName = ref('张')

        let lastName = ref('杰')

        //computed()函数的参数是一个回调函数,回调函数的返回值,就是计算属性的返回值

        // 定义只读的计算属性

        // let fullName = computed(()=>{

        //     return firstName.value + '.' + lastName.value

        // })

        // 定义读写计算属性

        let fullName = computed({

            get(){

                return firstName.value + '.' + lastName.value

            },

            set(val){

                let arr = val.split('.')

                firstName.value = arr[0]

                lastName.value = arr[1]

            }

        })

        return{

            firstName,

            lastName,

            fullName

        }

    }

};

4. 侦听器

<h2>侦听器</h2>

<div>薪资:{{money}}

    <button @click="money+=1000">加薪</button>

</div>

<div>学生:{{stu}}

    <button @click="stu.name='李四',stu.age=25">修改学生</button>

    <button @click="stu.car.name='宝马',stu.car.price=40">学生换车</button>

</div>

// 引入组合式API watch 和 watchEffect

import {ref,reactive, watch, watchEffect} from 'vue'

export default {

    // Vue2中的侦听器

    /* //数据

    data() {

        return {

            money:10000,

            stu:{

                name:'张三',

                age:20,

                car:{

                    name:'奔驰',

                    price:50

                }

            }

        }

    },

    //侦听器

    watch:{

        //根据数据的名称定义一个方法,用于该方法去侦听该属性值是否发生变化(参数1是新值,参数2是旧值)

        // 注意:默认情况下,侦听器一上来不会立刻执行,必须要侦听到值重新发生变化后,才执行。

        // money(nval,oval){

        //     console.log(nval,oval);

        // }

        // 完整写法,侦听器定义成一个对象

        money:{

            //表示侦听器默认执行一次

            immediate:true,

            //定义侦听器的方法

            handler(nval,oval){

                console.log(nval,oval);

            }

        },

        // 监听学生数据,注意:只有整个学生对象变化了才会监听到,如果只是修改对象身上的属性,监听不到。

        // stu(nval,oval){

        //     console.log(nval,oval);

        // }

        // 解决方案:监听器改成一个对象

        stu:{

            //表示侦听器开启深度监视

            deep:true,

            handler(nval,oval){

                console.log(nval,oval);

            }

        }

    } */

    // Vue3中的侦听器

    setup() {

        let money = ref(10000)

        let stu = reactive({

            name:'张三',

            age:20,

            car:{

                name:'奔驰',

                price:50

            }

        })

        // watch函数有三个参数:1.侦听谁,2.回调函数,3.配置对象(可以省略)

        // 简单用法:一上来没有立刻执行

        // watch(money,(nval,oval)=>{

        //     console.log(nval,oval);

        // })

        // 完整用法:加上第三个参数,配置对象

        watch(money,(nval,oval)=>{

            console.log(nval,oval);

        },{

            //立刻执行

            immediate:true,

        })

        // 监视reactive的数据,默认就开启深度监视,并且无法关闭

        // watch(stu,(nval,oval)=>{

        //     console.log(nval,oval);

        // })

        // 对于reactive的数据,可以采用监视部分属性

        watch(()=>stu.name,(nval,oval)=>{

            console.log(nval,oval);

        })

        // 如果监视的是reactive里面的对象属性,默认是不开启深度监视的,需要手动开启

        watch(()=>stu.car,(nval,oval)=>{

            console.log(nval,oval);

        },{

            deep:true

        })

        //watchEffect监听器,只有一个回调函数参数,并且没有参数

        // 特点:

        // 1.默认会执行一次

        // 2.不需要明确监视谁,回调函数里面用到了谁,谁变了,就会重新执行回调函数。

        watchEffect(()=>{

            console.log('我是watchEffect');

            let m = money.value

             let name = stu.name

        })

        return{

            money,

            stu

        }

    }

};

5. 过滤器

<h2>过滤器</h2>

<div>薪资:{{toFixed2(money)}}</div>

<div>薪资:{{toFixed2Money}}</div>

export default {

    data() {

        return {

            money:10000.12345

        }

    },

    // 注意:在Vue2中可以定义过滤器,但是在Vue3中已经取消了过滤器。

    /* filters:{

        toFixed2(val){

            return val.toFixed(2)

        }

    } */

    // Vue3推荐我们使用方法 或 计算属性的方式,实现之前过滤器的效果。

    methods: {

        toFixed2(val){

            return val.toFixed(2)

        }

    },

    computed:{

        toFixed2Money(){

            return this.money.toFixed(2)

        }

    }

};

6. 响应式

<h2>响应式</h2>

<div>薪资:{{money}} <button @click="updateMoney">涨薪</button></div>

<div>汽车:{{car}} <button @click="updateCar">换车</button></div>

<div>学生:{{stu}} <button @click="updateStu">修改学生</button></div>

//Vue3中的所有组合式API,都要采用按需引入的方式导入

import {ref,reactive} from 'vue'

export default {

    //setup是所有组合式API的入口

    setup() {

        //使用ref定义基本类型数据

        let money = ref(10000)

        let updateMoney = ()=>{

            money.value += 1000

        }

        //使用ref定义引用类型数据

        // ref方法,返回的是ref对象,ref对象的value属性是一个代理对象(Proxy)

        let car = ref({

            name:'奔驰',

            price:'50W'

        })

        let updateCar = ()=>{

            // 注意:这里每次修改数据时,必须要先.value再.具体的属性

            car.value.name = '宝马',

            car.value.price = '40W'

        }

        // 注意:reactive只能定义引用类型(对象和数组)

        // reactive方法,直接返回一个代理对象(Proxy)

        let stu = reactive({

            name:'张三',

            age:20

        })

        let updateStu = ()=>{

            stu.name = '李四'

            stu.age = 25

        }

        // 总结:通常情况下:

        // 1.基本类型的数据,选择用ref定义

        // 2.引用类型的数据,选择用reactive定义


        //setup方法里面返回出去的成员,在模板可以使用

        return{

           money,

           updateMoney,

           car,

           updateCar,

           stu,

           updateStu

        }

    }

}

7. fragment组件

在vue3的模板中,不再需要根标签,它内部有一个fragment的组件作为模板的根标签

三、Vue3高阶

1. Hook函数

useCar

import {ref,computed} from 'vue'

//导出去一个函数

export default function(){

    //汽车数据

    let carName = ref('保时捷')

    let carPrice = ref(100)

    //汽车的计算属性

    let carPrice2 = computed(()=>{

        return (carPrice.value*0.8).toFixed(2)

    })

    //操作汽车的方法

    let updateCar = ()=>{

        carName.value = '宾利'

        carPrice.value = 300

    }

    //返回暴露给外界的内容

    return {

        carName,

        carPrice,

        carPrice2,

        updateCar

    }

}

usePhone

import {ref,computed} from 'vue'

export default function(){

    //手机数据

    let phoneName = ref('华为')

    let phonePrice = ref(5000)

    //手机的计算属性

    let phonePrice2 = computed(()=>{

        return (phonePrice.value*0.5).toFixed(2)

    })

    //操作手机的方法

    let updatePhone = ()=>{

        phoneName.value = '苹果'

        phonePrice.value = 9000

    }

    //返回暴露给外界的内容

    return {

        phoneName,

        phonePrice,

        phonePrice2,

        updatePhone

    }

}

组件

<h1>Hook函数</h1>

<div class="car">

    <h2>汽车信息</h2>

    <ul>

        <li>汽车名称:{{carName}}</li>

        <li>汽车价格:{{carPrice}}万</li>

        <li>优惠价格:{{carPrice2}}万</li>

        <li>

            <button @click="updateCar">修改汽车信息</button>

        </li>

    </ul>

</div>

<div class="phone">

    <h2>手机信息</h2>

    <ul>

        <li>手机名称:{{phoneName}}</li>

        <li>手机价格:{{phonePrice}}</li>

        <li>优惠价格:{{phonePrice2}}</li>

        <li>

            <button @click="updatePhone">修改手机信息</button>

        </li>

    </ul>

</div>

// 导入hook函数

import useCar from '../hooks/useCar'

import usePhone from '../hooks/usePhone'

export default {

    setup() {

        // 返回模板中需要使用的数据

        return {

            //返回汽车信息

            ...useCar(),

            //返回手机信息

            ...usePhone()

        }

    }

}

2. 生命周期

<h1>生命周期</h1>

<h3>

    数量:{{count}}

    <button @click="count++">数量++</button>    

</h3>

// 组合式API生命周期函数

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

export default {

    // beforeCreate() {

    //     console.log('创建之前');

    // },

    // created() {

    //     console.log('创建完成');

    // },

    // beforeMount() {

    //     console.log('挂载之前1');

    // },

    // mounted() {

    //     console.log('挂载完成1');

    // },

    // beforeUpdate() {

    //     console.log('更新之前1');

    // },

    // updated() {

    //     console.log('更新完成1');

    // },

    //注意:在vue3中,对beforeDestroy和destroyed这两个生命周期函数,进行了重命名

    /* beforeDestroy() {

        console.log('销毁之前');

    },

    destroyed() {

        console.log('销毁完成');    

    }, */

    // 在vue3中,beforeUnmount 替换了 beforeDestroy;unmounted 替换了 destroyed

    // beforeUnmount() {

    //     console.log('卸载之前1');

    // },

    // unmounted() {

    //     console.log('卸载完成1');

    // },

    data() {

        return {

            count:1

        }

    },

    // setup()函数,可以替代beforeCreate 和 created 这两个生命周期函数

    setup() {

        console.log('setup');

        //组合式API生命周期函数,会先与传统的生命周期函数执行

        onBeforeMount(()=>{

            console.log('挂载之前2');

        })

        onMounted(()=>{

            console.log('挂载完成2');

        })

        onBeforeUpdate(()=>{

            console.log('修改之前2');

        })

        onUpdated(()=>{

            console.log('修改完成2');

        })

        onBeforeUnmount(()=>{

            console.log('卸载之前2');

        })

        onUnmounted(()=>{

            console.log('卸载完成2');

        })

    }

}

3. toRef和toRefs

<h1>toRef和toRefs</h1>

<div class="stu">

    <h2>学生信息</h2>

    <ul>

        <li>姓名:{{name}}</li>

        <li>姓名:{{age}}</li>

        <li>车名:{{car.name}}</li>

        <li>车价:{{car.price}}</li>

    </ul>

</div>

import { reactive,toRef,toRefs } from 'vue'

export default {

    setup() {

        // 定义数据

        let stuData = reactive({

            name:'张三',

            age:20,

            car:{

                name:'大众',

                price:'20W'

            }

        })

        return{

            // toRef()函数,可以用来为一个 reactive 对象的属性创建一个 ref

            // 这样做的好处是,简化了模板中的表达式。

            // toRef()函数,需要传两个参数:1.reactive 对象,2.具体的属性名

            // name:toRef(stuData,'name'),

            // age:toRef(stuData,'age'),

            // car:toRef(stuData,'car')

            // 假如 reactive 对象中,有100个属性,上面的操作要写100次,所以,一般都直接用toRefs函数

            // toRefs函数,把一个响应式对象转换成普通对象,该普通对象的每个 属性 都是一个 ref

            ...toRefs(stuData)

        }

    }

}

4. 其他的组合式API

<h1>其他的组合式API</h1>

<div>

    学生信息:{{stuData}}

    <br>

    <button @click="stuData.age++">修改年龄</button>

    <button @click="stuData.car.price++">修改车价</button>

</div>

<div>

    num3的值:{{num3}}

</div>

<div>

    汽车信息:{{car}}

    <button @click="updateCar">修改汽车</button>

</div>

<div>

    手机信息:{{phone}}

    <button @click="updatePhone">修改手机</button>

</div>

<div>

    年龄:{{age}}

    <button @click="age++">年龄++</button>

</div>

import {ref,reactive,readonly,isRef,unref, shallowRef, isReactive, shallowReactive,customRef,toRaw, markRaw} from 'vue'

export default {

    setup() {

        // 定义数据

        // readonly()函数,返回一份只读数据,这个只读是“深层的”,内部任何嵌套的属性也都是只读的

        let stuData = readonly({

            name:'张三',

            age:20,

            car:{

                name:'大众',

                price:20

            }

        })

        let num1 = ref(100)

        let num2 = 200

        // isRef()函数,检查一个值是否为一个 ref 对象

        // isProxy()函数,检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

        // isReactive()函数,检查一个对象是否是由 reactive 创建的响应式代理

        // isReadonly()函数,检查一个对象是否是由 readonly 创建的只读代理

        // let num3 = (isRef(num1)?num1.value:num1) + (isRef(num2)?num2.value:num2)

        // unref()函数,如果参数是一个 ref 则返回它的 value,否则返回参数本身

        let num3 = unref(num1) + unref(num2)

        // ref() 返回的对象 value 属性值是 reactive对象(代理对象)

        // shallowRef() 返回的对象 value 属性值是 object对象(普通对象),不再具备任何响应式了

        let car = shallowRef({

            name:'大众',

            type:{

                typeName:'SUV'

            }

        })

        let updateCar = ()=>{

            // 由于value返回的是object对象,所以,这里不再具有响应式

            car.value.name = '奔驰'

            car.value.type.typeName = '跑车'

        }

        // shallowReactive() 返回一个浅层的响应式代理,只对对象的第一层属性创建响应式

        let phone = shallowReactive({

            name:'华为',

            type:{

                typeName:'滑盖手机'

            }

        })

        // toRaw() 将代理对象转为一个普通对象返回

        let phone2 = toRaw(phone)

        console.log(phone2);

        console.log('--------------------');

        //定义了一份数据

        // markRaw() 记一个对象为“永远不会转为响应式代理”

        let food = markRaw({

            name:'面包'

        })

        // 注意:food2就是一个普通对象

        let food2 = reactive(food)

        console.log(food2);

        let updatePhone = ()=>{

            //修改name,会触发页面更新

            // phone.name += "!"

            //修改type里面的属性,不会触发页面更新

            phone.type.typeName += "!"

        }

        //自定义一个ref

        function useDebouncedRef(value, delay = 200) {

            let timeout

            // customRef(),用于自定义一个 ref

            return customRef((track, trigger) => {

                return {

                    get() {

                        track()

                        return value

                    },

                    set(newValue) {

                        clearTimeout(timeout)

                            timeout = setTimeout(() => {

                            value = newValue

                            trigger()

                        }, delay)

                    },

                }

            })

        }

        let age = useDebouncedRef(20,2000)

        return {

            stuData,

            num3,

            car,

            updateCar,

            phone,

            updatePhone,

            age

        }

    }

}

四、组件传值

1. 父子组件传值

<Son1 :name="name" :age="age" :sex="sex"

      @updateName="name=$event" @updateAge="age=$event" @updateSex="sex=$event">

    <!-- 通过template组件指定具体的插槽 -->

    <template v-slot:one>

        <div>

            <button>按钮1</button>

        </div>

        <p>HelloWorld</p>

    </template>

    <!-- #是v-slot:的简写 -->

    <template #two>

        <div>

            <button>按钮2</button>

        </div>

        <p>你好世界</p>

    </template>

</Son1>

<h2>Son1</h2>

<div>

    姓名:{{myName}}

    年龄:{{myAge}}

    性别:{{mySex}}

    <button @click="updateData">修改信息</button>

</div>

<!-- 插槽,定义多个插槽时,需要给插槽定义名称:具名插槽 -->

<slot name="one"></slot>

<slot name="two"></slot>

<GrandSon1 />

import { ref } from 'vue';

import GrandSon1 from './GrandSon1.vue'

export default {

  name: "Son1",

  components:{

      GrandSon1

  },

  // 接收父组件传过来的数据

  props:['name','age'],

  // Vue2中的方式  

  /* data() {

      return {

          myName:this.name,

          myAge:this.age,

          mySex:this.sex

      }

  },

  methods: {

      updateData(){

          this.myName = '李四'

          this.myAge = 30

          this.mySex = '女'

          this.$emit('updateName',this.myName)

          this.$emit('updateAge',this.myAge)

          this.$emit('updateSex',this.mySex)

      }

  }, */

  //setup函数中通过参数props接收父组件传递进来的参数

  //注意:props参数中,只会接收props选项中接收的参数

  //context参数里面有三个对象:attrs,emit,slots

  //attrs用于获取没有采用props选项接收的参数

  //emit用于触发自定义事件

  //slots用于获取插槽信息

  setup(props,{attrs,emit,slots}) {

      //slots对象返回的是插槽里面所有内容的虚拟DOM

      console.log(slots.one()[0].children);

      //获取姓名和年龄

      let myName = ref(props.name)

      let myAge = ref(props.age)

      //获取性别

      let mySex = ref(attrs.sex)

      //修改数据的方法

      let updateData = ()=>{

          //修改自身数据

          myName.value = '李四'

          myAge.value = 30,

          mySex.value = '女'

          //触发自定义事件,将最新数据回传给父组件

          emit('updateName',myName.value)

          emit('updateAge',myAge.value)

          emit('updateSex',mySex.value)

      }

      return{

          myName,

          myAge,

          mySex,

          updateData

      }

  }

};

2. 祖孙组件传值

import {reactive, provide} from 'vue'

let phone = reactive({

    name:'华为',

    price:5000

})

//provide将指定的数据添加为依赖数据,让后代组件可以直接使用

provide('phone',phone)

<h3>GrandSon1</h3>

<div>

    手机信息:{{phone}}

    <button @click="updatePhone">修改手机</button>

</div>

import {inject} from 'vue'

// inject注入祖级组件中设置为依赖的数据

let phone = inject('phone')

let updatePhone = ()=>{

    phone.name = '苹果'

    phone.price = 8000

}

return {

    phone,

    updatePhone,

}

3. v-model

<!-- Vue3可以通过v-model指令实现对多个数据的双向绑定 -->

<Son2 v-model:name="name" v-model:age="age" v-model:sex="sex"

@click="testClick" />

<h2>Son2</h2>

<div>

    姓名:{{myName}}

    年龄:{{myAge}}

    性别:{{mySex}}

    <button @click="updateData">修改信息</button>

    <button @click="emitClick">触发一个click事件</button>

</div>

let testClick = (e)=>{

    alert(e)

}

import { ref } from 'vue';

export default {

  name: "Son2",

  //props选项接收父组件参数

  props:['name','age','sex'],

  //emits选项确定父组件可以触发哪些事件

  //注意:因为click跟原生事件同名,如果不在emits里面配置的话,父组件会触发两次click事件

  emits:['click'],

  setup(props,{emit}) {

      let myName = ref(props.name)

      let myAge = ref(props.age)

      let mySex = ref(props.sex)

      let updateData = ()=>{

          myName.value = '谢娜'

          myAge.value = 35,

          mySex.value = '女'

          //注意:自定义事件名称必须命名为update:属性名

          //就可以实现对父组件中指定属性的双向绑定  

          emit('update:name',myName.value)

          emit('update:age',myAge.value)

          emit('update:sex',mySex.value)

      }

      let emitClick = ()=>{

          //触发一个click事件

          emit('click','哈哈')

      }

      return{

          myName,

          myAge,

          mySex,

          updateData,

          emitClick

      }

  }

};

4. 异步组件

定义

<h2>Son3</h2>

<div>姓名:{{name}},年龄:{{age}}</div>

import {ref} from 'vue'

export default {

  name: "Son3",

  setup() {

      let name = ref('周杰伦')

      let age = ref(20)

      //注意:通常情况下,setup方法直接返回对象,不要返回Promise对象。

      return new Promise((resolve,reject)=>{

          setTimeout(() => {

            resolve({

                name,

                age

            })

          }, 2000);

      })

  }

使用

<!-- suspense用于在渲染异步组件时,显示Loading... -->

<!-- 注意:异步加载的组件可以用suspense,也可以不用;

但是,如果组件中setup的返回值是一个Promise对象,该组件必须要用suspense -->

<suspense>

    <template #default>

        <Son3/>

    </template>

    <template #fallback>

        Loading...

    </template>

</suspense>

// defineAsyncComponent组合式API,用于定义异步组件

import {defineAsyncComponent} from 'vue'

// 异步导入组件

let Son3 = defineAsyncComponent(()=>import('./components/Son3.vue'))

5. teleport组件

<h2>Son4</h2>

<button @click="show=true">弹出</button>

<!-- teleport组件:官方起的名称:瞬移。通过to属性确定里面的元素移动到哪 -->

<teleport to="body">

    <div v-show="show" class="box">

        <button @click="show=false">关闭</button>

    </div>

</teleport>

五、vuex4 & vue-router4

1. 创建router对象

// createRouter方法,用于创建路由器对象

// createWebHashHistory方法,用于生成hash模式的路由,路由地址中包含一个#

// createWebHistory方法,用于生成history模式的路由

import {createRouter,createWebHashHistory} from 'vue-router'

// 创建当前项目中的路由器对象

let router = createRouter({

    //定义路由模式

    history:createWebHashHistory(),

    //定义具体的路由信息

    routes:[

        //每一条路由信息,配置一个对象

        {

            path:'/',

            name:'home',

            component:()=>import('../views/Home.vue')

        },

        {

            path:'/store',

            name:'store',

            component:()=>import('../views/Store.vue')

        },

        {

            path:'/list/:id',

            props:true,

            name:'list',

            component:()=>import('../views/List.vue')

        },

        {

            path:'/news',

            name:'news',

            component:()=>import('../views/News.vue')

        },

        {

            path:'/page1',

            name:'page1',

            component:()=>import('../views/Page1.vue')

        },

        {

            // 注意:不可以写通配符*

            // path:'*',

            path:'/:pathMatch(.*)*',

            name:'error404',

            component:()=>import('../views/Error404.vue')

        }

    ]

})

export default router

2. 使用router

//useRouter方法,返回当前项目中的路由器对象

//useRoute方法,返回当前路由信息对象

import {useRouter,useRoute} from 'vue-router'

//返回当前项目中的路由器对象

let $router = useRouter()

//获取当前路由信息

let $route = useRoute()

//通过props,也能获取都路由参数

props:['id']

//监听路由参数id

watch(()=>$route.params.id,(nval)=>{

    //清空数组

    showList.splice(0)

    //向数组中添加最新的数据

    showList.push(...list.filter(r=>r.typeId==$route.params.id))

},{

    //一上来,先执行一次

    immediate:true

})

3. 创建store对象

// 从vuex中导入createStore方法,该方法,用于创建全局状态管理对象

import { createStore } from 'vuex'

// 导入汽车模块

import car from './modules/car.js'

// 创建一个全局状态管理对象

let store = createStore({

    //定义状态

    state:{

        firstName:'张',

        lastName:'三'

    },

    //定义围绕状态的计算属性

    getters:{

        fullName(state){

            return state.firstName+'.'+state.lastName

        }

    },

    //定义同步方法

    mutations:{

        updateFirstName(state,val){

            state.firstName = val

        },

        updateLastName(state,val){

            state.lastName = val

        }

    },

    //定义异步方法

    actions:{

        updateFirstName(store,val){

            setTimeout(() => {

                store.commit('updateFirstName',val)

            }, 1000);

        },

        updateLastName(store,val){

            setTimeout(() => {

                store.commit('updateLastName',val)

            }, 1000);

        }

    },

    //模块

    modules:{

       car

    }

})

//导出全局状态管理对象

export default store

4. 使用store

//useStore方法,返回当前项目中的全局状态管理对象

import { useStore } from "vuex";

// 获取全局状态管理对象

let $store = useStore();

let firstName = computed(() => {

    return $store.state.firstName;

});

let lastName = computed(() => {

    return $store.state.lastName;

});

let fullName = computed(() => {

    return $store.getters.fullName;

});

let carName = computed(() => {

    return $store.state.car.carName;

});

let address = computed(() => {

    return $store.state.car.address;

});

let carInfo = computed(() => {

    return $store.getters["car/carInfo"];

});

function updateFirstName() {

    //调用mutations里面的方法,修改姓

    $store.commit("updateFirstName", "李");

}

function updateLastName() {

    //调用actions里面的方法,修改名

    $store.dispatch("updateLastName", "四");

}

function updateCarName() {

    //调用mutations里面的方法,修改车名

    $store.commit("car/updateCarName", "宾利");

}

function updateCarAddress() {

    //调用actions里面的方法,修改地址

    $store.dispatch("car/updateCarAddress", "英国");

}

5. 注册

// 导入当前项目中创建的全局状态管理对象

import store from './store'

// 导入当前项目中创建的路由器对象

import router from './router'

// 使用createApp方法创建一个Vue实例,该方法的参数是App组件,表示渲染App组件

// use方法,用于给当前vue实例添加功能

// mount方法,用于将渲染后的内容,挂载到指定的容器中

createApp(App).use(store).use(router).mount('#app')

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容