我正在参加「掘金·启航计划」
一、组件化开发思想
1.1 组件概念
组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。 组件 (Component) 作为 Vue.js 最强大的功能之一,可以扩展 HTML 元素,封装可重用的代码。 生活中的组简化思想体现:
组装一台电脑需要很多部件,如上面列出的部分主要部件....这些部件多是工厂按照一定的标准进行生产。
电脑厂家,分别从生产这些配件的工厂定制合格的配件产品,对这些标准配件进行集合组装,最终组装出我们所用的笔记本或者台式电脑,当然我们自己也可以进行配件的购买和电脑的组装....
编程中的组件化思想体现:
如上:整个页面由多个组件组件,每个组件部分可能包含:CSS单元、JS模块等等信息。
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构
1.2 组简化规范:Web Components
作为开发者,我们都知道尽可能多的重用代码是一个好主意。这对于自定义标记结构来说通常不是那么容易 — 想想复杂的HTML(以及相关的样式和脚本),有时您不得不写代码来呈现自定义UI控件,并且如果您不小心的话,多次使用它们会使您的页面变得一团糟。
Web Components 旨在解决这些问题, 它是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。
Web Components 由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
- Custom elements(自定义元素): 一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates(HTML模板): template和slot元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
二、组件实操应用
2.1 实现的效果图展示
每一个 .vue 文件都是一个组件 (SFC,单文件组件),每一个组件都可以复用。 通常一个应用程序会以一棵嵌套组件树的形式来组织呈现,下面就是我们要实现的效果图:
简化后的线型图如下:
2.2 预处理器
本案例中采用的预处理器为 Sass,当然可以根据自己需要选择 Less 或者 Stylus 等。
Sass(Syntactically Awesome StyleSheets) 是一款强化 CSS 的辅助工具,它在 CSS 语法的基础上增加了变量 (variables)、嵌套 (nested rules)、混合 (mixins)、导入 (inline imports) 等高级功能,这些拓展令 CSS 更加强大与优雅。使用 Sass 以及 Sass 的样式库(如 Compass)有助于更好地组织管理样式文件,以及更高效地开发项目。
安装 Sass
npm install --save-dev sass
Vite 中内置 Sass,直接安装即可,不需要安装 sass-loader
2.3 scoped
scoped 属性作为 模块的特殊属性,用于实现组件样式的私有化(当前组件内生效),可以避免组件内样式对全局的污染(每一个组件都可以自由的去定义样式)。定义了 scope 属性的样式,vue 通过处理在HTML结构和CSS样式上添加了唯一标记,如下:
一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。
三、全局注册
使用 Vue 应用实例的 app.component() 方法注册的组件叫做全局组件,可以在当前应用的任意一个组件模板中不需要引入即可使用。
main.js 中注册全局组件
import { createApp } from 'vue'
import App from './App.vue'
import DataModel from "./views/DataModel.vue"
const app = createApp(App)
app.component('DataModel',DataModel)
app.mount('#app')
app.component() 方法可以被链式调用
app.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
main.js中批量注册全局组件
参考 Element Plus 的 Icon 系列组件的注册,使用 for...of 迭代注册组件
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
3.1 局部注册
全局注册虽然很方便,但有以下几个问题:
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
- 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。
Tree shaking 是一个通常用于描述移除 JavaScript 上下文中的未引用代码 (dead-code) 行为的术语。
在使用 <script setup> 的单文件组件中,导入的组件可以直接在模板中使用,无需注册:
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
<ComponentA />
</template>
注:局部组件仅在当前被引入的组件内可用,而在任何的子组件或更深层的子组件中都不可用。
3.2 组件名格式
Vue 中推荐使用 PascalCase(帕斯卡命名法) 作为组件名的注册格式,这是因为:
- PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
<PascalCase />在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素 (web components) 区分开来。
在单文件组件和内联字符串模板中,我们都推荐这样做。但是,PascalCase 的标签名在 DOM 模板中是不可用的。
为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent 为名注册的组件,在模板中可以通过 <MyComponent> 或 <my-component> 引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。
PascalCase:帕斯卡命名法,每个单词首字母大写,又名大驼峰命名法。如:HelloWorld
camelCase:驼峰命名法,第一个单词首字母小写,后面的每个单词首字母大写,又名小驼峰命名法。如:helloWorld
kebab-case:短横线隔开命名法,每个单词首字母小写。如:hello-world、
四、动态组件
通过内置组件 的 is 属性,动态指定需要显示的组件。
动态组件实现 Tab 切换的效果:
演示代码:
<script setup>
import Tab1 from '../components/tabs/Tab1.vue';
import Tab2 from '../components/tabs/Tab2.vue';
import Tab3 from '../components/tabs/Tab3.vue';
import { ref, shallowRef, markRaw, reactive } from "vue"
// let cutComp = ref(Tab1)
let cutComp = shallowRef(Tab1)
let cutIdx = ref(0)
let data = reactive([{
id: 1,
title: "国际新闻",
tabName: markRaw(Tab1)
}, {
id: 2,
title: "国内新闻",
tabName: markRaw(Tab2)
}, {
id: 3,
title: "热点新闻",
tabName: markRaw(Tab3)
}])
const toggleTab = (idx) => {
cutIdx.value = idx
cutComp.value = data[idx].tabName
}
</script>
<template>
<div class="tab-wrap">
<!-- Vue中提供了component元组件用于实现组件的动态切换,基于特殊的属性 is;可以用于切换自定义组件,也可以用于切换原生DOM,当然内置组件也是可以的 -->
<!-- <component :is="Tab1"></component>
<component is="input"></component> -->
<!-- 选项卡切换 -->
<!-- <ul>
<li>国际新闻</li>
<li>国内新闻</li>
<li>热点新闻</li>
</ul> -->
<ul>
<li v-for="item, index in data" :class="index == cutIdx ? 'active' : ''" @click="toggleTab(index)">{{
item.title
}}</li>
</ul>
<div class="content">
<component :is="cutComp"></component>
</div>
</div>
</template>
<style lang="scss" scoped>
.tab-wrap {
width: 300px;
ul {
list-style: none;
display: flex;
padding: 0;
justify-content: space-between;
border: 1px solid #e3e3e3e3;
}
li {
flex: 1;
padding: 6px 0;
text-align: center;
cursor: pointer;
&.active {
color: blue;
font-weight: bold;
}
}
}
.content {
height: 160px;
padding: 10px;
border: 1px solid #e7e7e7;
}
</style>
二、递归组件
递归组件类似于递归函数,就是在当前组件内调用组件本身。一般情况下,不需要 import 引入直接使用即可。
4.1 递归组件演示
<script>
export default {
name: "MyRecursion"
}
</script>
<script setup>
const props = defineProps({
obj: {
type: Array,
default: () => []
}
})
const showItem = (title) => {
console.log(title)
}
</script>
<template>
<div>
<ul v-for="item in obj">
<li @click="showItem(item.title)"><strong>{{ item.title }}</strong></li>
<!-- <Tab1 v-if="item?.children?.length" :obj="item?.children" /> -->
<MyRecursion v-if="item?.children?.length" :obj="item?.children" />
</ul>
</div>
</template>
4.2 定义组件别名
-
采用选项式API语法多写一个 script 去通过 name 注册一个组件别名,当前组件内调用这个组件别名。
<script> export default { name:"OtherComponentName" } </script> <script setup> /* 当前组件式 API 相关代码 */ </script> <template> /* 模板代码 */ </template> <style lang='scss' scoped> /* 样式代码 */ </style> -
unplugin-vue-define-options插件定义组件别名Element Plus 源码使用这个插件来对组件名进行注册,所以我们完全可以放心的使用。
-
安装插件
npm install unplugin-vue-define-options -D -
vite.config.js文件添加插件import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import DefineOptions from 'unplugin-vue-define-options/vite'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue(), DefineOptions()], }); -
配置完成后,就可以在组件中直接使用了
<template> <button> </button> </template> <script setup> defineOptions({ name: 'TButton', }); </script> <style scoped></style>
-