Vue3 五天速成

文章目录

  • day 1
      • 1. 创建vue3工程
      • 3. 响应式数据
      • 4. 计算属性
  • day 2
      • 5. watch 监视
      • 6. watchEffect
      • 7. 标签的`ref`属性
  • day 3
      • 8. 回顾TS中的接口_泛型_自定义类型
      • 9. `props`的使用
      • 10. 生命周期
      • 11. 自定义Hooks
      • 12. 路由 基本切换效果
      • 13. 路由 两个注意点
      • 14. 路由 路由器的工作模式
      • 15. 路由 to的两种写法
      • 16. 路由 命名路由
      • 17. 路由 嵌套路由
      • 18. 路由 query 参数
      • 19. 路由 params 参数
      • 20. 路由 props配置
      • 21. 路由 replace属性
      • 22. 路由 编程式路由导航
      • 23. 路由 重定向
  • day 4
      • 24. pinia 选项式写法
      • 25. pinia 组合式写法
  • day 5
      • 26. 组件通信_props
      • 27. 组件通信_custom-event
      • 28. 组件通信_mitt
      • 29. 组件通信_v-model
      • 30. 组件通信_`$attrs`
      • 31. 组件通信_`$refs`与`$parent`
      • 32. 组件通信_provide-inject
      • 32. 组件通信_slot默认插槽
      • 33. 组件通信_slot具名插槽
      • 34. 组件通信_slot作用域插槽
      • 35. shallowRef 和 shallowReactive
      • 36. readonly 和 shallowReadonly
      • 37. toRaw 和 markRaw
      • 38. customRef
      • 39. Teleport
      • 40. Suspense
      • 41. 全局api转化为应用对象
      • 42. 其他

day 1

1. 创建vue3工程

相关代码如下:

## 创建vue工程
npm create vue@lastest

## 安装node_modules
npm install // npm i

创建工程完毕之后,进入文件夹可以发现有如下文件,下图是文件介绍:

入口文件介绍:

这里main.ts是与index.html建立联系的;

通过.mount('#app')这样 main.ts就与index.html建立起了联系;

其中main.ts的代码格式如下:

<template>
    <div>
        <h1>你好</h1>
    </div>
</template>

<script setup lang="ts">
    // js或者ts
</script>

<style scoped>
    /* 样式 */
</style>

App.vue的代码格式如下:

<template>
    <div class="app">
        <h1>你好</h1>
    </div>
</template>

<script setup lang="ts">
    // js或者ts
</script>

<style scoped>
    /* 样式 */
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>

网页调试vue可以在chrome应用商店search vue.js devtools下载安装就好;

你好

### 2. 选项式API和组合式API

感觉vue2和vue3的主要不同就在于vue文件中script部分;其中vue2是选项式OptionsAPI的,vue3是组合式Composition的;

选项式API相关代码如下

注意,这里并没有setup

<script lang="ts">
    export default {
	    // 定义组件名字
        name: 'Person',
        // 定义数据
        data(){
            return {
                name: '张三',
                age: 18,
                tel: '138888888'
            }
        },
        //  定义方法
        methods:{
            changeName(){
                this.name = 'zhang-san'
            },
            changeAge(){
                this.age += 1
            },
            showTel(){
                alert(this.tel)
            }
        },
    }
</script>

组合式相关代码如下

注意,这里的setup比beforecreate执行得还要早,setup的返回值也可以是渲染函数,data,method,setup可以同时存在,setup不可以读取data中的数据,反之则可以,因为setup是执行得最早的;

<script lang="ts">
    export default {
        name: 'Person',
        setup(){
            // 数据
            let name = '张三' // 此时name,age,tel 不是响应式的    应该改为ref或者reactive
            let age = 18    // 此时name,age,tel 不是响应式的  应该改为ref或者reactive
            let tel = '138888888'   // 此时name,age,tel 不是响应式的  应该改为ref或者reactive
            
            // 方法
            function changeName(){
                name = 'zhang-san'
            }
            function changeAge(){
                age += 1
            }
            function showTel(){
                alert(tel)
            }
            // 把数据和方法交出去
            return {name, age, changeAge, changeName, showTel}
        }
    }
</script>

setup语法糖简化后代码如下:

<script lang="ts">
    export default {
        name: 'Person',
    }
</script>

<script setup lang="ts">
    // 数据
    let name = '张三' // 此时name,age,tel 不是响应式的    应该改为ref或者reactive
    let age = 18    // 此时name,age,tel 不是响应式的  应该改为ref或者reactive
    let tel = '138888888'   // 此时name,age,tel 不是响应式的  应该改为ref或者reactive

    // 方法
    function changeName(){
        name = 'zhang-san'
    }
    function changeAge(){
        age += 1
    }
    function showTel(){
        alert(tel)
    }
    // 此时不需要return 自动提交
</script>

3. 响应式数据

响应式数据创建有两种方法,一种是ref,一种是reactive

ref定义基本类型的数据

使用的相关代码如下:

<template>
    <div class="app">
        <h2>姓名:{{ name }}</h2> // 利用大括号包起来的是不需要.value的
        <h2>年龄:{{ age }}</h2> // 利用大括号包起来的是不需要.value的
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="showTel">查看联系方式</button>
    </div>
</template>

<script setup lang="ts">
    import {ref} from 'vue'

    let name = ref('张三')
    let age = ref(18)   
    let tel = ref('138888888')
	// 在ref包裹住后,需要操作值都需要.value

    function changeName(){
        name.value = 'zhang-san'
    }
    function changeAge(){
        age.value += 1
    }
    function showTel(){
        alert(tel.value)
    }

这里值用ref包裹起来之后,变量会变成一个RefImpl类的数据,这时要修改值直接对变量操作是无意义的,我们需要对.value进行操作;这里要注意的是,ref也可以定义对象类的响应式数据,实现原理是先用ref包裹,再用reactive包裹,即取值还是需要用value

reactive定义对象类型的数据

使用的相关代码如下:

<template>
    <div class="app">
        <h2>姓名:{{ student.name }}</h2>
        <h2>年龄:{{ student.age }}</h2>
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person',
    }
</script>

<script setup lang="ts">
    import {reactive} from 'vue'

    let student = reactive({name:'张三', age:18})

    function changeName(){
        student.name = 'zhang-san'
    }
    function changeAge(){
        student.age += 1
    }

</script>

<style scoped>
    /* 样式 */
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
    
    button {
        margin: 10px;
    }
</style>

reactive包起来后,对象变成了一个Proxy的对象,原对象在Proxy里面的target中;这里的reactive是深层次的,只能定义对象类型的响应式数据;

refreactive的对比

这里要注意的是,重新分配reactive的对象时,使用Object.assign,但是如果是利用ref定义的对象类数据,我们是可以直接进行替换的;

对响应式数据进行解构,toReftoRefs

let person = reactive({
	name: '张三',
	age: 18
})

let {name, age} = person
// let name = person.name
// let age = person.age

let {name, age} = toRefs(person) //把reactive对象里面的每一组对象转化为ref
// let name = toRef(person, 'name')
// let age = toRef(person, 'age')

4. 计算属性

v-bind 是 单向绑定,数据流向页面;v-model 是 双向绑定,页面也可以流向数据;

计算属性 computed 是只要响应式变量出现了变化,就随之变化;

使用例子如下:

<template>
    <div class="app">
        姓名:<input type="text" v-model="name"> <br>
        年龄:<input type="text" v-model="age"> <br>
        information: {{ information }}
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person',
    }
</script>

<script setup lang="ts">
    import {ref, computed} from 'vue'

    let name = ref('zhangsan')
    let age = ref(18)

    let information = computed(()=>{
        return name.value + age.value
    })

</script>

<style scoped>
    /* 样式 */
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>

computed是用来区别于方法的,使用computed得到的属性是没有缓存的,而且computed得到的属性是不可修改的;如果需要修改,则需要使用以下方法:

<template>
    <div class="app">
        姓名:<input type="text" v-model="name"> <br>
        年龄:<input type="text" v-model="age"> <br>
        information: {{ information }} <br>
        <button @click="changeInformation">修改information为lisi</button>
        
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person',
    }
</script>

