Vue3+TS知识点补充

news2024/9/29 11:33:17

一、关于Ref

1.shallowRef()

shallowRef 是 Vue 3 中新引入的响应式数据类型之一,它与 ref 类型非常相似,但是有一些不同点。

不同的是shallowRef 只会对其包装的对象进行浅层次的响应式处理即如果这个对象的子属性发生改变,那么这个改变不会被响应到视图中

<template>
  <div>{{ Man }}</div>
  <button @click="change">修改</button>
</template>

<script setup lang="ts">
import { shallowRef } from "vue";
const Man = shallowRef({ name: "小4" });

const change = () => {
  Man.value.name = "小5";
  console.log(Man);
};
</script>

<style lang="scss" scoped></style>

在这里插入图片描述
需要注意的是,如果响应函数里有Ref,则shallowRef也会变成深响应式,浅响应式shallowRef会被深响应式ref影响。
在这里插入图片描述
shallowRef 的应用场景一般是在某些需要保持数据响应式特性的情况下使用,但同时也要避免做出过多的响应式开销。比如:

  • 针对复杂的大型数据对象,如果其中只有部分属性需要在组件渲染时被访问或监听,可以考虑使用 shallowRef 来对这些属性进行响应式封装,减少响应式追踪所需的开销
  • 在某些自定义 hook 中,为了确保能够及时地更新相关的状态或数据,可以使用 shallowRef 来跟踪某个对象的属性,当其中某些属性发生变化时,就能立即执行后续的逻辑处理。
  • 在与 Vuex 进行状态管理的时候,有些大型对象可能只需要针对部分属性进行响应式处理,此时使用 shallowRef 可以有效减少响应式系统的压力

2.triggerRef()

可以强制更新浅响应式的数据

具体来说,triggerRef 接收一个 ref 对象,并立即触发它的 setter 函数来更新该 ref 的值。这条更新操作会自动触发组件重新渲染,从而达到实时更新视图的效果。

let message: Ref<Obj> = shallowRef({
  name: "小5"
})
 
const changeMsg = () => {
  message.value.name = '大5'
  // 浅响应变深响应
  triggerRef(message)
 }

triggerRef的用途比较灵活,可以在多种场景下使用。例如:

  • 某些情况下需要根据用户输入实时更新数据显示,此时可以为相应的 ref对象设置监听器,在变化时使用triggerRef强制更新。
  • 在某些异步加载数据的情况下,可能需要等待一些时间才能获取到完整的数据。如果依赖于这些数据的组件需要及时更新,也可以将这些关键数据封装进ref对象中,在异步任务完成后使用triggerRef更新数据。

需要注意的是,triggerRef 虽然可以强制触发视图更新,但是不建议频繁使用。过度使用会导致性能问题和代码可读性下降。通常情况下,Vue 的响应式系统会自动检测数据变化并触发更新,只有在必要的情况下才考虑手动触发。

深响应式ref底层会自动调用triggerRef,因此要实现浅响应式,不能把深响应式放在一块

3.customRef()

customRef用于创造一个自定义的ref类型。与ref及其变体如 shallowRef 不同,customRef允许我们自己决定如何处理设置(ref.value)并得到(ref.value)读取操作
具体地说,customRef 接收一个工厂函数 (track, trigger),该函数需要返回一个对象,该对象包含了 getset 两个方法。这两个方法的作用分别对于 ref.value 的读取和更新操作,可以自行定义它们的实现逻辑。同时,还需要注意在读取或者更新值时手动调用 tracktrigger 函数,以便进行依赖追踪和触发更新:

import { customRef } from 'vue'

const myCustomRef = customRef((track, trigger) => {
  let value = 0

  return {
    get() {
      // 依赖追踪
      track()

      return value
    },

    set(newValue) {
      // 更新值
      value = newValue

      // 触发组件重新渲染
      trigger()
    }
  }
})

customRef 的应用场景比较灵活,通常用于以下情况:

  • 当需要对一些数据的读取和更新进行特殊的逻辑处理时,例如将数据进行格式化或者验证等,可以使用 customRef 来自定义 ref 的行为。
  • 当需要对一些本身不可响应的数据(例如函数或 DOM 元素)进行响应式处理时,可以使用 customRef 来替代 refreactive 等原有的响应式 API。

需要注意的是,customRef 是一个相对高级的 API,在开发中建议慎用。同时在对其使用时,也需要关注性能、代码可读性及其使用场景等因素。

二、关于Reactive

1.reactive()

reactive定义的对象是不能直接赋值,否则会破坏响应式对象

import { reactive } from "vue";

let list = reactive<string[]>([]);

const add = () => {
  setTimeout(() => {
    let res = ["EDG", "RNG", "JDG"];
    console.log(list);
    list.push(...res); // 正确的修改reactive对象方法
    // list = res;  // reactive proxy 不能直接赋值,否则会破坏响应式对象
    console.log(list);
  }, 2000);
};

在这里插入图片描述

2.readonly()

把属性变成只读的

const info = ref('xiao5')
const newInfo = readonly(info)
newInfo.value = '555555'  // 会报错,提示newInfo是只读属性

3.shallowReactive()

和shallowRef类似。

三、关于to系列

1.toRef()

toRef用于创建一个针对源对象的响应式引用它将一个对象的属性转换为单独的 ref,以便更容易地进行响应式处理

具体来说,toRef 接收一个源对象和一个属性键名作为参数,并返回一个 ref 对象。这个 ref 对象会自动跟踪源对象中对应属性的变化,并在变化时更新视图。

下面是一个使用 toRef 的例子:

import { toRef } from 'vue'

const myObj = {
  name: "vue",
  version: "3.2"
}
const myRef = toRef(myObj, "version")

上述代码中,我们创建了一个名为 myObj 的对象,并通过 toRef 将其中的 version 属性转换为一个 ref 对象。这样,当我们修改 myObj.version 的值时,myRef.value 的值也会随之更新。

有了 toRef,我们就可以方便地将源对象中特定的属性进行响应式处理,而不需要将整个源对象都转化为响应式对象或者手动为每个属性创建 ref

需要注意的是,**toRef 返回的 ref 对象本质上只是一个引用,而非复制。**因此如果在程序中同时存在多个对同一个 ref 对象的引用,那么它们之间的关系和更新状态是相互影响的。

2.toRefs()

toRefs可以帮我们批量创建ref对象,主要是方便我们解构reactive

import {reactive, toRefs} from 'vue'
const obj = reactive({
    foo: 1,
    bar: 1
})

let {foo, bar} = toRefs(obj)
foo.value++;
console.log(foo.value, bar.value); // 2 1

3.toRaw()

将响应式对象转换成普通对象

