组件化开发的重要性:在现代前端开发中,组件化已经成为一种标准实践。组件化允许开发者将复杂的用户界面分解成小的、独立的、可复用的部分。通过解决组件开发中的问题,开发者可以学习到如何处理数据流、状态管理、事件处理等关键概念。组件库的开发往往需要考虑如何组织代码、如何进行模块化、如何处理依赖管理等工程问题。这些实践有助于提升工程架构能力,使开发者能够构建出可扩展、可维护的大型应用。
本文将带你从头搭建vue3组件化项目实战,关注我跟我一起学习吧~
Vue3组件库开发项目实战——01组件开发必备知识导学-CSDN博客
Vue3组件库开发项目实战——02项目搭建(配置Eslint/Prettier/Sass/Tailwind CSS/VitePress/Vitest)-CSDN博客
一、Vue 3 简介
Vue 3 是一款流行的前端 JavaScript 框架,用于构建交互式的用户界面。Vue 3 是 Vue.js 的最新版本,带来了许多重要的改进和新特性,包括:
-
性能优化:Vue 3 在性能方面进行了优化,包括更快的渲染速度和更小的包大小,提升了应用的性能表现。
-
Composition API:Vue 3 引入了 Composition API,使得组件逻辑更易复用和组织,提高了代码的可维护性和可读性。
-
Teleport:Vue 3 引入了 Teleport 特性,可以将组件的内容渲染到任意 DOM 节点,方便实现模态框、弹出框等功能。
-
Fragments:Vue 3 支持 Fragments,允许组件返回多个根节点,简化了组件的编写。
-
TypeScript 支持:Vue 3 对 TypeScript 的支持更加友好,提供了更好的类型推断和提示,帮助开发者编写更加稳健的代码。
-
更好的响应式系统:Vue 3 中的响应式系统进行了重构,使得对数据的追踪更加高效和精确。
二、Vue 3 组件开发基础知识
- 组件基础概念:组件是 Vue 应用的基本构建块,可以封装可复用的代码和 UI 元素。
- 单文件组件:Vue 3 推荐使用单文件组件,将模板、脚本和样式组织在一个文件中,便于维护和管理。
- 组件通信:Vue 3 中组件之间可以通过 props、emit、provide/inject 等方式进行通信。
- 生命周期钩子:Vue 3 组件有多个生命周期钩子,如 created、mounted、updated、destroyed 等,用于在不同阶段执行逻辑。
- 插槽:插槽是 Vue 3 中用于分发内容的机制,可以让父组件向子组件传递内容。
- 动态组件:Vue 3 支持动态组件,可以根据条件渲染不同的组件。
- 异步组件:Vue 3 支持异步组件加载,可以实现按需加载组件,提高应用性能。
理解vue的响应式和生命周期
Vue的响应式系统是Vue框架的核心特性之一,它使得数据驱动视图的开发变得更加简单和高效。Vue3的响应式系统基于ES6的Proxy对象实现,当数据发生变化时,视图会自动更新。
Vue
实例有一个完整的生命周期,也就是从开始创建
、初始化数据
、编译模版
、挂载Dom -> 渲染
、更新 -> 渲染
、卸载
等一系列过程,我们称这是Vue
的生命周期
Vue
生命周期总共分为8个阶段创建前/后
,载入前/后
,更新前/后
,销毁前/后
beforeCreate
=>created
=>beforeMount
=>Mounted
=>beforeUpdate
=>updated
=>beforeDestroy
=>destroyed
。keep-alive
下:activated
deactivated
其他几个生命周期
beforeCreate
初始化vue
实例,进行数据观测。执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务created
组件初始化完毕,可以访问各种数据,获取接口数据等beforeMount
此阶段vm.el
虽已完成DOM
初始化,但并未挂载在el
选项上mounted
实例已经挂载完成,可以进行一些DOM
操作beforeUpdate
更新前,可用于获取更新前各种状态。此时view
层还未更新,可用于获取更新前各种状态。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。updated
完成view
层的更新,更新后,所有状态已是最新。可以执行依赖于DOM
的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。destroyed
可以执行一些优化操作,清空定时器,解除绑定事件- vue3
beforeunmount
:实例被销毁前调用,可用于一些定时器或订阅的取消 - vue3
unmounted
:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
Vue 3 响应式系统
Vue 3 的响应式是基于 Proxy 对象实现的,它使得任何数据对象一旦被 Vue 实例的数据属性所引用,就会自动成为响应式的。当数据发生变化时,Vue 会自动检测并更新视图。Vue 3 的响应式系统主要包括以下几个关键概念:
- 数据劫持(Dep): Vue 通过依赖收集器(Dep)来跟踪数据的变化。
- 响应式属性(Reactive): 使用
reactive
或ref
创建的数据对象是响应式的。 - 响应式数组(Computed Properties): 可以基于其他响应式数据创建计算属性,它们会根据依赖的数据变化自动更新。
- 深度监听(Computed with Deep Watch): 对于嵌套的对象或数组,Vue 会自动进行深度监听。
在实际应用中,利用Vue的响应式系统来构建数据驱动的交互式界面,通过生命周期钩子函数来管理组件的生命周期,执行一些初始化、清理或异步操作等。合理地理解和应用Vue的响应式系统和生命周期可以帮助你更好地开发Vue应用,提高开发效率和代码质量。
setup/ref/reactive
Vue 3中,定义响应式数据和逻辑是通过
setup
函数来实现的。setup
函数是Vue 3中新引入的特性,用于替代Vue 2中的data
、computed
、watch
等选项。在setup
函数中,你可以定义响应式数据、计算属性、方法等,并最终将它们暴露给模板使用。
在setup
函数中,你可以通过返回一个对象来暴露数据和方法给模板使用。在这个返回的对象中,可以包含data
、computed
、methods
等属性,它们会被自动合并到组件实例中。在setup
函数中定义的数据会自动成为响应式数据,当数据发生变化时,相关的视图会自动更新。
下面是一个简单的示例,演示了如何在Vue 3中使用setup
函数定义响应式数据:
import { ref, reactive } from 'vue';
export default {
setup() {
// 使用ref定义基本类型的响应式数据
const count = ref(0);
// 使用reactive定义复杂类型的响应式数据
const user = reactive({
name: 'Alice',
age: 25
});
// 定义一个方法
const increment = () => {
count.value++;
};
// 返回需要暴露给模板使用的数据和方法
return {
count,
user,
increment
};
}
};
在上面的示例中,我们使用
ref
和reactive
函数分别定义了基本类型和复杂类型的响应式数据,然后定义了一个方法increment
,最后将这些数据和方法通过return
语句暴露给模板使用。总的来说,
setup
函数是Vue 3中定义组件逻辑的地方,通过setup
函数可以更灵活地管理组件的状态和行为,并且更好地利用Composition API的特性。
<script setup>
标签
通过<script setup>
标签,你可以在单个文件组件中编写组件的逻辑,包括定义响应式数据、计算属性、方法等。不需要使用return将响应式数据返回。
下面是一个简单的示例,演示了如何在Vue 3中使用<script setup>
标签定义组件逻辑:
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => {
count.value++;
};
</script>
必须掌握组件通信的常用方法
根据使用频率介绍如下组件通信的方法
1.props接收父组件传值
props就相当于函数的形参,在组件内使用形参。父组件通过属性绑定传递给定义props的子组件。在props内部,将子组件需要的数据定义好变量名,变量的具体类型等。
<script setup lang="ts">
const props = defineProps({
model: { type: Object, required: true }
});
通过props.model访问
2.emit子组件向上传递事件
使用emit传递事件,首先要定义事件。在 Vue 3 中,defineEmits
是 defineComponent
函数的一部分,因此你不需要显式地从 vue
中导入它。
如何定义?
需要在子组件中使用 defineEmits
定义要触发的事件:
<script setup lang="ts">
const emit = defineEmits(['someEvent'])
emit('someEvent')
</script>
事件如何处理?
在父组件中通过-的事件名方式展开驼峰事件,在父组件定义方法,处理子组件抛出来的事件。通常emit的事件还会传递数据
<MyComponent @some-event="callback" />
3. 透传特性$arrts
在vue3中,那些没有明确在组件props和emits中声明的特性或事件监听器称为透传特性,以前叫非属性特性。比如class,style和id特性。当组件只有单根时,透传特性自动被添加到根元素上作为其特性。
为什么有props还需要透传属性?
透传属性的存在是为了方便开发者,因为有时候你可能需要在子组件的根元素上应用一些全局属性,比如
class
、style
、id
或者事件监听器(如@click
),而不需要每次都在子组件中声明这些属性。
透传属性的好处是:
- 减少重复代码:不需要在每个子组件中都声明一遍常用的属性。
- 提高灵活性:父组件可以动态地向子组件添加属性,而不需要子组件预先知道这些属性的存在。
- 遵循 HTML 标准:在 HTML 中,属性是可以自由添加到元素上的,透传属性保持了这种行为。
vue3中在script脚本里通useAttrs函数访问透传特性
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
如果你想在template模板中访问透传特性,可以使用 v-bind="$attrs"
或者 ...$attrs
来将所有透传特性应用到模板中的元素上。示例代码如下:
<template>
<div v-bind="$attrs">
<!-- 这里会自动应用所有透传属性 -->
</div>
</template>
4.插槽slot
如果要传递模板内容给子组件,我们使用插槽。插槽就相当于子组件设置了一个占位符,父组件通过传递值替换插槽内部的内容
template模板中通过<slot>标签定义插槽,在父组件中通过子组件标签名中间替换内容。
在script里可以通过useSlots获取父组件传递的插槽信息
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
const defaultContent = slots.default()
</script>
匿名插槽
// comp1
<div>
<slot></slot>
</div>
// parent
<comp>hello</comp>
具名插槽
// comp2
<div> <slot></slot> <slot name="content"></slot>
</div>
// parent
<Comp2>
<!-- 默认插槽⽤default做参数 -->
<template v-slot:default>具名插槽</template>
<!-- 具名插槽⽤插槽名做参数 -->
<template v-slot:content>内容...</template>
</Comp2>
5.提供/注入 provide/inject
隔代传参时,使用provide/inject这组API。
父组件通过provide函数注入数据,该数据表示父组件要传递给子组件的值
<script setup>
import { provide } from 'vue'
// 祖代提供数据
provide(/* key */ 'message', /* value */ 'hello!')
</script>
子组件通过inject接收数据
import { inject } from 'vue'
export default {
setup() {
// 后代注入数据
inject(/* key */ 'message', /* value */ 'hello!')
}
}
vue中的hook思想composables(必须掌握)
vue3提供了Composition API,Composition API 中的 composable 函数和 React 中的 Hook 在概念上是类似的。
在 Vue 3 中,Composition API 提供了一种更灵活和可组合的方式来组织和重用代码逻辑。通过编写可重用的 composable 函数,我们可以将相关的代码逻辑组合在一起,并在组件中使用这些 composable 函数来管理状态、副作用等。这种方式与 React 中的 Hook 的思想类似,都是为了解决代码逻辑复用、状态管理等方面的问题。
在 React 中,Hook 是一种函数,可以让你在函数组件中使用 state 和其他 React 特性。而在 Vue 3 中,composables 也是一种函数,可以封装可重用的逻辑,并提供给组件使用。
约定composables函数命名时加上use前缀
这个约定的目的是为了让开发者在使用这些可重用逻辑时更容易识别这些函数是 composable 函数,并且可以清晰地区分出哪些函数是提供给组件使用的可重用逻辑。通过在函数名称前加上
use
前缀,可以使代码更具可读性和一致性,让其他开发者更容易理解和使用这些 composable 函数。举个例子,如果我们要封装一个处理计时器的 composable 函数,我们可以将其命名为
useTimer
,这样其他开发者在看到这个函数名称时就能够清楚地知道这是一个提供给组件使用的计时器逻辑。
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// by convention, composable function names start with "use"
export function useMouse() {
// state encapsulated and managed by the composable
const x = ref(0)
const y = ref(0)
// a composable can update its managed state over time.
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// a composable can also hook into its owner component's
// lifecycle to setup and teardown side effects.
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// expose managed state as return value
return { x, y }
}
三、必备TSX语法——JS中内嵌HTML标签和属性
JSX (JavaScript XML) 是一种 JavaScript 的语法扩展,它允许开发者在 JavaScript 代码中嵌入 HTML 标签和属性,使得代码更像 XML,更容易表达组件的结构。虽然 JSX 是为 React 开发设计的,但它也可以在 Vue 中使用,尤其是在 Vue 3 通过
@vue/babel-plugin-jsx
插件支持 JSX 之后。
Vue 的模板(template)是一种基于 HTML 的语法,它用于定义 Vue 组件的结构和内容。Vue 通过模板编译器解析这些 HTML-like 的字符串,将其转换为 JavaScript 代码,生成一个可执行的渲染函数。在 Vue 3 的早期版本(不支持 JSX)中,template 是核心的表达方式。
相比之下,Vue 3 的 JSX 语法更接近 React,它允许开发者使用类似 <div>
、<button>
这样的标签,并可以直接在标签上定义 JavaScript 属性,如 v-bind
(在 JSX 中通常写成 :
)和事件处理函数。Vue 3 的 JSX 会经过 TypeScript 类型检查,提供了更好的类型支持。
总结来说,Vue 的 template 和 JSX 都是用于描述组件结构和行为的方式,但 JSX 是 Vue 3 中引入的额外特性,它提供了更直观的 HTML 风格的代码编写方式,同时也将 Vue 的模板系统与 React 的 JSX 集成,使得 Vue 3 在某些场景下更像 React。
1.defineComponent
defineComponent
用于定义一个 Vue 组件。
defineComponent
是 Vue 3 中引入的 Composition API(组合API)的一部分,它提供了一种更现代、更函数式的方式来组织组件。setup
函数是组件的核心,它替代了传统的render
函数,减少了对this
的依赖,并且可以更方便地处理副作用和数据管理。
defineComponent
函数接收一个对象,其中包含 setup
函数
import { defineComponent } from 'vue';
export default defineComponent({
setup(props, ctx) {
// 在这里,ctx 是一个提供了访问组件实例和渲染上下文的特殊对象
// props 是组件接收到的 props 对象
// 返回一个函数,该函数返回 JSX 或任何其他值,它将被渲染
return () => <div>tree</div>
}
})
这个
setup
函数返回的是一个函数,该函数在渲染时被调用,生成最终的 DOM 结构。这样做的好处是代码更清晰,避免了this
的杂乱,使得函数式编程的理念更易于理解和维护。需要注意的是,
setup
函数不能直接修改props
或ctx
,如果需要响应式地处理数据,可以使用ref
、reactive
或setupContext
等 Composition API 的工具。
2. withModifiers修饰符
withModifiers
是 Vue 3 中的一个内置函数,用于给一个事件处理函数添加一或多个修饰符。修饰符是一种特殊的指令,可以用于调整事件的行为。例如,"self"
修饰符可以确保一个事件只在元素自身上触发,而不是在其子元素上触发。当你使用
withModifiers
函数时,你需要传入两个参数:第一个是事件处理函数本身,第二个是一个包含修饰符名称的数组。可以将withModifiers
函数看作是一种将修饰符与事件处理函数关联在一起的工具。通过使用这个函数,你可以轻松地添加修饰符,而不必担心手动处理事件的细节。
import { ref, withModifiers, defineComponent } from 'vue';
const App = defineComponent({
setup() {
// 创建一个响应式的 count 变量,初始值为 0
const count = ref(0);
// 定义一个函数 inc,用于增加 count 的值
const inc = () => {
count.value++;
};
// 返回一个函数,该函数返回 JSX 结构,用于渲染组件
return () => (
<div onClick={withModifiers(inc, ["self"])}>{count.value}</div>
);
},
});
解析:
setup
函数是组件的入口点,它返回一个函数,该函数在渲染时被调用,生成最终的 DOM 结构。在这个例子中,setup
函数创建了一个响应式的count
变量,并定义了一个inc
函数来增加count
的值。
return
语句返回的函数使用 JSX 语法来描述组件的结构。这里创建了一个<div>
元素,它的文本内容是count
的当前值。onClick
属性绑定了一个事件处理函数,这个函数使用了withModifiers
来添加修饰符。
withModifiers(inc, ["self"])
的意思是,当点击<div>
元素时,调用inc
函数,但是只有当点击事件发生在<div>
自身上时才会触发,而不是在它的子元素上。这是通过"self"
修饰符实现的,它告诉 Vue 只响应直接在目标元素上的点击事件。
3. 常用vue指令在JSX的用法
Vue中,通常我们使用模板语法来编写组件的视图部分,其中包括常用的指令如
v-if
、v-for
、v-on
等。然而,在使用JSX语法编写Vue组件时,我们需要使用不同的语法来实现相同的功能。以下是常用的Vue指令在JSX中的用法示例:
注意,以下的vue指令不是在template模板中使用的,而是在script标签中使用的。在js中操作dom。默认是<script setup>标签
v-model
常用指令v-model,v-show跟以前用法类似,但也要注意后面是{},不是""
<input type="text" v-model={this.counter} />
v-bind
指令(动态属性绑定)
<img src={imageUrl} />
v-on
指令(事件绑定)
<button onClick={handleClick}>Click me</button>
v-if条件渲染
用条件语句或三元表达式代替
<div>{ condition ? <span>A</span> : <span>B</span> }</div>
{show && <div>Content</div>}
v-show
指令
<div style={{ display: show ? 'block' : 'none' }}>Content</div>
v-for——使用js的map遍历代替
// 导入Vue的defineComponent和ref函数
import { defineComponent, ref } from "vue";
// 定义一个名为App的Vue组件
const App = defineComponent({
// 在setup函数中定义组件的生命周期钩子
setup() {
// 使用ref创建一个名为list的响应式数组,初始为空字符串数组
const list = ref<string[]>([]);
// 箭头函数,用于渲染列表中的每个元素
return () => {
// 使用map方法遍历list数组,为每个元素创建一个p标签
// key属性用于给每个元素唯一标识,index是当前元素的索引
return list.value.map((data, index) =>
// 生成一个p标签,其内容为data(当前元素的值),key属性为index
<p key={index}>{data}</p>
);
}
}
});
jsx中自定义指令v-**用法
// 导入Vue的defineComponent函数
import { defineComponent } from "vue";
// 定义一个名为MyComponent的Vue组件
const MyComponent = defineComponent({
// 在组件中定义自定义指令
directives: {
// 定义一个名为focus的自定义指令
focus: {
// 当元素被挂载到DOM时触发mounted钩子
mounted(el) {
// 将焦点设置到该元素上
el.focus();
}
// 可以在这里添加其他生命周期钩子,如updated等
}
},
// 组件的setup函数,用于设置组件的响应式数据和方法
setup() {
// 返回一个渲染函数,该函数返回组件的模板
return () => (
// 使用v-focus自定义指令的input元素
// 当这个input元素被渲染到DOM时,它会自动获得焦点
<input v-focus />
);
}
});
// 导出MyComponent组件,以便在其他地方使用
export default MyComponent;
实现插槽——v-slots
JSX中想要实现Vue中的插槽写法也有很大不同,主要利用一个叫做v-slots的指令来实现:
Parent.tsx
// 导入Vue的defineComponent函数
import { defineComponent } from "vue";
// 导入Child组件,假设Child组件已经在其他地方定义过
import Child from "./Child";
// 定义一个名为MyComponent的Vue组件
export default defineComponent({
// 组件的setup函数,用于设置组件的响应式数据和方法
setup() {
// 返回一个渲染函数,该函数返回组件的模板
return () => (
<Child
// 使用v-slots属性定义插槽
v-slots={{
// 定义名为prefix的具名插槽
prefix: () => <i>prefix</i>, // 具名插槽内容为<i>prefix</i>
// 定义名为suffix的具名插槽,接受props参数
suffix: (props) => <span>{props.name}</span>, // 具名插槽内容为<span>{props.name}</span>
}}
>
默认插槽内容 // 默认插槽内容为"默认插槽内容"
</Child>
);
},
});
子组件
// 导入Vue的defineComponent函数
import { defineComponent } from "vue";
// 定义一个名为Child的Vue组件
const Child = defineComponent({
// 组件的setup函数,用于设置组件的响应式数据和方法
setup(props, context) {
// 从context中解构出slots对象,它包含了父组件传递的所有插槽内容
const { slots } = context;
// 返回一个渲染函数,该函数返回组件的模板
return () => (
<>
{/* 渲染默认插槽内容 */}
默认插槽: {slots.default && slots.default()}
<br />
{/* 渲染具名插槽内容,这里假设具名插槽名为'prefix' */}
具名插槽: {slots.prefix && slots.prefix()}
<br />
{/* 渲染作用域插槽内容,这里假设作用域插槽名为'suffix',并传递了props参数 */}
作用域插槽: {slots.suffix && slots.suffix({ name: "suffix" })}
</>
);
},
});
// 导出Child组件,以便其他组件可以导入使用
export default Child;
jsx传递emit
// 导入Vue的defineComponent函数
import { defineComponent } from "vue";
// 定义一个名为CustomButton的组件,声明它会触发一个名为'click'的自定义事件
const CustomButton = defineComponent({
// 声明组件会触发的自定义事件
emits: ["click"],
// 定义组件的setup函数,用于设置组件的响应式数据和方法
setup(props, context) {
// 从context中解构出emit函数,用于触发自定义事件
const { emit } = context;
// 返回一个渲染函数,该函数返回组件的模板
return () => (
// 创建一个按钮元素
<button
// 绑定一个点击事件处理器,当按钮被点击时,调用emit函数触发'click'事件
onClick={() => emit("click")}
>
// 按钮的文本
点我触发emit
</button>
);
},
});
// 导出CustomButton组件,以便其他组件可以导入使用
export default CustomButton;
三、Vue 3 组件库开发流程
在接下来的博客中,将按照以下内容进行组件化开发实战
-
确定需求:明确组件库的功能和设计需求。
-
设计组件:设计组件的 API、功能和样式。
-
编写组件:使用 Vue 3 编写组件的模板、脚本和样式。
-
测试组件:编写测试用例,确保组件功能正常。
-
文档编写:编写组件文档,包括使用说明、API 文档等。
-
打包和发布:使用构建工具将组件打包,并发布到 NPM 或私有仓库。
准备好了吗,开始搭建vue3组件库开发项目啦
Vue3组件库开发项目实战——02项目搭建(配置Eslint/Prettier/Sass/Tailwind CSS/VitePress/Vitest)-CSDN博客