<script setup lang="ts">
    import {ref, computed} from 'vue'

    let name = ref('zhangsan')
    let age = ref(18)

    let information = computed({
        get(){
            return name.value + '-' + age.value
        },
        set(val){
            const [s1, s2] = val.split('-')
            name.value = s1
            age.value = parseInt(s2)
        }
    })

    function changeInformation(){
        information.value = 'lisi-18'
    }

</script>

<style scoped>
    /* 样式 */
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>

day 2

5. watch 监视

情况1:监视ref定义的基本类型数据

停止监视只需要调用watch函数的返回值就可以;

<template>
    <div>
        <h5>情况1:监视【ref】定义的值是【基本类型】的数据</h5>
        sum : {{ sum }} <br>
        <button @click="changeSum"> sum + 1</button> <br>
        <hr>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {ref, watch} from 'vue'

    let sum = ref(1)

    function changeSum(){
        sum.value += 1
    }

    // 这里的sum是不需要添加.value的,返回值就是停止监视的函数
    const stopwatch = watch(sum, (newVal, oldVal)=>{
        console.log('sum变化了', newVal, oldVal)
        // 如果sum的最新值大于等于10则停止监视
        if(newVal >= 10 ){
            stopwatch()
        }
    })
</script>

<style scoped>
</style>

情况2:监视ref定义的对象类型数据

这里要注意的是:若修改的是ref定义的对象中的属性,newVal和oldVal都是新值,因为是同一个对象;若修改的是ref定义的整个对象,newVal是新值,oldVal是旧值,因为不是同一个对象;

<template>
    <div>
        <h5>情况2:监视【ref】定义的值是【对象类型】的数据</h5>
        姓名: {{ person.name }} <br>
        年龄: {{ person.age }} <br>
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changeFull">修改整个人</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {ref, watch} from 'vue'

    let person = ref({
        name: '张三',
        age: 18
    })

    function changeName (){
        person.value.name = '李四'
    }

    function changeAge (){
        person.value.age = 19
    }

    function changeFull (){
        person.value = {name: '王五', age: 20}
    }

    // 监视【ref】定义的【对象类型数据】,监视的是对象的地址值,若想要监视
    // 对象的内部属性的变化,需要手动开启深度监视deep

    // 若修改的是ref定义的对象中的属性,newVal和oldVal都是新值,因为是同一个对象
    // 若修改的是ref定义的整个对象,newVal是新值,oldVal是旧值,因为不是同一个对象

    // watch的第一个参数是:被监视的数据
    // watch的第二个参数是:监视的回调
    // watch的第三个参数是:配置对象(deep, immediate ... )
    watch(person, (newVal, oldVal)=>{
        console.log('person变化了', newVal, oldVal)
    }, {deep: true})
</script>

<style scoped>
    button {
        margin: 10px;
    }
</style>

情况3:监视reactive定义的对象类型数据

很简单,只需要把ref定义的对象改为reactive定义的对象,然后在修改整个对象的时候使用Object.assign替换就可以;

<template>
    <div>
        <h5>情况3:监视【reactive】定义的值是【对象类型】的数据</h5>
        姓名: {{ person.name }} <br>
        年龄: {{ person.age }} <br>
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changeFull">修改人</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {reactive, watch} from 'vue'

    let person = reactive({
        name: '张三',
        age: 18
    })

    function changeName (){
        person.name = '李四'
    }

    function changeAge (){
        person.age = 19
    }

    function changeFull (){
        // 并没有修改地址值;
        Object.assign(person, {name: '王五', age: 20})
    }

    // 监视【reactive】定义的对象类型数据,默认是开启深度监视的,而且深度是关不掉的
    watch(person, (newVal, oldVal)=>{
        console.log('person变化了', newVal, oldVal)
    })

    
</script>

<style scoped>
    button {
        margin: 10px;
    }
</style>

情况4:监视ref或者reactive定义的对象类型中的某个属性

<template>
    <div>
        <h5>情况4:监视【ref或reactive】定义的【对象类型】某个属性</h5>
        姓名: {{ person.name }} <br>
        年龄: {{ person.age }} <br>
        车辆:{{ person.car.c1 }}, {{ person.car.c2 }} <br>
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changeC1">修改c1</button>
        <button @click="changeC2">修改c2</button>
        <button @click="changeCar">修改car</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {reactive, watch} from 'vue'

    let person = reactive({
        name: '张三',
        age: 18,
        car:{
            c1: 'asd',
            c2: 'das'
        }
    })

    function changeName (){
        person.name = '李四'
    }

    function changeAge (){
        person.age = 19
    }

    function changeC1(){
        person.car.c1 = 'qqq'
    }
    function changeC2(){
        person.car.c2 = 'www'
    }
    function changeCar(){
        person.car = {c1:'yyy', c2:'jjj'}
    }

    // 监视响应式对象中的某个属性,且该变量是基本类型的,要写成函数式
    /* watch(()=>person.name, (newVal, oldVal)=>{
        console.log('person.name变化了', newVal, oldVal)
    }) */

    // 监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,推荐写函数
    watch(()=>person.car, (newVal, oldVal)=>{
        console.log('person.car变化了', newVal, oldVal)
    }, {deep:true})

    
</script>

<style scoped>
    button {
        margin: 10px;
    }
</style>

情况5:监视多个数据

<template>
    <div>
        <h5>情况4:监视【ref】定义的值是【对象类型】的数据</h5>
        姓名: {{ person.name }} <br>
        年龄: {{ person.age }} <br>
        车辆:{{ person.car.c1 }}, {{ person.car.c2 }} <br>
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changeC1">修改c1</button>
        <button @click="changeC2">修改c2</button>
        <button @click="changeCar">修改car</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {reactive, watch} from 'vue'

    let person = reactive({
        name: '张三',
        age: 18,
        car:{
            c1: 'asd',
            c2: 'das'
        }
    })

    function changeName (){
        person.name = '李四'
    }

    function changeAge (){
        person.age = 19
    }

    function changeC1(){
        person.car.c1 = 'qqq'
    }
    function changeC2(){
        person.car.c2 = 'www'
    }
    function changeCar(){
        person.car = {c1:'yyy', c2:'jjj'}
    }

    // 此时newVal和oldVal是数组与前面的对应
    watch([()=>person.name, ()=>person.age], (newVal, oldVal)=>{
        console.log('变化了', newVal, oldVal)
    })

    
</script>

<style scoped>
    button {
        margin: 10px;
    }
</style>

6. watchEffect

相较于watch,watchEffect不需要指定监视对象,而是响应式的追踪对象;

<template>
    <div>
        <h5>情况4:监视【ref】定义的值是【对象类型】的数据</h5>
        姓名: {{ person.name }} <br>
        年龄: {{ person.age }} <br>
        车辆:{{ person.car.c1 }}, {{ person.car.c2 }} <br>
        <button @click="changeName">修改名字</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changeC1">修改c1</button>
        <button @click="changeC2">修改c2</button>
        <button @click="changeCar">修改car</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {reactive, watch, watchEffect} from 'vue'

    let person = reactive({
        name: '张三',
        age: 18,
        car:{
            c1: 'asd',
            c2: 'das'
        }
    })

    function changeName (){
        person.name = '李四'
    }

    function changeAge (){
        person.age += 1
    }

    function changeC1(){
        person.car.c1 = 'qqq'
    }
    function changeC2(){
        person.car.c2 = 'www'
    }
    function changeCar(){
        person.car = {c1:'yyy', c2:'jjj'}
    }

    // 此时newVal和oldVal是数组与前面的对应
    /* watch([()=>person.name, ()=>person.age], (newVal, oldVal)=>{
        let [newname, newage] = newVal
        if( newage > 23 ){
            console.log('发送请求')
        }
    }) */

    // 如果采用watchEffect,全自动的watch;
    watchEffect(()=>{
        if( person.age > 23 ){
            console.log('发送请求')
        }
    })

</script>

<style scoped>
    button {
        margin: 10px;
    }
</style>

7. 标签的ref属性

利用refdocument.getElementById('')的区别在于,前者是局部的,不会受到整体的干扰;

<template>
    <h2> 北京 </h2>
    <h2 ref="title"> 师范 </h2>
    <h2> 大学 </h2>
    <button @click="output"> 点击一下输出样式 </button>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {ref, defineExpose} from 'vue'

    // 这里变量名和template中的ref中的变量名对应上了
    let title = ref()

    function output(){
        console.log(title.value)
    }
    // 在这里可以使调用该模块的模块得到该模块的内容
    defineExpose({title})