四、computed计算属性

计算属性就是当该计算属性所依赖的属性的值发生变化的时候,才会触发该计算属性的更改

1.函数形式

import { computed, reactive, ref } from 'vue'
let price = ref(0)//$0
 
let m = computed<string>(()=>{
   return `$` + price.value
})
 
price.value = 500

注意:函数形式只能get,不能set

2.对象形式

<template>
   <div>{{ mul }}</div>
   <div @click="mul = 100">click</div>
</template>
 
<script setup lang="ts">
import { computed, ref } from 'vue'
let price = ref<number | string>(1)  //$0
let mul = computed({
   get: () => {
      return price.value
   },
   set: (value) => {
      price.value = 'set' + value
   }
})
</script>

对象形式能get,也能set

3.computed之购物车案例

<template>
  <div>
    <input type="text" placeholder="搜索" v-model="keyword"/>
    <div style="margin-top: 20px">
      <table border width="600" cellpadding="0" cellspacing="0">
        <thead>
          <tr>
            <th>物品名称</th>
            <th>物品单价</th>
            <th>物品数量</th>
            <th>物品总价</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="item in filterData" :key="item.name">
            <td>{{ item.name }}</td>
            <td>{{ item.price }}</td>
            <td>
              <button @click="item.num > 0 ? item.num-- : item.num">-</button
              >{{ item.num }} <button @click="item.num++">+</button>
            </td>
            <td>{{ item.num * item.price }}</td>
            <td><button @click="delItem(item)">删除</button></td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <td colspan="5" align="right">总价:{{ sumPrice }}</td>
          </tr>
        </tfoot>
      </table>
    </div>
  </div>
</template>

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

interface Data {
  name: string;
  price: number;
  num: number;
}
let keyword = ref<string>('')
// 计算属性实现搜索功能
let filterData = computed(() => {
  return data.filter(item => {
    return item.name.includes(keyword.value)
  })
})
let data = reactive<Data[]>([
  {
    name: "小5的绿毛",
    price: 500,
    num: 1,
  },
  {
    name: "小5的红衣",
    price: 5000,
    num: 2,
  },
  {
    name: "小5的黑袜",
    price: 500000,
    num: 1,
  },
]);
// 计算属性总价
let sumPrice = computed<number>(() => {
  return data.reduce((preValue:number, item:Data) => {
    return preValue + item.num * item.price;
  }, 0);
});

const delItem = (item: Data) => {
  const index:number = data.findIndex((item1) => item1 == item);
  data.splice(index, 1);
};
</script>

<style lang="scss" scoped></style>

在这里插入图片描述

五、watch监视属性

1.watch

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    	console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据
    			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
    			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
    

2.watchEffect函数

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

高级侦听器补充

六、组件的生命周期

使用setup语法糖模式是没有beforeCreatecreated这两个周期的。
所以生命周期的一般顺序是:
Setup => Mounted => Updated => unMounted
在这里插入图片描述
组件A

<template>
  <h2>hello!!</h2>
  <!-- 测试Mounted前后 -->
  <h2>测试Mounted前后</h2>
  <div ref="div">{{ divContent }}</div>
  <!-- 测试Updated前后 -->
  <h2>测试Updated前后</h2>
  <button @click="update">update</button>
</template>

<script setup lang="ts">
import {
  onBeforeMount,
  onBeforeUnmount,
  onBeforeUpdate,
  onMounted,
  onUnmounted,
  onUpdated,
  ref,
} from "vue";
let num = ref(0);
let divContent = ref<string>("content");
let div = ref<HTMLDivElement>();
const update = () => {
  num.value++;
  divContent.value = "我被改啦";
};
console.log("setup");
onBeforeMount(() => {
  console.log("创建之前===>", div.value);
});
onMounted(() => {
  console.log("创建完成===>", div.value);
});
onBeforeUpdate(() => {
  console.log("更新之前===>", div.value?.innerText);
});
onUpdated(() => {
  console.log("更新完成===>", div.value?.innerText);
});

onBeforeUnmount(() => {
  console.log("卸载之前===>");
});
onUnmounted(() => {
  console.log("卸载完成===>");
});
</script>

<style lang="scss" scoped></style>

App.vue

<template>
  <A v-if="flag"></A>
  <hr />
  <!-- 测试UnMounted前后 -->
  <h2>测试UnMounted前后</h2>
  <button @click="change">unmounted</button>
</template>

<script setup lang="ts">
import A from "./components/A.vue";
import { ref } from "vue";
let flag = ref<Boolean>(true);
const change = () => {
  flag.value = !flag.value;
};
</script>

<style lang="scss" scoped></style>

在这里插入图片描述
总结:

  1. 使用setup语法糖模式是没有beforeCreatecreated这两个周期的。
  2. onBeforeMount是读不到dom的,onMounted可以读取到dom的。
  3. onBeforeUpdated获取的是更新之前的domonUpdated获取的是更新之后的dom
  4. 使用v-if可以测试unMouned

七、scss基本使用与BEM命名方式

1.scss

SCSS(Sass CSS)是CSS的一种较新的形式。它是基于 CSS3 的一个把 CSS 的语法进行了扩展的版本,因此,所有标准的 CSS 都是合法的 SCSS 代码。在 SCSS 中可以使用变量、嵌套、混入、函数等高级功能,极大地提高了 CSS 的编写效率。下面是 SCSS 的一些常用语法:

  • 变量的定义:使用$符号定义变量,例如:
$primary-color: #2196f3;
  • 嵌套:可以在样式规则中嵌套子规则,例如:
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }
  li { display: inline-block; }
  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
  • 混入(Mixin):可以定义一段样式,并在需要使用的地方调用,例如:
@mixin square($size: 50px, $color: red) {
  width: $size;
  height: $size;
  background-color: $color;
}
.box {
  @include square(100px, blue);
}
  • 继承(Extend):可以让一个选择器继承另外一个选择器的所有样式,例如:
.message {
  border: 1px solid #ccc;
  padding: 10px;
}
.success {
  @extend .message;
  border-color: green;
}
  • 函数:可以定义一些常用的函数,例如:
@function double($n) {
  @return $n * 2;
}

以上是 SCSS 的一些常用语法,当然还有很多其他的功能和特性,需要根据具体的需要进行学习和了解。

2.BEM

BEM(Block Element Modifier)是一种前端开发中常用的 CSS 类命名方式,它可以帮助我们编写出易于维护和扩展的代码。

BEM 的基本思想是将页面分解为多个块(Block),每个块包含多个元素(Element),而一个元素可以是另一个块的子块。块和元素分别由名称和描述符组成,这些名称和描述符之间使用双下划线和双短横线来分隔,例如:

