选项式API和组合式API

选项式API式一种通过包含多个选项的对象来描述组件逻辑的API,如data、methods、computed、watch等。在组件的初始化阶段,Vue会处理这些选项,吧选项中定义的数据、方法、计算属性、侦听器等内容添加到组件实例上,当页面渲染完成后,通过this关键字可以访问组件实例。

1
2
3
4
5
6
7
8
9
10
<script>
export default {
data(){
return{ }
},
methods:{ },
computed:{ },
watch:{ }
}
</script>

组合式API式将组件中的数据、方法、计算属性、侦听器等代码全部组合在一起,写在setup()函数中。

1
2
3
4
5
6
7
8
<script>
import { computed, ref, watch } from 'vue'
export default {
setup(){
return{ }
}
}
</script>

语法糖

1
2
3
4
<script setup>
import {computed, ref, watch} from 'vue'
...
</script>

Vue提供的选项式API和组合式API是同一底层系统所提供的两套不同的接口,选项式API是在组合式API的基础上实现的。使用组合式API可以讲项目的每个功能的数据和方法放到一起,能够快速定位到功能区域的相关代码,便于阅读和维护。同时,组合式API可以通过函数来实现高效的逻辑复用。

生命周期钩子函数

组件的生命周期是指每个组件从创建到被销毁的整个过程,每个组件都有生命周期,如果想要在某个特定的时机进行特定的处理,可以使用生命周期钩子函数。

Vue2生命周期 Vue3生命周期 说明
beforeCreate - 实例对象创建前
created - 实例对象创建后
beforeMount onBedoreMount 挂载前
mounted onMounted 挂载后
beforeUpdate onBeforeUpdate 更新前
updated onUpdated 更新后
beforeDestroy onBeforeUnmount 销毁前
destroyed onUnmounted 销毁后

组件的注册和引用

组件是Vue的基本结构单元,开发者可以将页面中独立的、可重用的部分封装成组件,对组件的结构、样式和行为进行设置,组件之间的可以相互引用。

注册组件

  1. 全局注册

    在main.js中注册全局组件,可以在任何组件中使用。

    1
    2
    3
    4
    5
    6
    import { createApp } from 'vue'
    import App from './App.vue'
    import MyComponent from './components/MyComponent.vue' // 引入组件
    const app = createApp(App)
    app.component('MyComponent', MyComponent) //在挂载前注册组件
    app.mount('#app')
  2. 局部注册

    只能在当前注册范围内使用

    1
    2
    3
    4
    5
    6
    7
    8
    <script>
    import MyComponent from './components/MyComponent.vue'
    export default {
    components: {
    MyComponent : MyComponent // 可以简写为 'MyComponent',组件名和组件对象名一致
    }
    }
    </script>

    如果使用setup语法糖,导入的组件会被自动注册,无须手动注册,导入后可以直接在模板中使用。

引用组件

被引用的组件需要写成标签的形式,标签名与组件名对应,命名可以使用短横线分隔命名法,如my-component,也可以使用驼峰命名法,如MyComponent

1
2
3
<template>
<MyComponent></MyComponent>
</template>

解决组件之间的样式冲突

scoped属性

<style>标签添加scoped属性后,Vue会自动为当前组件的DOM元素添加一个唯一的自定义属性,比如data-v-xxxxxx,然后在CSS选择器中添加该属性,以确保样式只作用于当前组件的DOM元素。

深度选择器

如果添加了scoped属性后还需要让某些样式对子组件生效,则可以通过深度选择器实现。被编译后的选择器为[data-v-xxxxxx] .child

1
2
3
4
5
<style scoped>
:deep(.child) {
color: red;
}
</style>