</script>

<style scoped>
    /* 这里的scoped是局部样式,防止和子文件样式出现重复而全部修改
    无脑加上就好 */
</style>

day 3

8. 回顾TS中的接口_泛型_自定义类型

一般在文件目录src下面有一个types文件夹,types中有一个index.ts文件可以直接导入(导入过程不需要写文件)

这里文件表示泛型和接口的意思,一般是结合后端使用,避免缺失参数;

// 定义一个接口,用于限制person对象的具体属性 string,number小写
// 使用export暴露
export interface PersonInter {
    id: string,
    name: string,
    // ?问号表示可有可无
    age?: number
}


// 一个自定义类型
export type Persons = Array<PersonInter>
// 也可以这样写 export type Persons = PersonInter[]
<template>
    <div class="app">

    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    // PersonInter是类型,必须使用type前置
    import {type PersonInter, type Persons} from '@/types'
	import {reactive} from 'vue'
	
    // 单个
    let person:PersonInter = {id: 'asdasdas01', name: '张三', age: 18}

    // 数组 第一种形式   尖括号表示的是泛型:即符合规范
    let persons:Array<PersonInter> = [
        {id: 'asdasdas01', name: '张三', age: 18},
        {id: 'asdasdas02', name: '李四', age: 20},
        {id: 'asdasdas03', name: '王五', age: 33}
    ]
	// 数组 第二种形式 这里结合index.ts文件
    let personList:Persons = [
        {id: 'asdasdas01', name: '张三', age: 18},
        {id: 'asdasdas02', name: '李四', age: 20},
        {id: 'asdasdas03', name: '王五', age: 33}
    ]

	let personsReactive = reactive<Persons>([
        {id: 'asdasdas01', name: '张三', age: 18},
        {id: 'asdasdas02', name: '李四', age: 20},
        {id: 'asdasdas03', name: '王五', age: 33}
    ])
    
</script>

<style scoped>
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>

9. props的使用

将父组件中的数据传给子组件

vuehtml标签属性前面加冒号表示表达式

<h2 a="1+1" :b="1+1"></h2>

父组件如下:

<template>
    <Person a="哈哈" :list="persons"/>
</template>

<script lang="ts">
    export default {
        name: 'App'
    }
</script>

<script lang="ts" setup>
    import Person from './components/Person.vue'
    import {reactive} from 'vue'
    import {type Persons} from '@/types'

    let persons = reactive<Persons>([
        {id: 'asdasdas01', name: '张三', age: 18},
        {id: 'asdasdas02', name: '李四', age: 20},
        {id: 'asdasdas03', name: '王五', age: 33}
    ])

</script>

<style scoped>
</style>

子组件如下:

<template>
    <div class="app">
        <ul>
            <li v-for="a in x.list" :key="a.id">{{ a.name }} - {{ a.age }}</li>
        </ul>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import {type Persons } from '@/types';
    // 下面可以不用引入defineProps,withDefaults
    import {defineProps, withDefaults} from 'vue'

    // 接收a
    // defineProps(['a'])

    // 接收a和list,同时将props保存起来
    // let x = defineProps(['a', 'list'])
    // x = {a:'哈哈'}

    // 接收list+限制类型
    // let x = defineProps<{list:Persons}>()

    // 接收list + 限制类型 + 限制必要性 + 指定默认值
    // list后面加?表示父组件可传可不传, 第二个list后面需要跟函数
    let x = withDefaults(defineProps<{list?:Persons}>(),{
        list:()=>[{id:'adasdasdaszxc01', name:'kang', age:20}]
    })
    
    
</script>

<style scoped>
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>

10. 生命周期

组件的生命周期有四个阶段:创建,挂载,更新,销毁;

钩子:生命周期函数 (阶段前,阶段完毕) 钩子一共有8个

<template>
    <div class="app">
        {{ message }}
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

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

    let message = '哈哈'
    
    onBeforeMount(()=>{
        console.log('ok')
    })

    onUnmounted(()=>{
        console.log('一般来说需要搭配v-if')
    })
    
</script>

<style scoped>
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>

11. 自定义Hooks

Hooks是组合式功能的体现,其创建过程类似于8节中在文件目录src下创建一个hooks的文件夹,里面存放useXxxx.ts文件,实例如下:

useAdd.ts

import { computed } from '@vue/reactivity'
import {ref, onMounted} from 'vue'

export default function(){
    let sum = ref(0)
    let bigSum = computed(()=>{
        return sum.value * 10
    })

    function add(){
        sum.value += 1
    }

    onMounted(()=>{
        sum.value += 1 
    })
    return {sum, add, bigSum}
}

useDog.ts

import {reactive, onMounted} from 'vue'
import axios from 'axios'

export default function(){
    let dogList = reactive([
        'https://images.dog.ceo/breeds/pembroke/n02113023_4136.jpg'
    ])
    
    // axios 的 实例用法1
    /* function addDog(){
        axios.get('https://dog.ceo/api/breed/pembroke/images/random').then(
            response => {dogList.push(response.data.message)},
            error => {console.log(error)}
        )
    } */

    // axios 的 实例用法2
    async function addDog() {
        try {
            let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
            dogList.push(result.data.message)
        } catch (error) {
            console.log(error)
        }
    }

    onMounted(()=>{
        addDog()
    })

    return {dogList, addDog}
}

这样组件中可以直接应用hooks文件下的内容:

<template>
    <div class="app">
        <h2> sum: {{ sum }};    bigSum: {{ bigSum }}</h2>
        <button @click="add"> 点我sum+1</button>
        <hr>
        <img v-for="(dog,index) in dogList" :src="dog" :key="index"> <br>
        
        <button @click="addDog"> 点我再添加一只狗 </button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<script setup lang="ts">
    import useAdd from '@/hooks/useAdd'
    import useDog from '@/hooks/useDog'

    const {sum, add, bigSum} = useAdd()
    const {dogList, addDog} = useDog()
</script>

<style scoped>
    .app {
        background-color: #ddd;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }

    img {
        height: 150px;
        margin-right: 10px;
    }
</style>

一般在文件目录src下面有一个types文件夹,types中有一个index.ts文件可以直接导入(导入过程不需要写文件)

12. 路由 基本切换效果

路由和路由器不一样

路由类似于键值对,route

路由器类似于管理路由,router

路由的创建与Hooks和TS接口类似,其创建过程类似于8节中在文件目录src下创建一个router的文件夹,里面存放index.ts文件(无需导入文件名),实例如下:

index.ts 实例如下:

// 创建一个路由器,并暴露出去


// 第一步:引入createRouter
import {createRouter, createWebHistory} from 'vue-router'

// 第二步:引入可能要呈现的组件
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import News from '@/components/News.vue'


// 第三步:创建路由器
const router = createRouter({
    // history表示路由的工作模式
    history: createWebHistory(),
    routes:[// 一个个路由规则
        {
            path: '/home',
            component: Home
        },
        {
            path: '/news',
            component: News
        },
        {
            path: '/about',
            component: About
        }
    ]
})

export default router

在这里我们只是创建了路由,于此同时,我们需要把路由加载到项目中,所以接下来我们需要在main.ts文件中操作;

// 引入createApp 用于创建应用
import { createApp } from 'vue'
// 引入app根组件
import App from './App.vue'
// 引入路由器
import router from './router'
// 创建一个应用
const app = createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount('#app')

为了在App.vue引入组件,我们还需要做一些操作,使用RouterView, RouterLink

<template>
    <h2> Vue 路由测试 </h2>
    <!-- 导航区 -->
    <div class="navigate">
        <RouterLink to="/home">主页</RouterLink>
        <RouterLink to="/news">新闻</RouterLink>
        <RouterLink to="/about">关于</RouterLink>
    </div>
    <!-- 展示区 -->
    <div class="main_content">
        <RouterView/>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'App'
    }
</script>

<script lang="ts" setup>
    import {RouterView, RouterLink} from 'vue-router'

</script>