/* 定义一个名为 list 的块 */
.list { … }

/* 定义一个名为 item 的元素 */
.list__item { … }

/* 定义一个名为 icon 的修饰符 */
.list__item--icon { … }

其中,list 是块的名称,item 是元素的名称,icon 是修饰符的名称。通过这种方式定义类名,可以使得 HTML 结构和 CSS 样式更加清晰、易于理解和扩展。

具体来说,BEM 将页面中的每个模块都视为独立的“块”,在 CSS 中以 .block 的形式进行命名,例如 .header.menu.button 等。然后再将一个块内部的各个元素视为该块的组成部分,以 .block__element 的形式进行命名,例如 .header__logo.menu__item.button__icon 等。需要注意的是,一个元素只能属于一个块,并且必须包含在该块的范围内

如果需要对某个元素进行样式修饰,则可以使用修饰符(Modifier),以 .block__element--modifier 的形式进行命名,例如.button__icon--small.menu-item--active 等。修饰符可以对一个或多个元素进行样式修改,以实现不同状态或风格的变化。

Element UI 就采用了 BEM(Block Element Modifier)的命名方式来定义组件样式类。

Element UI 中,每个组件都被视为一个块(Block),并以 el- 作为前缀进行命名,例如 el-buttonel-input 等。每个组件包含多个元素(Element),并以 __ 进行分隔,例如 el-button__iconel-input__prefix 等。同时,也可以为一个元素添加修饰符(Modifier),以 -进行分隔,例如 el-button--primaryel-input--disabled 等。

具体来说,通过 BEM 的命名方式,可以使 Element UI 的组件结构更加清晰,易于理解和修改,同时也方便了其他开发人员对组件样式的继承和覆盖。在使用 Element UI 组件时,我们只需要在 HTML 元素中指定相应的 CSS 类名即可,例如:

<el-button class="el-button--primary">确定</el-button>

上述代码中,我们为 el-button 组件添加了一个 el-button--primary 的修饰符,以实现按钮的主题色为蓝色。

总之,在使用 Element UI 组件时,建议按照 BEM 命名方式规范命名 CSS 类名,这样既能保证代码风格的统一性,也能提高代码的可读性和可维护性。

案例:配置全局BEM全局方式
配置文件bem.scss

$block-sel: "-" !default;  // B
$element-sel: "__" !default;  // E
$modifier-sel: "--" !default;  // M
$namespace: 'xw' !default;
@mixin bfc {
    height: 100%;
    overflow: hidden;
}

// .xw-block {
//  content
// }

// 混入
@mixin b($block) {
    $B: $namespace + $block-sel + $block;  // 变量
    .#{$B} {  // 插值语法#{name}
        @content;  // @content相当于一个占位符,会把自定义样式里的内容替换进来
    }
}

@mixin flex {
    display: flex;
}

// .xw-block__inner {
//  content
// }

@mixin e($element) {
    $selector: &;  // & 父选择器引用符,此处使用定义变量$selector,来获取父级的类名,如xw-block
    @at-root {  // 控制指令,用于将样式规则返回到其顶级作用域中,使用嵌套规则时非常有用,可避免样式规则嵌套过深,此处跳出嵌套
        #{$selector + $element-sel + $element} {
            @content;
        }
    }
}

@mixin m($modifier) {
    $selector: &;
    @at-root {
        #{$selector + $modifier-sel + $modifier} {
            @content
        }
    }
}

修改vite.config.ts文件

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/bem.scss";`
      }
    }
  }
})

测试BEM配置
App.vue

<template>
  <div class="xw-test">
    block
    <div class="xw-test__inner">element</div>
    <div class="xw-test--success">modify</div>
  </div>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
@include b(test) {
  // @include指令被用来引入某个mixin的样式规则
  color: green;
  @include e(inner) {
    color: blue;
  }
  @include m(success) {
    color: red;
  }
}
</style>

在这里插入图片描述

八、配置局部、全局、递归组件

1.配置局部组件

局部组件也就是常规的在components文件夹中的定义组件,然后在页面导入并引用组件。

2.配置全局组件

例如组件使用频率非常高,如table、input、button等这些组件,几乎每个页面都在使用,这类组件就可以封装成全局组件。
我们也可以在components文件夹中自定义频繁使用的组件,并将自定义组件注册成全局组件。
将注册成全局组件的方法都是一样的,看如下案例:
我们在这封装一个Card组件,想在任何地方去使用

<template>
  <div class="card">
    <header>
      <div>标题</div>
      <div>副标题</div>
    </header>
    <section>{{ content }}</section>
  </div>
</template>

<script setup lang="ts">
// import { ref, reactive } from "vue";
defineProps({
  content: {
    type: String,
    default: "",
  },
});
</script>

<style lang="less" scoped>
@border: #ccc;
.card {
  border: 1px solid @border;
  width: 400px;
  header {
    display: flex;
    justify-content: space-between;
    padding: 5px;
    border-bottom: 1px solid @border;
  }
  section {
    padding: 5px;
    min-height: 300px;
  }
}
</style>

main.ts中注册全局组件

import { createApp } from 'vue'
import App from './App.vue'
// 导入组件
import CardVue from './components/Card.vue'
export const app = createApp(App)
// 注册全局组件
app.component('Card', CardVue)

app.mount('#app')

在其他文件中使用全局组件

<template>
  <div>
    <!-- <TreeVue :data="data"></TreeVue> -->
    <Card content="this is content"></Card>
  </div>
</template>

结果展示:
在这里插入图片描述

3.配置递归组件

递归组件的原理和我们写js递归一样,自己调用自己,然后通过一个条件来结束递归,否则导致内存泄漏。
这里我们写一个简易的递归树案例
Tree.vue

<template>
  <div
    @click.stop="chilkTap(item, $event)"
    v-for="item in data"
    :key="item.name"
    class="tree"
  >
    <input type="checkbox" v-model="item.checked" />
    <span>{{ item.name }}</span>
    // 自己调用自己,并且使用v-if和?扩展运算符来终止递归
    <Tree v-if="item?.children?.length" :data="item?.children"></Tree>
  </div>
</template>

<script setup lang="ts">
interface Tree {
  name: string;
  checked: boolean;
  children?: Tree[];
}

defineProps<{
  data?: Tree[];
}>();
// 节点点击事件
const chilkTap = (item: Tree, e: any) => {
  console.log("item:", item);
  console.log("event", e);
};
</script>

<style lang="scss" scoped>
.tree {
  margin-left: 10px;
}
</style>

在其他页面使用递归组件

<template>
  <div>
    <TreeVue :data="data"></TreeVue>
  </div>
</template>