父组件向子组件传递数据(props)

  1. 在子组件中声明props属性,接收父组件传递的数据

    1
    2
    3
    4
    5
    6
    7
    <script>
    export default {
    props: {
    msg: String
    }
    }
    </script>

    可以不限制props类型,写成字符串数组形式的props,如props: ['msg']

    如果使用setup语法糖:

    1
    2
    3
    <script setup>
    const props = defineProps(['msg']) // 可以写成字符串数组形式或多个对象参数
    </script>
  2. 在父组件中传递数据

    如果数据时固定不变的,则通过静态绑定props方法。

    1
    <MyComponent msg="Hello"></MyComponent>

    使用v-bind动态绑定props,可以将父组件的数据动态传递给子组件。

    1
    <MyComponent :msg="msg"></MyComponent>

    在Vue中,所有的props都遵循单项数据流原则,props数据因父组件的更新而变化,变化后的数据将向下流向子组件,而且不会逆向传递
    这样可以防止子组件意外变更props导致数据流向难以理解的问题。每次父组件绑定的props发生变更时,子组件中的props都将会刷新为最新的值。

  3. 验证props

    1. 基础类型检查
    2. 必填项校验
    3. 属性默认值
    4. 自定义验证函数validator()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
props: {
// 基础类型检查
propA: Number,
// 多种类型
propB: [String, Number],
// 必填项
propC: {
type: String,
required: true
},
// 默认值
propD: {
type: Number,
default: 100
},
// 自定义验证函数
propE: {
validator(value) {
return value > 10
}
}
}

子组件向父组件传递数据

子组件向父组件传递数据,可以通过自定义事件来实现

  1. 在子组件中声明自定义事件

    1
    2
    3
    <script>
    const emit = defineEmits(['change']) // 字符串数组
    </script>
  2. 在子组件中触发自定义事件

    第一个参数为自定义时间的名称,第二个参数为需要传递的数据。

    1
    <button @click="$emit('change', 'Hello')">点击</button>

    setup函数

    1
    2
    3
    4
    5
    6
    7
    8
    export default {
    setup(props, ctx) {
    const handleClick = () => {
    ctx.emit('change', 'Hello')
    }
    }
    return { handleClick }
    }

    setup语法糖

    1
    2
    3
    4
    5
     <script setup>
    cosnt update = () => {
    emit('change', 'Hello')
    }
    </script>
  3. 在父组件中监听自定义事件

    1
    <MyComponent @change="handleChange"></MyComponent>
    1
    2
    3
    4
    5
    <script>
    const handleChange = (value) => {
    console.log(value)
    }
    </script>

跨级组件之前的数据传递

通过依赖注入实现跨级组件之间的数据传递,父组件作为依赖提供者,使用provide()函数,子组件如果想注入上层组件或整个应用提供的数据,需要使用inject()函数。

  1. provide()函数

    1
    2
    3
    4
    5
    6
    7
    8
    <script>
    import { provide } from 'vue'
    export default {
    setup() {
    provide('name', 'Tom') // 第一个参数是注入名,第二个参数是注入的值
    }
    }
    </script>

    使用语法糖

    1
    2
    3
    4
    <script setup>
    import { provide } from 'vue'
    provide('name', 'Tom')
    </script>

    全局依赖

    1
    2
    const app = createApp(APP)
    app.provide('name', 'Tom')
  2. inject()函数

    通过inject可以注入上层组件或者整个应用提供的数据,第一个参数是注入值,第二个参数是默认值,第三个参数是布尔值,代表是否将默认值视为工厂函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    import { inject } from 'vue'
    export default {
    setup() {
    // 注入一个值
    const name = inject('name', 'Jerry') // 第一个参数是注入名,第二个参数是默认值。
    // 当没有匹配到注入的值时,默认值可以是工厂函数
    const baz = inject('baz', () => new Map())
    // 注入时为了表明提供的默认值是函数,需要传入第三个参数
    const fn = inject('function', () => { }, false)
    }
    }
    </script>

工厂函数

  1. 把对象的创建过程封装在一个函数里;
  2. 显式返回一个对象(可以是字面量对象、也可以是其它构造结构);
  3. 相比于构造函数或类,使用工厂函数无需 new,避免了构造函数中 this 指向的问题。

动态组件

定义动态组件

使用<component>标签可以动态切换不同的组件,通过绑定is属性决定哪个组件被渲染。

1
<component :is="currentComponent"></component>

利用KeepAlive组件缓存动态组件

使用动态组件实现组件之间的按需切换时,隐藏的组件会被销毁,展示出来的组件会被重新创建,这样会导致组件的状态丢失。为了解决这个问题,可以使用<KeepAlive>组件对动态组件进行缓存。

1
2
3
<KeepAlive>
<component :is="currentComponent"></component>
</KeepAlive>