<style scoped>
    h2 {
        background: skyblue;
        text-align: center;
    }

    div.navigate {
        text-align: center;
    }
    div.navigate a{
        text-align: center;
        margin: 50px;
    }

    div.main_content{
        text-align: center;
        height: 500px;
    }
</style>

就完毕了,这里要用RouterLink替换a标签,并把href改成to,用RouterView表示component的插入位置;

13. 路由 两个注意点

路由组件一般放在pages或者views文件夹,一般组件通常放在components文件夹;

通过点击导航,视觉上效果消失了的组件,默认是被销毁掉的,需要的时候再去挂载;

14. 路由 路由器的工作模式

路由器有两种工作模式,history和hash,前端一般用history,后端一般用hash;

15. 路由 to的两种写法


%% 第一种: %%
<RouterLink to="/home" active-class="active">主页</RouterLink>

%% 第二种: %%:
<RouterLink :to="{path='/home'}" active-class="active">主页</RouterLink>

这里再介绍一下 active-class ,由于RouterLink在渲染的时候会变成a标签,所以样式可以类似于设置如下:


    a.active {
        text-align: center;
        margin: 50px;
        color: red;
    }

16. 路由 命名路由

可以在路由的index.ts文件中添加添加名字:

// 创建一个路由器,并暴露出去


// 第一步:引入createRouter
import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'

// 第二步:引入可能要呈现的组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import News from '@/views/News.vue'


// 第三步:创建路由器
const router = createRouter({
    // 路由的工作模式:history模式 不带#号 优先
    history: createWebHistory(),
    // 路由的工作模式:hash模式 带#号
    // history: createWebHashHistory(),
    routes:[// 一个个路由规则
        {
            name: 'zhuye',
            path: '/home',
            component: Home
        },
        {
            name: 'xinwen',
            path: '/news',
            component: News
        },
        {
            name: 'guanyu',
            path: '/about',
            component: About
        }
    ]
})

export default router

17. 路由 嵌套路由

子级不用加斜杠,但是to的时候需要完整的使用\news\detail

// 创建一个路由器,并暴露出去

// 第一步:引入createRouter
import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'

// 第二步:引入可能要呈现的组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import News from '@/views/News.vue'
import Detail from '@/views/Detail.vue'

// 第三步:创建路由器
const router = createRouter({
    // 路由的工作模式:history模式 不带#号 优先
    history: createWebHistory(),
    // 路由的工作模式:hash模式 带#号
    // history: createWebHashHistory(),
    routes:[// 一个个路由规则
        {
            name: 'zhuye',
            path: '/home',
            component: Home
        },
        {
            name: 'xinwen',
            path: '/news',
            component: News,
            children:[
                {
                    path: 'detail', //子级不用写斜杠
                    component: Detail,
                }
            ]
        },
        {
            name: 'guanyu',
            path: '/about',
            component: About
        }
    ]
})

export default router

18. 路由 query 参数

在传参数给组件之前,我们需要给接收参数的组件添加一个Hooks:useRoute

<template>
    <ul class="news-list">
        <li>编号:{{ route.query.id }}</li>
        <li>标题:{{ route.query.title }}</li>
        <li>内容:{{ route.query.content }}</li>
    </ul>
</template>

<script setup lang="ts">
    import {useRoute} from 'vue-router'
    import {toRefs} from 'vue'
    
    let route = useRoute()
    
	// 如果需要解构
	let {query} = toRefs(route)

</script>

<style scoped>
</style>

这里在RouterLink中添加query参数:

        <ul>
            <li v-for="news in NewsList" :key="news.id">
                <!-- 第一种写法 -->
                <!-- <RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{ news.title }}</RouterLink> -->
                <!-- 第二种写法 -->
                <RouterLink :to="{
                    path: '/news/detail',
                    query: {
                        id: news.id,
                        title: news.title,
                        content: news.content
                    }
                }">
                    {{ news.title }}
                </RouterLink>

            </li>
        </ul>

19. 路由 params 参数

首先需要在router中设置占位:就是在path中添加/:

// 创建一个路由器,并暴露出去

// 第一步:引入createRouter
import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'

// 第二步:引入可能要呈现的组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import News from '@/views/News.vue'
import Detail from '@/views/Detail.vue'


// 第三步:创建路由器
const router = createRouter({
    // 路由的工作模式:history模式 不带#号 优先
    history: createWebHistory(),
    // 路由的工作模式:hash模式 带#号
    // history: createWebHashHistory(),
    routes:[// 一个个路由规则
        {
            name: 'zhuye',
            path: '/home',
            component: Home
        },
        {
            name: 'xinwen',
            path: '/news',
            component: News,
            children:[
                {
                    name: 'xiangqing',
                    path: 'detail/:id/:title/:content?', // 需要占位 ?代表可传可不传
                    component: Detail,
                }
            ]
        },
        {
            name: 'guanyu',
            path: '/about',
            component: About
        }
    ]
})

export default router

params只能使用routename,而不能使用path,同时传参数只能使用基本类型,而不能使用数组和对象;

        <ul>
            <li v-for="news in NewsList" :key="news.id">
                <!-- 第一种写法 -->
                <!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</RouterLink> -->
                <!-- 第二种写法 -->
                <RouterLink :to="{
                    name: 'xiangqing',
                    params: {// 只能传基本类型,不能是对象和数组
                        id: news.id,
                        title: news.title,
                        content:news.content,
                    }
                }">
                    {{ news.title }}
                </RouterLink>

            </li>
        </ul>

20. 路由 props配置

props:true 布尔值写法,只能搭配params使用,path需要占位

props(route){return route.query} 函数式写法,其中route可以操作params和query

props:{id:xx,title:xx,content:xx}

其原理就相当于在<Detail/> 中 添加了 <Detail id=xx title=xx content=xx/>

21. 路由 replace属性

类似于浏览器的前进后退机制;

    <div class="navigate">
        <RouterLink replace to="/home" active-class="active">主页</RouterLink>
        <RouterLink replace :to="{name:'xinwen'}" active-class="active">新闻</RouterLink>
        <RouterLink replace to="/about" active-class="active">关于</RouterLink>
    </div>

直接在RouterLink后面添加replace,不会留下记录;

22. 路由 编程式路由导航

可以做到不点标签直接跳转的效果,脱离RouterLink实现跳转;router.push() router.replace() 括号中to能怎么写,括号就能怎么写;

setTimeout(()=>{
	// 在此处执行跳转
}, 3000) // 3秒后执行函数

实例代码如下:

import {onMounted} from 'vue'
import { useRouter } from 'vue-router';

const router = useRouter()

onMounted(()=>{
	setTimeout(()=>{
		router.push('/news')
	}, 3000)
})

按钮点击实例代码如下:

<template>
    <div class="news">
        <!-- 导航区 -->
        <ul>
            <li v-for="news in NewsList" :key="news.id">
                <button @click="showContent(news)">点击获取详情</button>
                <RouterLink :to="{
                    name: 'xiangqing',
                    params: {// 只能传基本类型,不能是对象和数组
                        id: news.id,
                        title: news.title,
                        content:news.content,
                    }
                }">
                    {{ news.title }}
                </RouterLink>

            </li>
        </ul>
        <!-- 展示区 -->
        <div class="news-content">
            <RouterView></RouterView>
        </div>
    </div>
</template>

<script setup lang="ts">
    import type path from 'path';
    import {reactive} from 'vue'
    import {RouterView, RouterLink, useRouter} from 'vue-router'

    interface News{
        id: string,
        title: string,
        content: string,
    }

    const NewsList = reactive<News[]>([
        {id: 'asdasd01', title:'新闻阿斯达所多01', content: '在成长速度还01'},
        {id: 'asdasd02', title:'新闻阿斯达所多02', content: '在成长速度还02'},
        {id: 'asdasd03', title:'新闻阿斯达所多03', content: '在成长速度还03'},
        {id: 'asdasd04', title:'新闻阿斯达所多04', content: '在成长速度还04'},
    ])

    const router = useRouter()

    function showContent(news:News){
        router.push(
            {
                name: 'xiangqing',
                params: {// 只能传基本类型,不能是对象和数组
                    id: news.id,
                    title: news.title,
                    content:news.content,
                }
            })
    }
</script>

<style scoped>
    div.news {
        background-color: yellow;
        height: 500px;
    }

    div.news-content {
        background-color: aqua;
        height: 300px;
        width: 200px;
    }