<script setup lang="ts">
import {reactive } from "vue";
import TreeVue from "./components/Tree.vue";
interface Tree {
  name: string;
  checked: boolean;
  children?: Tree[];
}
// mock一个三层结构的静态数据
const data = reactive<Tree[]>([
  {
    name: "1",
    checked: false,
    children: [{ name: "1-1", checked: false }],
  },
  {
    name: "2",
    checked: false,
  },
  {
    name: "3",
    checked: false,
    children: [
      {
        name: "3-1",
        checked: false,
        children: [{ name: "3-1-1", checked: false }],
      },
    ],
  },
]);
</script>

结果展示:
在这里插入图片描述

九、动态组件

什么是动态组件:
让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
用法如下:
引入组件

import A from './A.vue'
import B from './B.vue'

通过is切换A、B组件

  <component :is="A"></component>

使用场景:tab切换
下面来看案例:
定义三个子组件,分别是B、C、D。

<template>
  <div style="display: flex">
    <div
      @click="switchCom(item, index)"
      :class="[active == index ? 'active' : '']"
      class="tabs"
      v-for="(item, index) in data"
      :key="item.name"
    >
      <div>{{ item.name }}</div>
    </div>
  </div>
  // 此处调用动态组件
  <component :is="comId"></component>
</template>

<script setup lang="ts">
import { ref, reactive, shallowRef, markRaw } from "vue";
// 引入子组件
import B from "./components/B.vue";
import C from "./components/C.vue";
import D from "./components/D.vue";
// 默认激活的子组件
const comId = shallowRef(B);
// 默认激活的tab索引
const active = ref(0);
// 通过data来管理子组件,以便动态调用子组件
const data = reactive([
  {
    name: "B组件",
    // markRaw作用:使其在被响应式代理时不会被视为响应式数据
    com: markRaw(B),
  },
  {
    name: "C组件",
    com: markRaw(C),
  },
  {
    name: "C组件",
    com: markRaw(D),
  },
]);
// tab切换触发的组件切换
const switchCom = (item, index) => {
  comId.value = item.com;
  active.value = index;
};
</script>

<style lang="scss" scoped>
.active {
  background-color: skyblue;
}
.tabs {
  border: 1px solid #ccc;
  margin: 10px;
  padding: 10px;
  cursor: pointer;
}
</style>

效果展示:
在这里插入图片描述
注意事项:
1.在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的

2.如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref.
Component that was made reactive:

这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处,为了节省性能开销,推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理

十、异步组件

异步组件是什么?
异步组件是一种在 Vue.js 应用程序中,延迟加载组件的机制当应用程序需要加载某个组件时,使用异步组件可以将该组件从应用程序的主要代码中分离出来,以实现按需加载和提高应用程序的初始性能。

传统的情况下,所有组件都是同步加载的,这意味着当用户打开应用程序时,所有组件都会立即被加载并注册到 Vue.js 实例中。随着应用程序变得更加庞大,这些组件的数量和大小也会增加,导致应用程序加载速度变慢,甚至影响用户体验。使用异步组件机制,可以将某些不常用的组件或者较为耗时的组件单独分离出来,延迟加载,从而提高应用程序性能。

在 Vue 2 中,可以通过 require.ensure 或者 Webpack 的 import() 函数来实现异步组件。而在 Vue 3 中,则提供了新的 API:defineAsyncComponent 来实现异步组件的定义。

异步组件的应用场景

  1. 当应用程序比较庞大时,需要优化应用程序的加载速度和时间。在这种情况下,将组件拆分成多个小模块,并使用异步加载以降低应用程序的初始负载就非常重要了。
    如骨架屏的实现
    在这里插入图片描述

  2. 除了优化应用程序的启动性能之外,还可以通过按需加载组件来减小应用程序的总体大小。例如,在一个包含多个不同页面的单页应用程序中,可能只有某些页面才需要特定的组件或功能。如果将所有组件都打包到应用程序中,则会增加整体应用程序的。

顶层await
在 Vue 3 中,await 可以被用于异步组件的定义中。在组件定义对象中,可以使用 load 函数来定义加载组件的异步操作,例如:

const AsyncComponent = {
  name: 'AsyncComponent',
  async load() {
    const component = await import('./MyComponent.vue');
    return component.default;
  },
  render() {
    return h('div', [this.Component ? h(this.Component) : null])
  }
};

在上面的代码中,load 函数是一个 async 函数,它通过 import 语句异步地加载了我们想要的组件,并返回该组件的默认导出值。

在 render 函数中,我们使用 this.Component 来渲染异步加载的组件。当异步组件还没有加载完成时,this.Component 的值为 null,所以这里使用了条件判断来避免出现“渲染组件未定义”的错误。

总之,Vue 3 中的 await 可以被用于异步组件的定义中,帮助我们更方便地实现组件的异步加载。

<script setup> 中可以使用顶层 await。结果代码会被编译成 async setup()

<script setup>
const post = await fetch(`/api/post/1`).then(r => r.json())
</script>

父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包

<script setup lang="ts">
import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue'
 
const Dialog = defineAsyncComponent(() => import('../../components/Dialog/index.vue'))
 
//完整写法
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),
 
  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,
 
  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

suspense
<suspense>组件有两个插槽。它们都只接收一个直接子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。

     <Suspense>
     	    <!-- default -->
            <template #default>
                <Dialog>
                    <template #default>
                        <div>我在哪儿</div>
                    </template>
                </Dialog>
            </template>
            <!-- fallback -->
            <template #fallback>
                <div>loading...</div>
            </template>
        </Suspense>

十一、teleport

teleport是Vue3.0新特性之一。
teleport是一种能够将我们的模板渲染之指定DOM节点,不受父级syle、v-show等属性影响,但data、prop数据依旧能够共用的技术,类似于React的Protal。
teleport的主要应用场景包括:

  1. 模态框和弹出框:在用户点击按钮后,模态框或弹出框通常需要动态地插入到页面的某个节点上。使用 Teleport 可以更方便地实现这个操作。例如:
<button @click="showModal = true">打开模态框</button>
<teleport to="body">
  <modal v-if="showModal" @close="showModal = false"></modal>
</teleport>
  1. 提示和通知:当需要在页面的某个位置显示提示或通知时,Teleport 也会变得非常有用。例如,可以在 HTML 文档的末尾添加一个容器元素,用于显示全局的消息提示:
<template>
  <div>
    <button @click="showMessage()">显示消息</button>
    <teleport to="#message-container">
      <div class="message" v-if="message">{{ message }}			   </div>
    </teleport>
  </div>
  <div id="message-container"></div>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  },
  methods: {
    showMessage() {
      this.message = '这是一条消息';
      setTimeout(() => {
        this.message = '';
      }, 3000);
    }
  }
}
</script>
  1. 图片懒加载:Teleport 还可以用于实现图片懒加载。通常,我们需要在滚动到指定位置时再将某些元素插入页面中,以避免资源的浪费。此时,Teleport 提供了一个很好的解决方案,例如:
<template>
  <div>
    <teleport to="#image-container">
      <img :src="imageSrc" @load="handleImageLoad" />
    </teleport>
  </div>
  <div id="image-container"></div>
</template>

<script>
export default {
  data() {
    return {
      imageSrc: '',
      loaded: false
    }
  },
  mounted() {
    window.addEventListener('scroll', this.loadImage);
  },
  beforeUnmount() {
    window.removeEventListener('scroll', this.loadImage);
  },
  methods: {
    handleImageLoad() {
      this.loaded = true;
    },
    loadImage() {
      if (this.loaded) return;
      const container = document.querySelector('#image-container');
      const rect = container.getBoundingClientRect();
      if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
        this.imageSrc = 'image.jpg';
      }
    }
  }
}
</script>

总之,Teleport 能够很好地解决组件渲染的问题,在模态框、提示和通知、图片懒加载等场景中表现非常出色。

下面通过一个项目中常见的案例来体会一下teleport的妙用:
我们定义一个使用position:absolute的方式实现提示框在可视区垂直、水平居中显示的子组件。
子组件Dialog代码:

<template>
  <div class="dialog">
    <header><div>提示:</div></header>
    <main>
      <hr />
      <div>content</div>
    </main>
  </div>
</template>

<style lang="scss" scoped>
.dialog {
  width: 300px;
  height: 300px;
  background-color: #ccc;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
</style>

当父组件没有用position: relative;时,对话框成功实现垂直、水平居中。
在这里插入图片描述
但当父组件使用position: relative;后,对话框将无法实现在可视区垂直、水平居中。代码如下:

<template>
  <div class="father">
  	我是父组件
    <Dialog></Dialog>
  </div>
</template>

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

<style lang="scss" scoped>
.father {
  height: 500px;
  background-color: yellow;
  position: relative;
}
</style>

结果如下:
在这里插入图片描述
这时,我们就可以使用teleport来实现将子组件Dialog渲染到指定DOM节点body
代码如下:

<template>
  <div class="father">
    我是父组件
    <teleport to="body">
      <Dialog></Dialog>
    </teleport>
  </div>
</template>

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

<style lang="scss" scoped>
.father {
  height: 500px;
  background-color: yellow;
  position: relative;
}
</style>

结果如下:
在这里插入图片描述

十二、keep-alive缓存组件

Vue中keep-alive的深入理解和使用

十三、transition动画组件

在 Vue 中,Transition 是一种动画效果的实现方式。在组件切换时,可以通过添加 Transition 的相关属性来实现过渡动画效果。

使用 Transition 可以让我们的应用程序更加生动、具有交互性。例如,在两个组件之间进行切换时,添加 Transition 效果可以使界面转换更加自然、平滑,提升用户的体验感受。

1.过渡的类名

在进入/离开的过渡中,会有6个class切换。

  1. v-enter-from:定义进入过渡的开始状态,在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态,在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效(与此同时v-enter-from被移除),在过渡/动画完成之后移除。
  4. v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。

案例:

<template>
  <div>
    <button @click="flag = !flag">切换</button>
    <transition name="fade">
      <div v-if="flag" class="box"></div>
    </transition>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import "animate.css";
let flag = ref(true);
</script>

<style lang="scss" scoped>
.box {
  width: 300px;
  height: 300px;
  background-color: red;
}
// 开始过渡DOM的初始状态
.fade-enter-from {
  width: 0;
  height: 0;
  transform: rotate(360deg);
  background-color: red;
}
// 开始过渡到过渡完成的过程
.fade-enter-active {
  transition: all 1.5s linear;
}
// 定义过渡结束的Dom状态,即过渡当这个状态时结束
.fade-enter-to {
  background-color: yellow;
  width: 100px;
  height: 100px;
}
// 离开的过渡
.fade-leave-from {
  width: 200px;
  height: 200px;
  transform: rotate(360deg);
}
// 离开的过渡过程
.fade-leave-active {
  transition: all 1s linear;
}

//离开完成
.fade-leave-to {
  width: 100px;
  height: 50px;
}
</style>

2.自定义过渡class类名

transition动画组件有如下过渡class类名(props)

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class
    还可以使用duration属性定义过渡时间。
<transition enter-from-class="" enter-active-class="" enter-to-class="" leave-from-class="" leave-active-class="" leave-to-class="" >...</transition>
<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

3.animate动画库的使用

官方文档 Animate.css | A cross-browser library of CSS animations.
安装animate库:$ npm install animate.css --save
基本用法:<h1 class="animate__animated animate__bounce">An animated element</h1>
copy此类名,调用时加上类名animate__animated动画效果就能生效。
在这里插入图片描述

结合transition动画组件自定义库使用的案例:

<template>
  <div>
    <button @click="flag = !flag">切换</button>
    <transition
      leave-active-class="animate__animated animate__bounce"
      enter-active-class="animate__animated animate__flash"
    >
      <div v-if="flag" class="box"></div>
    </transition>
  </div>
</template>

在这里插入图片描述

十四、transition-group过渡列表

1. 过渡列表的简单用法

<transition-group> 是 Vue.js 内置的一个高级动画组件,它可以帮助我们实现在添加或删除元素时,自动应用过渡动画。通常与 <v-for> 一起使用,当数据改变时,<transition-group> 会自动检测哪些元素被添加或删除,然后应用相应的过渡动画。
<transition-group> 在使用上与 <transition> 组件类似,但它能够对一组元素进行过渡效果的处理。常见的应用场景包括列表渲染,在增加或删除列表项时,以平滑动画效果来显示添加和删除的元素,从而增强用户体验。
下面看一个简单的案例:

<template>
  <div><button @click="add">add</button> <button @click="pop">pop</button></div>
  <div class="wraps">
    <transition-group
      leave-active-class="animate__animated animate__bounceOut"
      enter-active-class="animate__animated animate__fadeInDownBig"
    >
      <div style="margin: 10px" v-for="item in list" :key="item" class="item">
        {{ item }}
      </div>
    </transition-group>
  </div>
</template>

<script setup lang="ts">
import { reactive } from "vue";
import "animate.css";
const list = reactive<number[]>([1, 2, 3, 4, 5]);
const add = () => {
  list.push(list.length + 1);
};
const pop = () => {
  list.pop();
};
</script>

<style lang="scss" scoped>
.wraps {
  display: flex;
  flex-wrap: wrap;
}
.item {
  padding: 10px;
  font-size: 20px;
  border: 1px solid #ccc;
}
</style>

结果展示:
在这里插入图片描述

2.过渡列表的移动过渡

<transition-group> 组件还有一个特殊之处。除了进入和离开,它还可以为定位的改变添加动画
只需要为<transition-group> 组件的move-class属性绑定自定义的过渡类别就行,我们通过以下案例来体会移动过渡。

<template>
  <div>
    <button @click="random">random</button>
    <transition-group move-class="mmm" tag="div" class="wraps">
      <div :key="item.id" v-for="item in list" class="item">
        {{ item.number }}
      </div>
    </transition-group>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import _ from "lodash";
// 生成9*9的列表数据
let list = ref(
  Array.apply(null, { length: 81 } as number[]).map((_, index) => {
    return {
      id: index,
      number: (index % 9) + 1,
    };
  })
);
// console.log(list.value);
const random = () => {
  // 使用lodash库的_.shuffle()方法实现列表内的数据顺序打乱
  list.value = _.shuffle(list.value);
};
</script>

<style lang="scss" scoped>
.wraps {
  display: flex;
  flex-wrap: wrap;
  width: calc(27px * 9);
  .item {
    height: 25px;
    width: 25px;
    border: 1px solid #ccc;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
// 过渡动画
.mmm {
  transition: all 1s;
}
</style>

结果展示:
在这里插入图片描述

3.状态过渡

十五、mitt事件总线库

mitt 是一个小巧的 JavaScript 事件总线库,可以用于前端项目中轻松地实现自定义事件的管理和监听。

它是按照发布-订阅模式设计的,可以在任何地方使用,例如 React、Vue、Angular 等前端框架中。相比于 Vue.js 或 React 内置的事件系统,mitt 更为灵活,不需要依赖特定的框架或库。

以下是一个简单的示例:

import mitt from 'mitt'

// 创建一个事件总线
const eventBus = mitt()

// 监听事件
eventBus.on('event1', data => {
  console.log('event1', data)
})

// 触发事件
eventBus.emit('event1', 'hello world')

// 移除事件监听器
eventBus.off('event1', listener)

下面我们使用mitt实现全局配置事件总线:
配置入口文件main.ts

import { createApp } from 'vue'
import App from './App.vue'
// import CardVue from './components/Card.vue'

import mitt from 'mitt'
const Mit = mitt()

export const app = createApp(App)
// 注册全局组件
// app.component('Card', CardVue)

// 这段代码利用了ts module declaration的特性,声明了一个名为vue的模块
// 并通过ComponentCustomProperties接口扩展了Vue.js组件实例类型。
// 其中ComponentCustomProperties是一个内置的Vue.js接口,可用于向组件实例添加自定义属性。
declare module 'vue' {
    export interface ComponentCustomProperties {
        // 通过$Bus: typeof Mit 的语法,我们将$Bus属性的类型指定为Mit类型的的类型本身,
        // 也就是自定义事件总线库的类型,这样对于使用TS的项目,就可以在组件中
        // 直接访问到$Bus属性,并且享受到类型检查的好处。
        $Bus: typeof Mit
    }
}
// 使用mitt注册全局事件Bus
app.config.globalProperties.$Bus = Mit

app.mount('#app')

父组件App.vue

<template>
  <!-- 兄弟组件B、C -->
  <B></B>
  <C></C>
</template>

<script setup lang="ts">
import B from "./components/B.vue";
import C from "./components/C.vue";
</script>

通过兄弟组件中的B组件向C组件派发事件,C组件触发事件
B.vue

<template>
  <h1>我是B</h1>
  <button @click="emit">按钮</button>
</template>

<script setup lang="ts">
import { getCurrentInstance } from "vue";

const instance = getCurrentInstance();
const emit = () => {
  instance?.proxy?.$Bus.emit("on-xiao5", "mitt");
  instance?.proxy?.$Bus.emit("on-xiao4", "mitt2");
  instance?.proxy?.$Bus.emit("on-xiao3", "mitt3");
};
</script>

C.vue

<template>
  <h1>C</h1>
</template>

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

import { getCurrentInstance } from "vue";

const instance = getCurrentInstance();
// 监听一个事件
instance?.proxy?.$Bus.on("on-xiao5", (str) => {
  console.log(str);
});
// 监听多个事件"*",回调函数的第一个参数是事件类型(事件名),第二个参数是传的参
instance?.proxy?.$Bus.on("*", (type, str) => {
  console.log("type:", type);
  console.log("value:", str);
});
// 删除一个事件
// instance?.proxy?.$Bus.off("on-xiao5", () => {
//   console.log("off over");
// });
// 删除全部事件
// instance?.proxy?.$Bus.all.clear();
</script>

结果展示:
在这里插入图片描述

十六、TSX

参考博客:学习Vue3 第二十五章(TSX)

TSX 是一种在 TypeScript 中编写 React 组件的语法,相比于传统的 JSX 语法,它能提供更好的类型检查和编译时错误检查。TSX 其实就是将 JSX 和 TypeScript 结合起来,所以可以在 TSX 中使用 JSX 的所有语法,同时也可以使用 TypeScript 的类型注解来为组件和组件 Props 声明类型。通常,TSX 的文件扩展名为 “.tsx”。

安装tsx插件:npm install @vitejs/plugin-vue-jsx -D
修改vite.config.ts配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 导入tsx插件
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  // 启动tsx插件
  plugins: [vue(),vueJsx()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/bem.scss";`
      }
    }
  }
})

