Vue3+TS知识点补充

一、关于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>

Vue3+TS知识点补充_第1张图片
需要注意的是,如果响应函数里有Ref,则shallowRef也会变成深响应式,浅响应式shallowRef会被深响应式ref影响。
Vue3+TS知识点补充_第2张图片
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);
};

Vue3+TS知识点补充_第3张图片

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>

Vue3+TS知识点补充_第4张图片

五、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
Vue3+TS知识点补充_第5张图片
组件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>

Vue3+TS知识点补充_第6张图片
总结:

  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







Vue3+TS知识点补充_第7张图片

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

1.配置局部组件

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

2.配置全局组件

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






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')

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


结果展示:
Vue3+TS知识点补充_第8张图片

3.配置递归组件

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







在其他页面使用递归组件




结果展示:
Vue3+TS知识点补充_第9张图片

九、动态组件

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

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

通过is切换A、B组件

  

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







效果展示:
Vue3+TS知识点补充_第10张图片
注意事项:
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. 当应用程序比较庞大时,需要优化应用程序的加载速度和时间。在这种情况下,将组件拆分成多个小模块,并使用异步加载以降低应用程序的初始负载就非常重要了。
    如骨架屏的实现
    Vue3+TS知识点补充_第11张图片

  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 可以被用于异步组件的定义中,帮助我们更方便地实现组件的异步加载。

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




结果如下:
Vue3+TS知识点补充_第13张图片
这时,我们就可以使用teleport来实现将子组件Dialog渲染到指定DOM节点body
代码如下:






结果如下:
Vue3+TS知识点补充_第14张图片

十二、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 被移除),在过渡/动画完成之后移除。

案例:






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
基本用法:

An animated element


copy此类名,调用时加上类名animate__animated动画效果就能生效。
Vue3+TS知识点补充_第15张图片

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


Vue3+TS知识点补充_第16张图片

十四、transition-group过渡列表

1. 过渡列表的简单用法

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






结果展示:
Vue3+TS知识点补充_第17张图片

2.过渡列表的移动过渡

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






结果展示:

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




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




C.vue




结果展示:
Vue3+TS知识点补充_第18张图片

十六、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 =>
  6. {item}
  7. ) }
  8. 支持v-show
  9. reftemplate自动解包.value,但tsx不会,要手动加.value

案例代码:
App.tsx

// 写法一
// export default function () {
//     return (
小5
)
// } // 写法二:optionsAPI // import { defineComponent } from "vue" // export default defineComponent({ // data() { // return { // age: 23 // } // }, // render() { // return (
{this.age}
)
// } // }) // 写法三: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




结果展示:
Vue3+TS知识点补充_第19张图片

十七、深入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>

结果展示:
Vue3+TS知识点补充_第20张图片

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>

结果展示:
Vue3+TS知识点补充_第21张图片

3.自定义修饰符

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

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

十八、自定义指令directive

在Vue中,自定义指令(Directive)是一种非常有用的功能,可以将对DOM元素的操作封装到可复用的指令中。指令可以用于添加样式、绑定事件、自定义触发器等方面,它们提供了一种简单而强大的方式来扩展HTML。

Vue中有v-ifv-forv-showv-model等等一系列方便快捷的指令,下面我们来了解一下vue中的提供的自定义指令。

1.Vue3指令的钩子函数

  • created:元素初始化的时候
  • beforeMount:指令绑定到元素后调用,只调用一次
  • mounted:元素插入父级DOM调用
  • beforeUpdate:元素被更新之前调用
  • update:这个周期方法被移除,改用updated
  • beforeUnmount:在元素被移除前调用
  • unmounted:指令被移除后调用,只调用一次

Vue2指令的钩子函数:bind、inserted、update、componentUpdated、unbind。

2.钩子函数参数详解