</style>

23. 路由 重定向

自动切换url,在router文件中补充规则:

{
	path: '/',
	redirect: '/home'
}

day 4

24. pinia 选项式写法

pinia环境首先需要安装

npm install pinia

安装之后,pinia的创建与Hooks和TS接口类似,其创建过程类似于8节中在文件目录src下创建一个store的文件夹,里面存放对应components中的文件实例如下:

在main.ts文件中需要引入pinia

import {createApp} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
import router from './router'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)

app.mount('#app')

Count.vue如下:

<template>
    <div class="count">
        <h2>当前求和为: {{ sum }}, bigsum:{{ bigSum }}, smallsum:{{ smallSum }}</h2>
        <!-- 这里.number表示value数值后面的符号为单位 -->
        <select v-model.number="n">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>
        <button @click="add">加</button>
        <button @click="minus">减</button>
    </div>
</template>

<script  lang="ts">
    export default {
        name: 'Count'
    }
</script>

<script setup lang="ts">
    import {useCountStore} from '@/store/Count'
    import {storeToRefs} from 'pinia'

    let countStore = useCountStore()
    // storeToRefs只会关注store中的数据
    let {sum, n, bigSum, smallSum} = storeToRefs(countStore)

    // 以下两种方式都能拿到state中的数据
    // console.log(countStore.sum)
    // console.log(countStore.$state.sum)

    // 方法
    function add(){
        // 第一种修改方式
        // countStore.sum += countStore.n

        // 第二种修改方式
        // countStore.$patch({
        //     sum: 100
        // })

        // 第三种修改方式 复杂内容可以放到store中的actions中
        countStore.increment()
    }
    function minus(){
        sum.value -= n.value
    }

</script>

<style scoped>
    .count {
        background-color: skyblue;
        padding: 10px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        margin-bottom: 20px;
    }
    select, button {
        margin: 0 10px;
        height: 25px;
    }
</style>

Count.ts 如下:

import {defineStore} from 'pinia'
import {ref} from 'vue'


export const useCountStore = defineStore('Count', {
	// 写函数
    actions:{
        increment(){
            this.sum += this.n
        }
    },
    // 真正存储数据的地方
    state(){
        return {
            sum: ref(0),
            n: ref(1),
        }
    },
    // 类似于vue中的computed属性
    getters:{
        bigSum: state=>state.sum*10,
        smallSum():number{
            return this.sum - 1000
        }
    }
})

LoveTalk.vue如下:

<template>
    <div class="talk">
        <button @click="getMessage"> 点击获取一句土味情话</button>
        <ul>
            <li v-for="talk in talkList" :key="talk.id">{{ talk.content }}</li>
        </ul>
    </div>
</template>

<script  lang="ts">
    export default {
        name: 'LoveTalk'
    }
</script>

<script setup lang="ts">
    import {useLoveStore} from '@/store/LoveTalk'
    import {storeToRefs} from 'pinia'

    let loveStore = useLoveStore()
    let {talkList} = storeToRefs(loveStore)

    loveStore.$subscribe((mutate, state)=>{
        // mutate是变化的东西,state是后面的状态
        console.log('lovestore保存的数据发生了变化!', mutate, state)
        // 存储在本地的talkList变量中,由于talkList只能接受字符串数据,所以需要JSON.stringify
        localStorage.setItem('talkList', JSON.stringify(state.talkList))
    })

    async function getMessage(){
        loveStore.getTalk()
    }

</script>

<style scoped>
    .talk {
        background-color: orange;
        padding: 10px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }

</style>

LoveTalk.ts如下:

import {defineStore} from 'pinia'
import {nanoid} from 'nanoid' // 生成id
import axios from 'axios'

export const useLoveStore = defineStore('LoveTalk', {
    actions:{
        async getTalk(){
            let {data:{content}} = await axios.get('https://api.uomg.com/api/rand.qinghua')
            let obj = {id: nanoid(), content: content}
            this.talkList.unshift(obj)
        }
    },
    // 真正存储数据的地方
    state(){
        return {
            talkList: JSON.parse(localStorage.getItem('talkList') as string ) || []
        }
    }
})

25. pinia 组合式写法

这里相对于选项式,我们需要把state中的部分转化为响应式才有效,然后添加return返回:

LoveTalk.ts如下:

import {defineStore} from 'pinia'
import {nanoid} from 'nanoid'
import axios from 'axios'
import {reactive} from 'vue'

export const useLoveStore = defineStore('LoveTalk', ()=>{

    // 这里写state中的部分
    let talkList = reactive(JSON.parse(localStorage.getItem('talkList') as string ) || [])

    async function getTalk(){
        console.log('获取一句')
        try {
            let {data:{content}} = await axios.get('https://api.uomg.com/api/rand.qinghua')
            let obj = {id: nanoid(), content: content}
            talkList.unshift(obj)
        } catch (error) {
            console.log('获取失败!')
        }

    }

    return {talkList, getTalk}
})

day 5

26. 组件通信_props

Father.vue 如下:

<template>
    <div class="father">
        <h2>这是父组件</h2>
        <h4>汽车:{{car}}</h4>
        <h4 v-if="toy">获取到儿子的玩具:{{ toy }}</h4>
        <Child :car="car" :sendToy="getToy"></Child>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Props'
    }
</script>

<script setup lang="ts">
    import Child from './Child.vue'
    import {ref} from 'vue'

    // 数据
    let car = ref('benz')
    let toy = ref('')

    // 方法
    function getToy(val:string){
        toy.value = val
    }

</script>

<style scoped>
    div.father {
        margin: 30px;
        background-color: yellow;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

Child.vue如下:

<template>
    <div class="child">
        <h2>这是子组件</h2>
        <h4>玩具:{{ toy }}</h4>
        <h4>父亲给的车:{{ car }}</h4>
        <button @click="sendToy(toy)">点击把玩具交给父亲</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child'
    }
</script>

<script setup lang="ts">
    import { ref } from 'vue'

    // 数据
    let toy = ref('atm')

    defineProps(['car', 'sendToy'])
</script>

<style scoped>
    div.child {
        background-color: gray;
        width: 80%;
        height: 70%;
        margin-left: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

27. 组件通信_custom-event

标签中使用@开头的props表示的是事件,比如@click是点击事件,如果我们需要打印事件,我们可以使用$event当做参数放入函数中获取事件;

事件一般用串串的形式,而回调函数使用小驼峰

Father.vue 代码如下:

<template>
    <div class="father">
        <h2>这是父组件</h2>
        <h3 v-if="toy">儿子给的玩具{{ toy }}</h3>

        <!-- 这里send-toy表示时间的名字,getToy表示事件触发时使用的函数回调 -->
        <!-- 事件一般用串串的形式,而回调函数使用小驼峰 -->
        <Child @send-toy="getToy"></Child>

        <!-- 这里可以使用$event 提取出事件进行打印 -->
        <!-- <Child @send-toy="getToy(param1, param2, $event)"></Child> -->

    </div>
</template>

<script lang="ts">
    export default {
        name: 'Event'
    }
</script>

<script setup lang="ts">
    import Child from './Child.vue'
    import {ref} from 'vue'

    let toy = ref('')

    function getToy(val:string){
        toy.value = val
    }

</script>

<style scoped>
    div.father {
        margin: 30px;
        background-color: yellow;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

Child.vue 代码如下:

<template>
    <div class="child">
        <h2>这是子组件</h2>
        <h3>儿子的玩具{{ toy }}</h3>
        <button @click="emit('send-toy', toy)"> 发送给父亲</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child'
    }
</script>

<script setup lang="ts">
    import {ref} from 'vue'
    let toy = ref('atm')

    let emit = defineEmits(['send-toy'])

</script>

<style scoped>
    div.child {
        background-color: gray;
        width: 80%;
        height: 70%;
        margin-left: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

28. 组件通信_mitt

类似于总线bus,收数据的:在公共区域提前绑定好事件;提供数据的:在合适的时候触发事件;

首先需要安装mitt

npm i mitt

由于mitt属于一个工具,所以在src目录下创建一个utils文件夹,创建一个emitter.ts文件;

emitter.ts 文件内容如下:

// 引入 mitt
import mitt from 'mitt'

// 调用mitt得到emitter,emitter可以绑定事件和触发事件
const emitter = mitt()

// 绑定事件
emitter.on('test1', ()=>{
    console.log('test1被调用了')
})
emitter.on('test2', ()=>{
    console.log('test2被调用了')
})

// 触发事件
setInterval(()=>{
    emitter.emit('test1')
    emitter.emit('test2')
}, 2000)

// 解绑事件
setTimeout(()=>{
    emitter.off('test1')

    // 全部解绑
    // emitter.all.clear()
}, 3000)

// 暴露emitter
export default emitter

同时,需要在mian.ts文件中进行引入:

import {createApp} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
import router from './router'
import emitter from './utils/emitter'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)

app.mount('#app')

注意,这里不需要app.use;

Child1.vue代码如下:

<template>
    <div class="child">
        <h2>这是子1</h2>
        <h3> 玩具 {{toy}}</h3>
        <button @click="emitter.emit('send-toy', toy)">点击发送玩具</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child1'
    }