在 TSX 中,需要遵循以下规范:

  1. 文件名应该以 .tsx 为后缀。
  2. 组件名应该以大写字母开头并使用驼峰式大小写,如 MyComponent。
  3. JSX 不支持v-bind,使用 {} 来代替,例如 value = { this.props.name }
  4. JSX 中不支持 if 语句,可以使用三目运算符代替,例如 { condition ? true : false }
  5. JSX 中不支持 for 循环,可以使用 Array.map() 函数来代替,例如 { this.props.items.map(item => <li>{item}</li>) }
  6. 支持v-show
  7. reftemplate自动解包.value,但tsx不会,要手动加.value

案例代码:
App.tsx

// 写法一
// export default function () {
//     return (<div>小5</div>)
// }

// 写法二:optionsAPI
// import { defineComponent } from "vue"

// export default defineComponent({
//     data() {
//         return {
//             age: 23
//         }
//     },
//     render() {
//         return (<div>{this.age}</div>)
//     }
// })

// 写法三:setup
import { defineComponent, ref } from "vue"; 

// 组件A
const A = (_:any, { slots }) => (
    <>
        <div>{slots.default ? slots.default() : '默认值'}</div>
    </>
)

interface Props {
    name?: string
}

export default defineComponent({
    props: {
        name: String
    },
    emits: ['on-click'],
    setup(props: Props, {emit}) {
        const flag = ref(false)
        const data = [
            {
                name: '小5——1'
            },
            {
                name: '小5——2'
            },
            {
                name: '小5——3'
            }
        ]
        const fn = (value: any) => {
            console.log('触发了', value);
            // 派发事件
            emit('on-click', value)
        }
        const slot = {
            default: () => (<div>5default slots</div>)
        }
        const modelValue = ref()
        return () => (
            <>
                <h5>测试父组件向子组件传递数据</h5>
                <div>props:{props?.name}</div>
                <hr />
                <h5>测试map代替v-for</h5>
                {
                    data.map(item => {
                        return (<div>{item.name}</div>)
                    })
                }
                <hr />
                <h5>测试三元表达式代替v-if</h5>
                <div>{flag.value ? <div>true</div> : <div>false</div>}</div>
                <hr />
                <h5>测试事件派发和监控</h5>
                <button onClick={() => fn('尼赣嘛')}>按钮</button>
                <hr />
                <h5>测试插槽</h5>
                <A v-slots={slot}></A>
                <hr />
                <h5>测试v-model</h5>
                <input type="text" v-model={modelValue.value} />
                <div>{modelValue.value}</div>
            </>
        )
    }
})