第一个参数:el,表示当前绑定的DOM元素
第二个参数:bindingbinding是一个Object,其他包含以下属性。

  • arg:传递给指令的参数,如果有的话。例如在v-my-direactive:foo中,argfoo
  • dir:一个对象,在注册指令时作为参数传递,例如在自定义指令里重写了生命周期createdmounted,这俩将作为函数存在dir中。
  • instance:使用指令的组件实例。
  • modifiers:包含修饰符(如果有的话)的对象,例如在v-my-directive.bar中,修饰符对象为{bar: true
  • oldValue:先前的值,仅在beforeUpdateupdated中可用,无论值是否有更改都可用。
  • value:传递给指令的值。例如,在v-my-directive="1 + 1",该值为2。
    下面我们通过以下案例来体会参数
    App.vue
<template>
  <button @click="show = !show">开关</button>
  <div
    v-if="show"
    v-move-directive:foo.bar="{ background: 'green' }"
    style="width: 100px; height: 100px; background-color: #ccc"
  ></div>
</template>

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

let show = ref(true);
const vMoveDirective: Directive = {
  created() {},
  mounted(el: any, dir: any) {
    console.log("el:", el);
    console.log("dir:", dir);
    // 通过自定义指令传来的value改变样式
    el.style.background = dir.value.background;
  },
};
</script>

结果展示
Vue3+TS知识点补充_第22张图片

3.案例:使用自定义指令实现按钮权限控制

<template>
  <div class="btns">
    <!-- 通过自定义指令根据按钮的权限控制按钮的显示与隐藏 -->
    <button v-has-show="'shop:create'">创建</button>
    <button v-has-show="'shop:edit'">编辑</button>
    <button v-has-show="'shop:delete'">删除</button>
  </div>
</template>

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

// 本地存储用户id
localStorage.setItem("userId", "xiao5-per");

// mock后台返回的权限数据
const permission = [
  "xiao5-per:shop:edit",
  "xiao5-per:shop:create",
  // "xiao5-per:shop:delete",
];
// 获取本地存储的用户id
const userId = localStorage.getItem("userId") as string;
// 自定义指令
const vHasShow: Directive<HTMLElement, string> = (el, binding) => {
  // 如果权限不存在则隐藏按钮
  if (!permission.includes(userId + ":" + binding.value)) {
    el.style.display = "none";
  }
};
</script>

<style lang="scss" scoped>
.btns {
  button {
    margin: 10px;
  }
}
</style>

由于mock的后台返回的数据没有删除按钮的权限,因此,删除按钮被隐藏,结果展示如下:
Vue3+TS知识点补充_第23张图片

4.案例:自定义指令实现拖拽指令

<template>
  <div v-move class="box">
    <div class="header"></div>
    <div>内容</div>
  </div>
</template>

<script setup lang="ts">
import { Directive, DirectiveBinding } from "vue";
const vMove: Directive<any, void> = (
  el: HTMLElement,
  binding: DirectiveBinding
) => {
  // 获取header元素
  let moveElement: HTMLDivElement = el.firstElementChild as HTMLDivElement;
  // console.log(moveElement);
  // 鼠标按下时的回调
  const mouseDown = (e: MouseEvent) => {
    let X = e.clientX - el.offsetLeft;
    let Y = e.clientY - el.offsetTop;
    // 鼠标移动的回调
    const mouseMove = (e: MouseEvent) => {
      // console.log(e);
      // 更改el的位置
      el.style.left = e.clientX - X + "px";
      el.style.top = e.clientY - Y + "px";
    };
    // 鼠标按下时开始监听鼠标移动并触发其回调
    document.addEventListener("mousemove", mouseMove);
    document.addEventListener("mouseup", () => {
      // 鼠标抬起时终止监听鼠标移动并终止回调
      document.removeEventListener("mousemove", mouseMove);
    });
  };
  // 监听鼠标按下的情况
  moveElement.addEventListener("mousedown", mouseDown);
};
</script>
<style lang="scss" scoped>
.box {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  border: 1px solid #ccc;
  .header {
    height: 20px;
    background: black;
    cursor: move;
  }
}
</style>

结果展示:
Vue3+TS知识点补充_第24张图片

5.案例:自定义指令实现图片懒加载

<template>
  <div>
    <img
      v-lazy="item"
      width="360"
      height="500"
      v-for="item in arr"
      :key="item"
      alt=""
    />
  </div>
</template>

<script setup lang="ts">
// import { ref, reactive } from 'vue'
// vite自带了读取文件的方法,如下
// glob 是懒加载模式
// let modules = {
//   'xxxxx': () => import('xxxxx')
// }
// globEager 是静态加载模式
// import xxxxx from 'xxxxx'

import { Directive } from "vue";

// 加了eager参数后就等同于globEager
let imageList: Record<string, { default: string }> = import.meta.glob(
  "./assets/images/*.*",
  { eager: true }
);
// console.log(imageList);
let arr = Object.values(imageList).map((item) => item.default);
// console.log(arr);

let vLazy: Directive<HTMLImageElement, string> = async (el, binding) => {
  const def = await import("./assets/vue.svg");
  el.src = def.default;
  // console.log(el);
  // IntersectionObserver 是一种现代的异步浏览器 API,可以用于监视目标元素
  // (通常是指网页中的某个 DOM 元素)与其祖先元素或顶级文档视窗之间的交叉状态。
  // 这个 API 通过异步方式,提供了对所监视元素的交差信息的回调和事件。
  const observer = new IntersectionObserver((entries) => {
    // console.log(entries[0]);
    // console.log("value:", binding.value);
    if (entries[0].intersectionRatio > 0) {
      el.src = binding.value;
      console.log("懒加载一张");
      // 取消监听
      observer.unobserve(el);
    }
  });
  observer.observe(el);
};
</script>

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

结果展示:

知识点补充:
IntersectionObserver 是一种现代的异步浏览器 API,可以用于监视目标元素(通常是指网页中的某个 DOM 元素)与其祖先元素或顶级文档视窗之间的交叉状态。这个 API 通过异步方式,提供了对所监视元素的交差信息的回调和事件。

通过这个 API,网页开发人员可以更好地进行性能优化,动态加载和卸载内容,从而为用户提供更快、更流畅和更可靠的体验。它尤其适用于对于那些已经在节流、分块加载、预加载等方面进行了优化操作并仍然有性能瓶颈的页面。

基本用法:

const observer = new IntersectionObserver((entries) => {
    // console.log(entries[0]);
    // console.log("value:", binding.value);
    if (entries[0].intersectionRatio > 0) {
      el.src = binding.value;
      console.log("懒加载一张");
      // 取消监听
      observer.unobserve(el);
    }
  });
  observer.observe(el)

IntersectionObserver中回调函数的参数entries所返回的对象的每个属性的含义如下:

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒。
  • target:被观察的目标元素,是一个DOM节点对象。
  • rootBounds:容器元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有容器元素(即直接相对于视口滚动),则返回null。
  • boundingClientRect:目标元素的矩形区域的信息。
  • intersectionRect:目标元素与视口(或容器元素)的交叉区域的信息。
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,即目标元素在可见区域展现的面积占目标元素的面积的比例,完全可见时为1,完全不可见时小于等于0。

十九、Vue3定义全局函数和变量

1.Vue3与Vue2定义全局函数和变量的区别

Vue2

Vue.prototype.$http = () => {}

Vue3

const app = createApp(App)
app.config.globalProperties.$http = () => {}

2.定义一个全局filters

vue3取消了filters,这里我们自己全局配置一个filters。
main.ts

import { createApp } from 'vue'
import App from './App.vue'

export const app = createApp(App)

// vue3取消了filters,这里我们自己全局配置一个filters
app.config.globalProperties.$filters = {
    format<T extends any>(str: T): string {
        return `${str}`
    }
}
// 定义一个全局属性
app.config.globalProperties.$env = 'dev'

type Filter = {
    format<T>(str: T): string
}

// 配置全局事件/属性的声明代码[格式都是一致的]
declare module 'vue' {
	// ComponentCustomProperties是一个内置的Vue.js接口,可用于向组件实例添加自定义属性。
    export interface ComponentCustomProperties {
        $filters: Filter,
        $env: string
    }
}
app.mount('#app')

上述代码实现了定义一个全局函数filters和全局属性env。并且使用内置apiComponentCustomProperties实现全局事件/属性的声明。

下面我们通过以下代码尝试使用全局函数/属性。
App.vue

<template>
  <h5>template使用全局属性/函数</h5>
  <div>{{ $filters.format("尼赣嘛") }}</div>
  <div>{{ $env }}</div>
</template>

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

const instance = getCurrentInstance();
// setup语法使用全局属性/函数
console.log(instance?.proxy?.$filters.format("尼赣嘛"));
console.log(instance?.proxy?.$env)
</script>

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

结果展示:
Vue3+TS知识点补充_第25张图片

二十、编写Vue3插件

Vue 3 的插件系统与 Vue 2 类似,它允许开发者对 Vue 核心添加全局功能或自定义组件,并可以在不同的组件中共享这些公用的逻辑和状态。在 Vue 3 中,推荐使用新的 app.use() API 来安装插件。

1.编写一个Loading插件

插件的vue文件
components/Loading/index.vue

<template>
  <!-- 插件内的html -->
  <div v-if="isShow" class="loading">
    <div class="loading-content">Loading</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
// 插件内的函数
const isShow = ref(false); // 定位loading1的开关
// 插件内的方法
const show = () => {
  isShow.value = true;
};

const hide = () => {
  isShow.value = false;
};

// 对外暴露 当前组件的属性和方法
defineExpose({
  isShow,
  show,
  hide,
});
</script>

<style lang="scss" scoped>
.loading {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  &-content {
    font-size: 30px;
    color: #fff;
  }
}
</style>

配置一个插件对象
components/Loading/index.ts

import { createVNode, render, VNode, App } from "vue";
// 引入插件的vue文件
import Loading from './index.vue'
// 暴露插件对象
export default {
    install(app: App) {
        
        // createVNode:vue底层方法,可以给组件创建一个DOM 也就是Vnode
        const vnode: VNode = createVNode(Loading)
        // render把我们的Vnode渲染成真实的DOM 并挂载到指定节点
        render(vnode, document.body)  // 第二个参数是挂载点

        console.log(app);
        console.log(vnode);
        // 可以通过vnode中的component属性的exposed获取暴露的属性和方法
        console.log(vnode.component?.exposed);
        
        // Vue提供的全局配置 可自定义
        app.config.globalProperties.$loading = {
            show: () => vnode.component?.exposed?.show(),
            hide: () => vnode.component?.exposed?.hide()
        }
        
        // 调用全局注册的$loading
        // app.config.globalProperties.$loading.show()
    }
}

修改入口文件的全局配置项,注册插件
main.ts

import { createApp } from 'vue'
import App from './App.vue'
import Loading from './components/Loading'

export const app = createApp(App)
// 启用插件
app.use(Loading)
// 定义类型声明
type Loading = {
    show: () => void,
    hide: () => void 
}
// 配置全局事件的声明代码
declare module 'vue' {
    export interface ComponentCustomProperties {
    	// 声明loading插件
        $loading: Loading
    }
}
app.mount('#app')

App.vue中使用插件

<template>
  <div></div>
</template>

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

const instance = getCurrentInstance();
// 调用插件的show方法展示loading页面
instance?.proxy?.$loading.show();
// 调用插件的hide方法隐藏loading页面
// instance?.proxy?.$loading.hide();
</script>

结果展示:
Vue3+TS知识点补充_第26张图片

2.知识点回顾

下面我们编写一个包含先前学习的知识点的简易插件

// 定义一个插件对象,该对象需要包含 install 方法
const ExamplePlugin = {
  install(app) {
    // 在 Vue 应用 app 上挂载 $hello 属性,值为 "Hello World!"
    app.config.globalProperties.$hello = 'Hello World!';

    // 定义自定义指令 v-focus,用于聚焦元素
    app.directive('focus', {
      mounted(el) {
        el.focus();
      },
    });

    // 定义一个全局组件 example-component,具有单个属性 message
    app.component('example-component', {
      props: ['message'],
      template: '
{{ message }}
'
, }); }, }; // 在 Vue 应用上安装插件 createApp(App).use(ExamplePlugin).mount('#app');

在这个示例代码中,我们首先定义了一个插件对象 ExamplePlugin,其中包含了一个名为install 的方法,接受一个应用实例 (app) 作为唯一参数。然后在 install 方法中,我们又通过 app 对象分别实现了三个全局功能:

  • 使用 app.config.globalProperties 存储在任意应用实例上挂载全局属性。
  • 使用 app.directive 注册自定义指令 focus,用于聚焦元素。
  • 使用 app.component 注册全局组件 example-component,并注入属性 message

最后,在应用启动时,我们将这个插件对象安装到应用实例之中,并调用 mount 方法挂载根组件。接着,在应用实例里就可以使用 $hello 全局属性了,也就可以在任何其他组件中直接引用 v-focus 指令和 标签了。

二一、详解Scoped和样式传透

样式穿透主要用于修改很多vue常用的组件库(element、vant、AntDesign),组件库虽然配好了默认样式,但还是需要更改其他样式,样式穿透就派上用场了。

1.scoped的原理

vue中的scoped通过在DOM结构以及CSS样式上加唯一不重复的标记:data-v-hash的方式,以保证唯一(而这个工作是由PostCSS转译实现的),达到样式私有化、模块化的目的。
总结一下scoped的三条渲染规则

  1. 给HTML的DOM节点加一个不重复的data属性(形如:data-v-123)来表示它的唯一性。
  2. 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式。
  3. 如果组件内部包含其他组件,那么只会给其他组件的最外层标签加上当前组件的data属性。
    PostCSS会给一个组件中的所有DOM添加一个独一无二的动态属性data-v-xxx,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件的DOM,这种做法使得样式只作用于含有该属性的DOM——组件内部DOM,从而达到了**‘样式模块化’**的效果。

下面我们通过实例在检验上述的三条渲染规则:

加了scoped
Vue3+TS知识点补充_第27张图片
没加scoped
Vue3+TS知识点补充_第28张图片
根据上述两图成功验证了规则1:给HTML的DOM节点加一个不重复的data属性(形如:data-v-123)来表示它的唯一性。
加了scoped
Vue3+TS知识点补充_第29张图片

没加scoped
Vue3+TS知识点补充_第30张图片
根据上述两图成功验证规则2: 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式

加了scoped
Vue3+TS知识点补充_第31张图片

根据上图成功验证规则3:如果组件内部包含其他组件,那么只会给其他组件的最外层标签加上当前组件的data属性。

2.样式穿透

样式为何需要穿透?
假设我们成功查找到输入框的样式类,并需要通过此样式类来更改输入框的背景色,如下图:
Vue3+TS知识点补充_第32张图片
使用样式穿透后:
Vue3+TS知识点补充_第33张图片
这里需要注意区分一下Vue2和Vue3样式穿透的写法:
Vue2写法:

/deep/ .el-input__inner {
	background: red;
}

Vue3写法:

:deep(.el-input__inner) {
	background: red;
}

3.插槽选择器

Vue3+TS知识点补充_第34张图片
插槽选择器使用在子组件中,通过引用父组件所调用子组件的class或id,插槽选择器的格式:

:slotted(子组件class/id) {
	...
}

4.全局选择器

Vue3+TS知识点补充_第35张图片
在之前我们想加入全局样式,通常都是新建一个style标签,不加scoped


 

现在有更优雅的解决方案:


5.动态CSS

Vue3+TS知识点补充_第36张图片

<template>
  <div class="div1">动态CSS</div>
  <div class="div2">尼赣嘛</div>
</template>

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

// 定义成string
const color1 = ref("red");
// 定义成对象
const color2 = ref({
  color: "yellow",
});
</script>

<style lang="scss" scoped>
.div1 {
  color: v-bind(color1);
}
.div2 {
  // 定义成对象的写法
  color: v-bind("color2.color");
}
</style>

6.CSS module写法

Vue3+TS知识点补充_第37张图片