Vue3 五天速成(下)

简介: Vue3 五天速成(下)

Vue3 五天速成(中)https://developer.aliyun.com/article/1504166?spm=a2c6h.13148508.setting.28.36834f0eJwPRIa

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标签,然后在组件中加入;

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() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 getset 方法的对象。

一般来说,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

这个组件中有一个  按钮来触发打开模态框,和一个 class 名为 .modal

,它包含了模态框的内容和一个用来关闭的按钮。

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

  • position: fixed 能够相对于浏览器窗口放置有一个条件,那就是不能有任何祖先元素设置了 transformperspective 或者 filter 样式属性。也就是说如果我们想要用 CSS transform 为祖先节点

    设置动画,就会不小心破坏模态框的布局!

  • 这个模态框的 z-index 受限于它的容器元素。如果有其他元素与

    重叠并有更高的 z-index,则它会覆盖住我们的模态框。

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

<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

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

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

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

  1. 带有异步 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. 其他



目录
相关文章
|
3天前
|
JavaScript 前端开发 CDN
vue3速览
vue3速览
12 0
|
3天前
|
设计模式 JavaScript 前端开发
Vue3报错Property “xxx“ was accessed during render but is not defined on instance
Vue3报错Property “xxx“ was accessed during render but is not defined on instance
|
3天前
|
JavaScript API
Vue3 官方文档速通(中)
Vue3 官方文档速通(中)
18 0
|
3天前
|
缓存 JavaScript 前端开发
Vue3 官方文档速通(上)
Vue3 官方文档速通(上)
20 0
|
3天前
Vue3+Vite+Pinia+Naive后台管理系统搭建之五:Pinia 状态管理
Vue3+Vite+Pinia+Naive后台管理系统搭建之五:Pinia 状态管理
8 1
|
3天前
Vue3+Vite+Pinia+Naive后台管理系统搭建之三:vue-router 的安装和使用
Vue3+Vite+Pinia+Naive后台管理系统搭建之三:vue-router 的安装和使用
7 0
|
3天前
Vue3+Vite+Pinia+Naive后台管理系统搭建之二:scss 的安装和使用
Vue3+Vite+Pinia+Naive后台管理系统搭建之二:scss 的安装和使用
6 0
|
3天前
|
JavaScript 前端开发 API
Vue3 系列:从0开始学习vue3.0
Vue3 系列:从0开始学习vue3.0
9 1
|
3天前
|
网络架构
Vue3 系列:vue-router
Vue3 系列:vue-router
8 2
|
3天前
Vue3+Vite+Pinia+Naive后台管理系统搭建之一:基础项目构建
Vue3+Vite+Pinia+Naive后台管理系统搭建之一:基础项目构建
7 1