相关生命周期

  • onActivated:被包裹的动态组件被激活时触发
  • onDeactivated:被包裹的动态组件被停用时触发

KeepAlive组件的常用属性

属性 类型 说明
include String、RegExp 只有名称匹配的组件会被缓存
exclude String、RegExp 名称匹配的组件不会被缓存
max Number 缓存组件的最大数量

插槽

组件封装期间为组件的使用者预留的占位符,允许组件的使用者在组件内展示特定的内容

默认插槽

定义插槽

1
2
3
4
5
<template>
<div>
<slot></slot>
</div>
</template>

使用插槽

1
2
3
<MyComponent>
<p>插槽内容</p>
</MyComponent>

具名插槽

当Vue中需要定义多个插槽时,可以通过具名插槽来区分不同的插槽。具名插槽是给每一个插槽定义一个name名称,默认插槽的名称是default。

定义具名插槽

1
2
3
4
5
6
<template>
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>

使用具名插槽

1
2
3
4
5
6
7
8
<MyComponent>
<template v-slot:header>
<p>头部插槽内容</p>
</template>
<template #footer>
<p>底部插槽内容</p>
</template>
</MyComponent>

作用域插槽

作用域插槽时带有数据的插槽,子组件提供一部分数据给插槽,父组件接受子组件的数据进行页面渲染。

定义数据

1
<slot :data="data"></slot>

使用数据

1
2
3
4
5
<MyComponent>
<template v-slot:default="slotProps">
<p>{{slotProps.data}}</p>
</template>
</MyComponent>

自定义指令

自定义指令方便开发者通过直接操作DOM元素来实现业务逻辑

  • 私有自定义指令:组件内不定义,只在当前组件内使用
  • 全局自定义指令:在main.js中定义,全局使用

自定义指令的生命周期函数

函数名 说明
created 在绑定元素的属性前调用
beforeMount 绑定元素挂载之前调用
mounted 绑定元素的父组件及其自身所有子节点都挂载完成后调用
beforeUpdate 绑定元素的父组件更新之前调用
updated 绑定元素的父组件更新完成后调用
beforeUnmount 绑定元素的父组件卸载之前调用
unmounted 绑定元素的父组件卸载完成后调用

常用的自定义指令声明周期函数的参数

  • el:指令所绑定的元素,可以用来直接操作DOM
  • binding:一个对象,包含多个属性,用于接收属性的参数值
    • value: 传递给指令的值
    • arg:传递给指令的参数
    • oldValue:之前的值,仅仅在update相关中可用
    • modifiers:一个包含修饰符的对象
    • instance:指令所绑定的组件实例
    • dir: 指令的定义对象
  • vnode:代表绑定元素底层的虚拟节点
  • prevNode:之前页面渲染中指令所绑定元素的虚拟节点

私有自定义指令

声明

1
2
3
4
5
6
7
8
9
10
<template>
<div v-myDirective></div>
</template>
<script>
export default {
directives: {
myDirective:{ }
}
}
</script>

使用语法糖则不需要声明directives

1
2
3
4
5
6
7
<script setup>
const myDirective = {
mounted: (el) => {
el.style.color = 'red'
}
}
</script>

全局自定义指令

声明

1
2
3
4
5
6
7
8
9
10
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('myDirective', {
mounted: (el) => {
el.style.color = 'red'
}
})
app.mount('#app')

为自定义指令绑定参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div v-color="color"></div>
<div v-demo="{color: 'red', text: 'hello'}"></div>
</template>
<script>
const vColor = {
mounted: (el, binding) => {
el.style.color = binding.value
}
}
const vDemo = {
mounted: (el, binding) => {
el.style.color = binding.value.color
el.innerText = binding.value.text
}
}
</script>

对于自定义指令来说,通常仅需要在mounted和updated函数中操作DOM元素,如果这两个函数中的操作逻辑相同,可以将操作逻辑提取出来,封装成一个函数,然后在mounted和updated函数中调用。

引用静态资源

  • public目录用于存放不可以编译的静态资源文件,该目录下的文件会被复制到打包目录,该目录下的文件需要使用绝对路径访问。
  • src\assets目录用于存放可编译的静态资源文件,例如土坯啊样式文件等,该目录下需要使用相对路径访问。