App.vue

<template>
  <div>
    <xiao5 name="小6" @on-click="handleOn"></xiao5>
  </div>
</template>

<script setup lang="ts">
// 导入tsx
import xiao5 from "./App";

const handleOn = (value: any) => {
  console.log("父组件中触发子组件派发的事件");
  console.log("父组件收到子组件派发事件传来的参数:", value);
};
</script>

结果展示:
在这里插入图片描述

十七、深入v-model

在 Vue 3 中,使用 v-model 与 Vue 2 中的用法略有不同,主要是因为 Vue 3 中有了新的 setup() 函数和 reactive() 函数,这些函数使得我们可以更方便地编写组件逻辑。

在 Vue 3 中,v-model 可以使用 modelValueupdate:modelValue 两个属性来实现双向数据绑定。

1.v-model绑定一个值

案例:使用双向绑定v-model实现点击切换开关控制子组件的显示与隐藏,并可以通过关闭按钮隐藏子组件。
父组件App.vue

<template>
  <div>
    <h3>我是父组件</h3>
    <div>isShow:{{ isShow }}</div>
    <button @click="isShow = !isShow">切换</button>
    <A v-model="isShow"></A>
  </div>
</template>

<script setup lang="ts">
import A from "./components/A.vue";
import { ref } from "vue";
const isShow = ref<boolean>(true);
</script>

子组件A.vue:

<template>
  <div class="main" v-if="modelValue">
    <h3>我是子组件</h3>
    <div>content</div>
    <button class="btn" @click="close">关闭</button>
  </div>
</template>

<script setup lang="ts">
// 接收父组件v-model传来的值,并用modelValue代替
defineProps<{
  modelValue: boolean;
}>();
// 定义更新modelValue的emit,实现双向绑定
const emit = defineEmits(["update:modelValue"]);
// 通过事件触发emit
const close = () => {
  emit("update:modelValue", false);
};
</script>

<style lang="scss" scoped>
.main {
  position: relative;
  left: 200px;
  width: 200px;
  height: 200px;
  border: 1px solid #ccc;
}

.btn {
  position: absolute;
  top: 0;
  right: 0;
}
</style>

结果展示:
在这里插入图片描述

2.v-model绑定多个值

案例:继上一个案例,实现多个值的双向绑定。
父组件App.vue

<template>
  <div>
    <h3>我是父组件</h3>
    <div>isShow:{{ isShow }}</div>
    <div>text:{{ text }}</div>
    <button @click="isShow = !isShow">切换</button>
    <!-- 传多个v-model -->
    <A v-model="isShow" v-model:textVal="text"></A>
  </div>
</template>

<script setup lang="ts">
import A from "./components/A.vue";
import { ref } from "vue";
const isShow = ref<boolean>(true);
const text = ref<string>("小5");
</script>

子组件A.vue:

<template>
  <div class="main" v-if="modelValue">
    <h3>我是子组件</h3>
    <div>content</div>
    <button class="btn" @click="close">关闭</button>
    <div>内容:<input type="text" :value="textVal" @input="change" /></div>
  </div>
</template>

<script setup lang="ts">
// 同时接收父组件v-model传来的多个值
defineProps<{
  modelValue: boolean;
  textVal: string;
}>();
// 定义更新多个值的emit
const emit = defineEmits(["update:modelValue", "update:textVal"]);
// 通过事件触发emit
const close = () => {
  emit("update:modelValue", false);
};
const change = (e: Event) => {
  const target = e.target as HTMLInputElement;
  emit("update:textVal", target.value);
};
</script>

结果展示:
在这里插入图片描述

3.自定义修饰符

在Vue 3中,你可以使用自定义修饰符来改变v-model指令的行为。自定义修饰符应该用.符号来表示,例如.number和.trim。