</script>

<script setup lang="ts">
    import emitter from '@/utils/emitter';
    import {ref} from 'vue'


    let toy = ref('atm')

</script>

<style scoped>
    div.child {
        background-color: gray;
        width: 80%;
        height: 70%;
        margin-left: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

Child2.vue代码如下:

<template>
    <div class="child">
        <h2>这是子2</h2>
        <h3> 电脑 {{computer}}</h3>
        <h3 v-if="toy"> 玩具 {{toy}}</h3>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child2'
    }
</script>

<script setup lang="ts">
    import {ref, onUnmounted} from 'vue'
    import emitter from '@/utils/emitter';
    

    let computer = ref('wxr')
    let toy = ref('')

    emitter.on('send-toy', (val:any)=>{
        toy.value = val
    })

    // 防止被卸载后占用内存
    onUnmounted(()=>{
        emitter.off('send-toy')
    })


</script>

<style scoped>
    div.child {
        background-color: gray;
        width: 80%;
        height: 70%;
        margin-left: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

29. 组件通信_v-model

UI组件库底层大量使用v-model进行通信;

e v e n t :对于原生事件, event: 对于原生事件, event:对于原生事件,event就是事件对象; 能==> .target
对于自定义事件,$event就是触发事件时,所传递的数据; 不能==> .target

Father.vue如下:

<template>
    <div>
        <h2>父组件</h2>
        <h4>value : {{ username }}</h4>
        <!-- v-model用在html标签上 下面两种方法等价-->
        <!-- <input type="text" v-model="username"> -->
        <!-- <input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value"> -->
    
        <!-- v-model用在组件标签上 下面两种方法等价-->
        <!-- <MyCustomInput v-model="username"></MyCustomInput> -->
        <!-- <MyCustomInput :modelValue="username" @update:modelValue="username = $event"></MyCustomInput> -->

        <!-- 自定义modelValue 需要把UI组件中所有的modelValue改成qwe --> 
        <MyCustomInput v-model:qwe="username"></MyCustomInput>


    </div>
</template>

<script lang="ts">
    export default {
        name: 'Model'
    }
</script>

<script setup lang="ts">
    import {ref} from 'vue'
    import MyCustomInput from './MyCustomInput.vue';

    let username = ref('zhangsan')

</script>

<style scoped>

</style>

自定义组件MyCustomInput.vue如下:

<!-- 要是用v-model,必须要modelValue和update:modelValue -->
<template>
    <div>
        <input 
            type="text" 
            :value="modelValue"
            @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)"
        >
    </div>
</template>

<script lang="ts">
    export default {
        name: 'MyCustomInput'
    }
</script>

<script setup lang="ts">
    defineProps(['modelValue'])
    const emit =  defineEmits(['update:modelValue'])

</script>

<style scoped>
    input {
        border: 2px solid black;
        background-image: linear-gradient(45deg, red, yellow, green);
        height: 30px;
        font-size: 20px;
        color: white;
    }

</style>

30. 组件通信_$attrs

使用props,未用defineProps的数据,子组件会使用attrs接收;

Father.vue

<template>
    <div class="father">
        <h2> 父组件 </h2>
        <h4> {{ data }} </h4>
        <!-- v-bind="{x: 100, y:200}" 相当于 :x="100" :y="200" -->
        <Child v-bind="data"></Child>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'AttrsListeners'
    }
</script>

<script setup lang="ts">
    import Child from './Child.vue'
    import {reactive} from 'vue'

    let data = reactive({
        a: 100,
        b: 200,
        c: 300
    })


</script>

<style scoped>
    div.father {
        margin: 30px;
        background-color: yellow;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

Child.vue

<template>
    <div class="child">
        <h2> 子组件 </h2>
        <Grandson v-bind="$attrs"> </Grandson>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child'
    }
</script>

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

<style scoped>
    div.child {
        margin: 30px;
        background-color: red;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

Grandson.vue

<template>
    <div class="grandson">
        <h2> 孙组件 </h2>
        <h4> {{ $attrs }}</h4>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Grandson'
    }
</script>

<script setup lang="ts">


</script>

<style scoped>
    div.grandson {
        margin: 30px;
        background-color: green;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

31. 组件通信_$refs$parent

$refs可以获取html下所有的ref组件,$parent可以获取父组件中的数据;都要结合defineExpose()

Father.vue 代码如下:

<template>
    <div>
        <h2> 父组件 </h2>
        <h4>房产:{{house}}</h4>
        <button @click="changeToys">改变c1的toys</button>
        <button @click="changeComputer">改变c2的computer</button>
        <button @click="getAllChild($refs)">获取所有的子组件实例对象</button>
        <Child1 ref="c1"/>
        <Child2 ref="c2"/>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'RefChildrenParent'
    }
</script>

<script setup lang="ts">
    import Child1 from './Child1.vue'
    import Child2 from './Child2.vue'
    import {ref} from 'vue'

    let house = ref(3)
    let c1 = ref()
    let c2 = ref()

    function changeToys(){
        c1.value.toys = '修改后的玩具'
    }
    function changeComputer(){
        c2.value.computer = '修改后的电脑'
    }

    // refs:{[key:string]:any} 或者 refs:any
    function getAllChild(refs:{[key:string]:any}){
        for(let index in refs){
            refs[index].books += 3
        }
    }

    defineExpose({house})
</script>

<style scoped>

</style>

Child1.vue 代码如下:

<template>
    <div>
        <h2> 子组件1 </h2>
        <h4>玩具:{{ toys }}</h4>
        <h4>书籍:{{ books }}</h4>
        <button @click="minusHouse($parent)">干掉父亲的一套房产</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child1'
    }
</script>

<script setup lang="ts">
    import {ref} from 'vue'

    let toys = ref('asdsdsaxzczxc')
    let books = ref(3)

    function minusHouse(parent:any){
        parent.house -= 1
    }

    defineExpose({toys, books})
</script>

<style scoped>

</style>

Child2.vue 代码如下:

<template>
    <div>
        <h2> 子组件1 </h2>
        <h4>电脑:{{ computer }}</h4>
        <h4>书籍:{{ books }}</h4>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Child2'
    }
</script>

<script setup lang="ts">
    import {ref} from 'vue'

    let computer = ref('asdas')
    let books = ref(3)

    defineExpose({computer, books})
</script>

<style scoped>

</style>

32. 组件通信_provide-inject

常用于祖孙之间越过父亲直接通信;

祖使用provide传送,孙使用inject接收,inject第二个参数是默认值

Father.vue

<template>
    <div class="father">
        <h2> 父组件 </h2>
        <h4>钱:{{ money }}</h4>
        <h4 v-for="car in cars">车:{{ car.brand }} 价值 :{{ car.price }}</h4>
        <Child></Child>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'ProvidedInject'
    }
</script>

<script setup lang="ts">
    import Child from './Child.vue'
    import {ref, reactive, provide} from 'vue'

    let money = ref(100)
    let cars = reactive([{
        brand: 'benz',
        price: 1000,
    }])

    // 向后代提取数据
    provide('qian', money)
    provide('che', cars)

</script>