// 同时接收父组件v-model传来的多个值
defineProps<{
  modelValue: boolean;
  textVal: string;
  textValModifiers?: {  // 值+Modidiers
    default: () => {
    
  	}
}>();

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试——黑盒测试

1.测试概述 1.1综述 本测试报告为计算机程序能力在线测评系统的黑盒测试&#xff0c;黑盒测试可以在不知道程序内部结构和代码的情况下进行&#xff0c;用来测试软件功能是否符合用户需求&#xff0c;是否达到用户预期目标&#xff0c;是否拥有较好的人机交互体验。 图1.1 黑…

media设备节点初始化与Video4Linux初始化

media设备节点初始化与Video4Linux初始化 文章目录 media设备节点初始化与Video4Linux初始化media设备节点初始化Video4Linux初始化 media设备节点初始化 media_devnode_init函数是一个内核初始化函数&#xff0c;用于在Linux内核启动期间进行设备节点初始化。 函数的主要作用…

复习:遥感图像解译复习整理

惭愧&#xff0c;这个课程从始自终就没有认真学过&#xff0c;一部分是因为自己的原因&#xff0c;另一部分也是因为自己的原因。因此&#xff0c;对于整理的资料有不足之处请指正。 另外&#xff0c;资料自word复制&#xff0c;没有时间整理博客的格式。 -- 2023年05月19日记…

深度学习训练营之Densenet网络

深度学习训练营 原文链接环境介绍前言设计理念网络结构实验结果和讨论pytorch实现DenseNet附录 原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;365天深度学习训练营-第J3周&#xff1a;Densenet网络学习&…

第一代AIGC硬件悄然爆发

文 | 智能相对论 作者 | 叶远风 看起来&#xff0c;这可能是一副正常的黑框眼镜&#xff0c;你戴上去彬彬有礼、斯斯文文&#xff1b; 实际上&#xff0c;它里边还装了一个“小伙伴”&#xff0c;你随时可以与它交流&#xff0c;谈天说地或者提出各种问题接受它的帮助&#x…

深度学习之构建MPL神经网络——泰坦尼克号乘客的生存分析

大家好&#xff0c;我是带我去滑雪&#xff01; 本期使用泰坦尼克号数据集&#xff0c;该数据集的响应变量为乘客是生存还是死亡&#xff08;survived&#xff0c;其中1表示生存&#xff0c;0表示死亡&#xff09;&#xff0c;特征变量有乘客舱位等级&#xff08;pclass&#x…

广告投放的关键成功因素:广告归因与广告效果监测

在当今竞争激烈的市场环境中&#xff0c;广告归因和广告效果监测成为了广告投放中至关重要的环节。通过深入了解广告归因和广告效果监测的方法&#xff0c;企业可以更好地评估广告投放的成效&#xff0c;并做出精确的决策&#xff0c;以提高广告效果和最大化投资回报。 本文将带…

昆仑万维“勇闯”百模大战:一个“无懈可击”的商业故事话本?

文丨智能相对论 作者丨沈浪 新能源火了做新能源&#xff0c;元宇宙火了做元宇宙。 如果一个热点领域没有昆仑万维的身影&#xff0c;那一定是这个领域还不够“热”&#xff0c;爆不了。 但凡是热到爆的领域&#xff0c;昆仑万维虽迟但到。 不过&#xff0c;这样说可能有些…

【计算机网络基础】章节测试3 数据链路层

文章目录 判断题选择题辨析题应用题 判断题 相对于广域网而言&#xff0c;局域网的误码率较低。√ 交换机是依据IP地址来转发数据包的。 局域网使用集线器作为网络连接设备时&#xff0c;逻辑上是星型结构。 PPP协议应首先满足的需求是简单&#xff0c;以使得协议在实现的时…

Go语言的学习【2】基础语法

目录 代码组成部分字符串格式化字符数据类型变量遇到的问题及解决办法1 代码组成部分 Go 程序可以由多个标记组成&#xff0c;可以是关键字&#xff0c;标识符&#xff0c;常量&#xff0c;字符串&#xff0c;符号。 在 Go 程序中&#xff0c;一行代表一个语句结束。 如果你…

【linux】图文并茂,让你轻松掌握Linux基本指令

目录 一&#xff0c;前提 二&#xff0c; 在root身份下&#xff0c;管理用户 1. 判断身份 2. 创建用户 3. 销毁用户 三&#xff0c;文件增&#xff0c;删&#xff0c;移动指令 1. pwd——查看路径 2. ps ——打开文件目录 3. touch——创建文件 4. nano——打开文件 5.…

【大数据】Presto(Trino)REST API 与执行计划介绍

文章目录 一、概述二、环境准备三、常用 REST API1&#xff09;worker 节点优雅退出2&#xff09;提交SQL查询请求3&#xff09;获取查询状态4&#xff09;获取查询结果5&#xff09;取消查询请求6&#xff09;获取Presto 节点信息7&#xff09;获取Presto服务器使用统计信息8&…

功率放大器电路中的三极管和MOS管,究竟有什么区别?

学习模拟电子技术基础&#xff0c;和电子技术相关领域的朋友&#xff0c;在学习构建功率放大器电路时最常见的电子元器件就是三极管和场效应管&#xff08;MOS管&#xff09;了。那么三极管和MOS管有哪些联系和区别呢&#xff1f;在构建功率放大器电路时我们要怎么选择呢&#…

干货 | 利用SPSS进行高级统计分析第一期

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐&#xff5e; 你是否还在为分析实验数据而感到头疼&#xff1f;你是否还在苦于自己不知道如何选择合适的模型来分析数据&#xff1f; 本期我们就来为大家带来了利用SPSS软件进行高级统计分析…

【学习日记】在不可联网电脑上安装Python和深度学习环境

测试环境 Hyer-V上开了个虚拟机&#xff0c;win7-64位企业版&#xff0c;全新未安装任何环境的最基本的操作系统。 因为不联网安装&#xff0c;而且是win7这种古老的操作系统&#xff0c;确实会遇到很多问题。做个记录。 安装Python 打开python-3.7.8.exe 安装程序 此时可能…

离岗识别 yolov5模型

离岗识别通过yolov5网络模型技术&#xff0c;离岗识别可以自动识别现场画面中人员离岗脱岗睡岗等行为&#xff0c;发现违规行为立即抓拍告警。YOLOv5在YOLOv4算法的基础上做了进一步的改进&#xff0c;检测性能得到进一步的提升。虽然YOLOv5算法并没有与YOLOv4算法进行性能比较…

Unity3d 开发Pico4程序闪退弹窗【版权保护】检测的解决方法

前言 最近在进行基于Pico4的应用开发&#xff0c;然后在部分设备上程序是无法正常进入的&#xff0c;而且总是弹出这个版权保护的窗口&#xff1a; 按理说正常的自己开发的测试的程序不应该有这种限制&#xff0c;查询后发现是 PICO 内置了版权保护机制。应用上架后&#xff0…

研发工程师玩转Kubernetes——通过文件创建Service

在《研发工程师玩转Kubernetes——部署应用》一文中&#xff0c;我们使用kubectl expose创建了一个Service&#xff0c;暴露了一个Pod上的nginx服务。这篇文章我们将使用文件的形式创建Service。 为了增加有趣性&#xff0c;我们采用《研发工程师玩转Kubernetes——构建、推送自…

与众不同的夜间开关交互效果

这个夜间模式切换开关效果是不是很炫酷&#xff0c;在短视频曾刷到过是一个国外的设计师看似是为了难为我们前端开发设计了一个元素超多且动画复杂的开关切换效果。 结果在逛 codepen 的时候发现真的被一个大佬给做出来了&#xff0c;效果真的很不错&#xff0c;而且还在原来的…

矩池云教程|体验 OpenAI 最近推出的 3D 生成模型 Shap-E!

Shap-E 是由 OpenAI 最近推出的3D生成模型&#xff0c;使用者可以通过简单的文字或图像进行三维模型的生成&#xff0c;OpenAI 认为相比于点云的生成模型Point-E&#xff0c;Shap-E的收敛更快。本文将展示如何在矩池云上体验3D模型生成。 Shap-E&#xff1a;https://github.co…