<style scoped>
    div.father {
        margin: 30px;
        background-color: yellow;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

Child.vue

<template>
    <div class="grandson">
        <h2> 孙组件 </h2>
        <h4> 钱:{{ money }}</h4>
        <h4 v-for="car in cars">车:{{ car.brand }} 价值 :{{ car.price }}</h4>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Grandson'
    }
</script>

<script setup lang="ts">
    import {inject} from 'vue'

    //  这里inject第二个参数是默认值
    let money = inject('qian', '我是默认值')
    let cars = inject('che', {})

</script>

<style scoped>
    div.grandson {
        margin: 30px;
        background-color: green;
        width: 500px;
        height: 300px;
        border-radius: 10px;
        box-shadow: 0 0 10px;
    }
</style>

32. 组件通信_slot默认插槽

slot有三种:默认插槽,具名插槽,作用域插槽;

默认插槽就是在组件中间使用html标签,然后在组件中加入<slot></slot>

Father.vue 如下:

<template>
    <div class="father">
        <Category>
            <h2> 热门游戏列表</h2>
            <ul>
                <li v-for="game in games" :key="game.id">{{ game.name }}</li>
            </ul>
        </Category>
        <Category>
            <template v-slot:default>
                <h2> 热门美食列表</h2>
                <img :src="imgUrl" alt="">
            </template>
        </Category>
        <Category>
            <h2> 热门美食列表</h2>
            <video :src="videoUrl" controls> </video>
        </Category>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Slot'
    }
</script>

<script setup lang="ts">
    import Category from './Category.vue';
    import {ref, reactive} from 'vue'

    let games = reactive([
        {id: 'zxzczxczxc01', name: '爱谁谁大所多'},
        {id: 'zxzczxczxc02', name: '爱阿萨德按所多'},
        {id: 'zxzczxczxc03', name: '爱自行车在线所多'},
        {id: 'zxzczxczxc04', name: '爱谁周传雄所多'},
    ])

    let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
    let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')

</script>

<style scoped>
    .father {
        display: flex;
        background-color: rosybrown;
        padding: 20px;
        border-radius: 10px;
        justify-content: space-evenly;
        width: 100%;
    }
    img, video {
        width: 100%;
    }

</style>

Child.vue 如下:

<template>
    <div class="category">
        <slot></slot>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Category'
    }
</script>

<script setup lang="ts">

</script>

<style scoped>
    .category {
        background-color: skyblue;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        padding: 10px;
        width: 200px;
        height: 300px;
    }
</style>

33. 组件通信_slot具名插槽

具名插槽意思就是具有名字的插槽,这里默认插槽的名字为default,多个slot可以倒序;

Category.vue

<template>
    <div class="category">
        <slot name="s1"></slot>
        <slot name="s2"></slot>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Category'
    }
</script>

<script setup lang="ts">

</script>

<style scoped>
    .category {
        background-color: skyblue;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        padding: 10px;
        width: 200px;
        height: 300px;
    }
</style>

Father.vue

<template>
    <div class="father">
        <Category>
            <template #s1>
                <h2> 热门游戏列表 </h2>
            </template>
            <template v-slot:s2>
                <ul>
                    <li v-for="game in games" :key="game.id">{{ game.name }}</li>
                </ul>
            </template>
        </Category>
        <Category>
            <template #s2>
                <img :src="imgUrl" alt="">
            </template>
            <template #s1>
                <h2> 热门美食列表 </h2>
            </template>
        </Category>
        <Category>
            <template #s2>
                <video :src="videoUrl" controls> </video>
            </template>
            <template #s1>
                <h2> 热门美食列表 </h2>
            </template>
        </Category>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Slot'
    }
</script>

<script setup lang="ts">
    import Category from './Category.vue';
    import {ref, reactive} from 'vue'

    let games = reactive([
        {id: 'zxzczxczxc01', name: '爱谁谁大所多'},
        {id: 'zxzczxczxc02', name: '爱阿萨德按所多'},
        {id: 'zxzczxczxc03', name: '爱自行车在线所多'},
        {id: 'zxzczxczxc04', name: '爱谁周传雄所多'},
    ])

    let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
    let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')

</script>

<style scoped>
    .father {
        display: flex;
        background-color: rosybrown;
        padding: 20px;
        border-radius: 10px;
        justify-content: space-evenly;
        width: 100%;
    }
    img, video {
        width: 100%;
    }

</style>

34. 组件通信_slot作用域插槽

主要用在子里面有数据,但是父组件想以不同形式进行表示;

Game.vue

<template>
    <div class="game">
        <h2>游戏列表</h2>
        <slot :youxi="games"></slot>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Category'
    }
</script>

<script setup lang="ts">
    import {reactive} from 'vue'

    let games = reactive([
        {id: 'zxzczxczxc01', name: '爱谁谁大所多'},
        {id: 'zxzczxczxc02', name: '爱阿萨德按所多'},
        {id: 'zxzczxczxc03', name: '爱自行车在线所多'},
        {id: 'zxzczxczxc04', name: '爱谁周传雄所多'},
    ])
</script>

<style scoped>
    .game {
        background-color: skyblue;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        padding: 10px;
        width: 200px;
        height: 300px;
    }
</style>

Father.vue

<template>
    <div class="father">
        <Game>
            <!-- 子组件中slot传的props中所有的数据都在a的手里-->
            <template v-slot="{youxi}">
                <ul>
                    <li v-for="y in youxi" :key="y.id"> {{ y.name }}</li>
                </ul>
            </template>
        </Game>
        <Game>
            <!-- 子组件中slot传的props中所有的数据都在a的手里-->
            <template v-slot="{youxi}">
                <ol>
                    <li v-for="y in youxi" :key="y.id"> {{ y.name }}</li>
                </ol>
            </template>
        </Game>
        <Game>
            <!-- 子组件中slot传的props中所有的数据都在a的手里-->
            <template v-slot="{youxi}">
                <h3 v-for="y in youxi" :key="y.id"> {{ y.name }}</h3>
            </template>
        </Game>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Slot'
    }
</script>

<script setup lang="ts">
    import Game from './Game.vue';


</script>

<style scoped>
    .father {
        display: flex;
        background-color: rosybrown;
        padding: 20px;
        border-radius: 10px;
        justify-content: space-evenly;
        width: 100%;
    }
    img, video {
        width: 100%;
    }

</style>

35. shallowRef 和 shallowReactive

36. readonly 和 shallowReadonly

37. toRaw 和 markRaw

38. customRef

customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

示例 创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
	  // 数据被读取时,调用get
      get() {
        track() // 告诉vue数据value很重要,一旦value变化就去更新
        return value
      },
      // 数据被修改时,调用set
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger() // 通知一下vue数据value变化了
        }, delay)
      }
    }
  })
}

39. Teleport

这个组件中有一个 <button> 按钮来触发打开模态框,和一个 class 名为 .modal 的 <div>,它包含了模态框的内容和一个用来关闭的按钮。

当在初始 HTML 结构中使用这个组件时,会有一些潜在的问题:

  • position: fixed 能够相对于浏览器窗口放置有一个条件,那就是不能有任何祖先元素设置了 transformperspective 或者 filter 样式属性。也就是说如果我们想要用 CSS transform 为祖先节点 <div class="outer"> 设置动画,就会不小心破坏模态框的布局!

  • 这个模态框的 z-index 受限于它的容器元素。如果有其他元素与 <div class="outer"> 重叠并有更高的 z-index,则它会覆盖住我们的模态框。

<Teleport> 提供了一个更简单的方式来解决此类问题,让我们不需要再顾虑 DOM 结构的问题。让我们用 <Teleport> 改写一下 <MyModal>

<button @click="open = true">Open Modal</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>

40. Suspense

在这个组件树中有多个嵌套组件,要渲染出它们,首先得解析一些异步资源。如果没有 <Suspense>,则它们每个都需要处理自己的加载、报错和完成状态。在最坏的情况下,我们可能会在页面上看到三个旋转的加载态,在不同的时间显示出内容。

有了 <Suspense> 组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。

<Suspense> 可以等待的异步依赖有两种:

  1. 带有异步 setup() 钩子的组件。这也包含了使用 <script setup> 时有顶层 await 表达式的组件。
  2. 异步组件。
<Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <Dashboard />

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

41. 全局api转化为应用对象

import {createApp} from 'vue'
import App from './App.vue'
import Hello from './Hello.vue'
//创建应用
const app = createApp(App)

// 定义全局组件
app.component('Hello',Hello)

// 定义全局属性
app.config.globalProperties.x = 99
declare module 'vue'{
    interface ComponentCustomProperties{
        x: number
    }
}

// 定义样式 对组件使用v-beauty改变样式
app.directive('beauty',(element,{value})=>{
    element.innerText += value
    element.style.color = 'green'
    element.style.backgroundColor = 'yellow'
})

// app挂载
app.mount('#app')

// app卸载
setTimeout(()=>{
    app.unmount()
}, 3000)

42. 其他

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/430776.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类和对象终篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. 友元2.1 友元函数2.2 友元类 3. 内部类4. 匿名对象5. 拷贝对象时的一些编译器优化6. 再次理解类和对象 1. 前言 在上一篇博客中提到了类和对象中的构造函数与stat…

网盘拉新平台,如何授权对接“星子助推”?

找到“星子助推”&#xff1a;首先&#xff0c;找到“星子助推”这个授权渠道。他们是网盘服务提供商的合作伙伴&#xff0c;为你提供机会。注册并申请授权&#xff1a;在“星子助推”的平台上注册&#xff0c;并同时申请授权。填写邀请码8x25k&#xff0c;提交申请。获得授权并…

云服务器2核4G能支持多少人同时访问?2核4G5M并发量评测

腾讯云轻量应用服务器2核4G5M配置一年优惠价165元、252元15个月、三年756元&#xff0c;100%CPU性能&#xff0c;5M带宽下载速度640KB/秒&#xff0c;60GB SSD系统盘&#xff0c;月流量500GB&#xff0c;折合每天16.6GB流量&#xff0c;超出月流量包的流量按照0.8元每GB的价格支…

#QT(网络编程-UDP)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a;UDP 不分客户端和服务端 3.记录 &#xff08;1&#xff09;做一个UI界面 &#xff08;2&#xff09;编写open按钮代码进行测试&#xff08;用网络调试助手测试&#xff09; &#xff08;3&#xff09;完善其他功能测试 4.代码 …

设计模式——2_3 迭代器(Iterator)

生活就像一颗巧克力&#xff0c;你永远不知道下一颗是什么味道 ——《阿甘正传》 文章目录 定义图纸一个例子&#xff1a;假如你的供应商提供了不同类型的返回值单独的遍历流程实现 碎碎念如果读写同时进行会发生啥&#xff1f;外部迭代和内部迭代迭代器和其他模式迭代器和组合…

AI从截图直接生成代码、前端程序员的福音

简介 项目可以将任何屏幕截图或设计转换为干净的代码&#xff08;支持大多数框架&#xff09;。来自领先公司的开发人员和设计师使用的排名第一的工具。完全开源&#xff0c;在 GitHub 上拥有超过 35,000 颗星。非常受欢迎。 各位小伙伴们感觉有帮助的&#xff0c;可以收藏一…

【促销定价】背后的算法技术3-数据挖掘分析

【促销定价】背后的算法技术3-数据挖掘分析 01 整体分析1&#xff09;整体概览2&#xff09;类别型特征概览3&#xff09;数值型特征概览 02 聚合分析1&#xff09;天维度2&#xff09;品维度3&#xff09;价格维度4&#xff09;数量维度 03 相关分析1&#xff09;1级品类2&…

指针中的回调函数与qsort的深度理解与模拟

今天给大家在更新一下指针类型的知识&#xff0c;这里讲到了一个库函数sqort&#xff0c;以及回调函数的理解。 望喜欢 目录 回调函数 qsort函数 qsort模拟实现 回调函数 回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数…

利用文件实现进程间共享数据

概述 文件可以存储任何非结构化字节序列&#xff0c;这个比较简单&#xff0c;就一个写一个读&#xff1b;学习到此&#xff0c;留个记录&#xff0c;以后可以直接抄代码&#xff0c;哈哈 Demo代码 #include <fstream> #include <iostream> #include <thread&…

CMIP6数据处理方法与典型案例分析

气候变化对农业、生态系统、社会经济以及人类的生存与发展具有深远影响&#xff0c;是当前全球关注的核心议题之一。IPCC&#xff08;Intergovernmental Panel on Climate Change&#xff0c;政府间气候变化专门委员会&#xff09;的第六次评估报告明确&#xff1b;指出&#x…

职场卷王:我用可视化大屏模板做工作汇报,惊艳了同事和领导。

2023结束了&#xff0c;我和我的小伙伴们纷纷开始忙碌的年终总结和汇报。 正忙着汇总Excel数据、写word讲稿、找PPT模板时&#xff0c;我发现隔壁组的老王独自在大会议室偷偷调试起了那台汇报用的电视机。 不会吧不会吧&#xff0c;年终汇报还有一周呢&#xff0c;这家伙PPT都…

Java中文件的相关知识及文件IO操作

在我们日常生活中&#xff0c;会把许多东西都称之为文件。比如&#xff0c;一份纸质报告&#xff0c;或u盘中的一些文档&#xff0c;都会把它们称为文件。那么&#xff0c;这里说的文件是以操作系统的角度出发的。在操作系统中&#xff0c;会把许多硬件设备和软件资源都抽象成“…

Kafka | SpringBoot集成Kafka

SpringBoot集成Kafka 一、前言二、项目1. pom2. application.properties4. 消息生产者-测试5. 消息消费者 三、启动测试四、有总结的不对的地方/或者问题 请指正, 我在努力中 一、前言 该文章中主要对SpringBoot 集成Kafka 主要是 application.properties 与 pom坐标就算集成完…

win11系统中nginx简单的代理配置

一.背景 为了公司安排的师带徒任务。 操作系统版本&#xff1a;win11家庭版 nginx版本&#xff1a;1.24.0 二.配置代理 之前文章已经说明了nginx简单的安装&#xff0c;要看阅读这个文章哈。web服务器nginx下载及在win11的安装-CSDN博客 1.配置需求识别 前端服务nginx(80…

【面试题】webpack的五大核心、构建流程、性能优化

【面试题】webpack的五大核心、webpack的构建流程、webpack的性能优化 webpack是什么?webpack的五大核心webpack的构建流程webpack性能优化 webpack是什么? js静态模块打包工具。 功能 将多个文件打包成更小的文件&#xff0c;(压缩)翻译 babal-loader es6进行降级兼容。 …

低代码:数智化助力新农业发展

随着科技的飞速发展和数字化转型的深入推进&#xff0c;低代码开发平台正逐渐成为软件开发的热门话题。尤其在农业领域&#xff0c;低代码技术为传统农业注入了新的活力&#xff0c;助力新农业实现高效、智能的发展。 低代码开发平台的概念与特点 随着科技的飞速发展&#xff0…

猫咪冻干的价格差别为什么那么大?价格实惠的主食冻干分享

随着养猫科学知识的普及&#xff0c;越来越多的铲屎官选择更符合猫咪饮食天性的主食冻干喂养。尽管有些铲屎官因价格犹豫&#xff0c;但像我这样的资深铲屎官深知其益处。尽管其价格稍高于烘焙粮和膨化粮&#xff0c;但主食冻干为猫咪健康带来的实际好处是无法估量的。 对于像我…

代码学习记录11

随想录日记part11 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.04 主要内容&#xff1a;今天的主要内容是深入了解栈和队列中比较难的题录类型&#xff1a;滑动窗口最大值与前 K K K 个高频元素&#xff0c;最后对于这三天学习的队列和栈的知识进行总结。…

结构体详解

结构体 什么是结构体 结构体是一种用户自定义的数据类型&#xff0c;可以组合多个相关值成为一个单一类型。它是由一批数据组合而成的结构型数据&#xff0c;结构体可以包含多个不同类型的字段&#xff0c;如基本数据类型、其他结构体、枚举类型等。在Rust中&#xff0c;结构…

Ubantu 18.04 配置固定IP

1.首先在终端里输入命令,将你的网关和ip&#xff0c;记下来 ifconfig 2. 执行命令&#xff1a; sudo gedit /etc/network/interfaces 3.在弹出来的框里输入 auto后面的就是网关&#xff0c;address是你虚拟机的ip&#xff0c;gateway是你的网关ip&#xff0c;netmask是你的子…
最新文章