Vue组件化基础 - 脚手架
Vue的组件化
-
我们在处理一些任务量比较庞大的工作时候,会将工作内容进行拆分,分步骤完成
-
而组件化的思想正式如此,对于一个庞大的项目,我们可以将其拆分成一个个的小功能,分步骤进行实现
-
组件化是Vue的核心思想
- 在前面的学习中,我们 在createApp中传入了一个App对象,这个对象的本质其实就是一个组件
html<div id="app"> <div v-for="item in arr" :key="item">{{item}}</div> </div> <script> //使用Vue const App = { data() { return { arr: [1, 2, 3], }; }, } const app = Vue.createApp(App); //挂载 app.mount("#app"); </script>
- 任何应用都会被抽象成一颗组件树
注册组件的方式
分为全局组件注册:任何地方都可以用
局部组件注册:
注册全局组件
- 全局组件需要使用 app进行注册
- 通过 component方法传入组件名称,组件对象即可注册一个全局组件
html
<div id="app">
<my-component></my-component>
</div>
<script>
const App = {};
//全局组件对象
const myComponent = {
template: `
<h2>我是一个组件</h2>
`,
};
//使用Vue
const app = Vue.createApp(App);
//注册全局组件
//传入全局组件的名称以及对象
app.component("my-component", myComponent);
//挂载
app.mount("#app");
</script>
- 组件的名称
- 使用短横线分隔符:my-component 进行命名,使用的时候
<my-component></my-component>
- 使用大驼峰命名法:MyComponent 进行命名,使用的时候
<my-component></my-component>
或者<MyComponent></MyComponent>
- 使用短横线分隔符:my-component 进行命名,使用的时候
注册局部组件
全局组件往往是在程序一开始的时候,就会全局组件注册完成,但是有些组件我们并为使用过,在打包的时候,会增加打包文件的体积
因此局部组件是开发中常用的
- 通过 components属性进行注册
- 要明确要在哪里使用该组件
html
<div id="app">
<my-component></my-component>
<my-item></my-item>
</div>
<script>
const App = {
components: {
//key就是组件的名称,后面跟着组件对象
"my-component": {
template: `
<h2>我是一个组件</h2>
`,
},
//第二个局部组件
MyItem: {
template: `
<h2>我是一个组件Item</h2>
`,
},
},
};
//使用Vue
const app = Vue.createApp(App);
//挂载
app.mount("#app");
</script>
- 局部组件与全局组件的区别就是
- 全局组件可以在任意组件内使用
- 而局部组件只能在定义的组件的内部使用:比如 MyItem 组件不能再 my-component组件中使用
Vue的开发模式
- 在真正开发Vue项目的时候,我们是通过创建一个 Vue文件进行开发的
- 而浏览器是不认识 Vue文件的,此时我们可以借助打包工具 webpack/vite等,进行打包
- 将 Vue文件转换成JS对象,这样浏览器就可以识别
单文件的特点
上面说的Vue文件实际上就是一个单文件
- 以下就是一个简单的 Vue文件
Vue CLI脚手架
为了让浏览器能够正常渲染Vue文件,可以使用Vue CLI搭建相关环境
- 在真实的开发中,我们通常会使用 脚手架来创建一个项目 ,Vue项目我们使用的就是Vue的脚手架
- Vue的脚手架就是 Vue CLI
- 我们可以通过 CLI选择项目的配置和创建出我们的项目
- Vue脚手架已经 内置了webpack的相关配置,不需要从零开始配置
安装和脚手架
- 通过
npm install @vue/cli -g
全局安装 Vue脚手架
- 接下来我们就可以使用 Vue脚手架创建项目了
- 使用
vue create 项目名称
创建一个项目- 注意 项目名称中不能含有大写字母
- 接下来选择项目的配置项
- 我们选择自定义配置项
- 根据个人需求选择下面的选项即可
- 选择创建Vue项目的版本
- 此处我们选择 Vue3版本
- 针对于 Babel等工具配置项的选项
- 我们选择将配置项单独生成一个文件
- 以下还有一些内容的配置
- 而后等待项目自动生成即可
- 进入到项目目录中,使用
npm run serve
命令即可运行项目
Vue项目的目录分析
- 整体的项目目录如下
- node_modules:项目的依赖包
- public:里面的两个文件是项目的图标,以及html模板(用于打包时候自动生成的html)
- src:用于编写源代码
- .browserslistrc:进行浏览器适配的,比如写了ES6+以上的代码,根据适配的浏览器,决定是否转化,会在caniuse中去查找相应的内容
- .gitignore:git的忽略文件
- babel.config.js:babel工具的配置项
- jsconfig.json:给vscode使用,可以拥有更好的友好提示
- package-lock.js package.json:安装依赖的版本,执行脚本等
- vue.config.js:可以在这里对webpcak的打包工具进行设置等操作
src文件夹中的main入口文件,以及App.vue文件
- main.js 是项目的入口文件
- 这和之前写的代码时一样的
- 之前我们通过
Vue.createApp(App)
创建一个Vue实例,而这是通过import
引入Vue - 该文件中,还引入了 App.vue文件,并进行挂载
- 在此处我们可以注册全局组件
js
import { createApp } from "vue";
import App from "./App.vue";
import HelloWorld from "./components/HelloWorld";
const app = createApp(App);
//注册全局组件
//直接在任意vue文件中使用即可
app.component("hello-world", HelloWorld);
//挂载
app.mount("#app");
- App.vue 文件就是根组件
- 可以在里面注册局部组件
vue
<template>
<h2>{{ title }}</h2>
<HelloWorld></HelloWorld>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
//注册局部组件
components: {
//这是对象的增强写法,等同于HelloWorld:HelloWorld
HelloWorld,
},
data() {
return {
title: "zhangcheng",
};
},
};
</script>
<style></style>
- 因此对于今后的开发就变成了以下的情况
其他创建vue项目的方法
使用npm init vue@latest
-
运行
npm init vue@latest
命令创建项目- 首先会安装一个本地工具 : create-vue
- 使用 create-vue创建一个vue项目
-
且是使用 vite进行打包的,不是使用webpack进行打包
-
输入完命令,会询问是否安装 vue-create工具
- 输入 y
- 而后根据需要进行如下配置
- 最后根据提示,安装相应的依赖包,即可运行项目
组件化 - 组件间通信
认识组件的嵌套
- 在根组件中,我们可以引入各种小组件,完成组件嵌套
vue
<template>
<div>
<!-- 头部 -->
<header-com></header-com>
<!-- 内容 -->
<content-com></content-com>
<!-- 底部 -->
<footer-com></footer-com>
</div>
</template>
<script>
import HeaderCom from "./components/HeaderCom.vue";
import ContentCom from "./components/ContentCom.vue";
import FooterCom from "./components/FooterCom.vue";
export default {
//注册局部组件
components: {
HeaderCom,
ContentCom,
FooterCom,
},
data() {
return {
title: "zhangcheng",
};
},
};
</script>
<style></style>
父子组件之间的通信
- 父组件传递给子组件 : 通过props属性
- 组组件传递给父组件:通过$emit触发事件
组件间通信-父传子
在以上案例中,相当于是App.vue中的数据,要传递给HeaderCom.vue/ContentCom.vue/FooterCom.vue
-
在子组件中通过 props接收父组件传递过来的参数
-
什么是Props
- Props是可以在组件上注册一些自定义的attribute
- 父组件给 这些attribute赋值 ,子组件通过attribute的名称获取对应的值
-
Props有两种常见的用法
- 方式一:字符串数组
- 弊端:不能对传入的数据进行类型验证
- 没有默认值
- 方式二:对象类型
- 方式一:字符串数组
type的类型都是哪些
- 常见的类型
js
String
Number
Boolean
Array
Object
Date
Function
Symbol
对象类型的其他写法
- 接收的是一个对象/数组
js
props: {
//若是对象类型,则需要用函数返回
obj:{
type:Object,
//因为对象类型,多个组件共享该对象的话,其中一个组件进行了更改,所有的都会更改
default:()=>{
return {name:"zhangcheng"}
}
},
arr:{
type:Array,
default(){
return [1,2,3]
}
}
},
- 声明多个类型
js
props: {
//可以接收多个类型
age: [Number,String],
},
组件间通信-子传父
在以上案例中,相当于是HeaderCom.vue/ContentCom.vue/FooterCom.vue中的数据,要传递给App.vue
- 用到子传父的时候
- 当 子组件中有一些事件发生的时候,比如子组件中发生了点击,父组件需要切换内容
- 子组件中有一些内容想要传递给父组件
- 用法
- 首先在子组件中 通过 $emit发射一个事件
- 在父组件引用子组件的地方 使用发射的事件
- 在父组件中定义一个事件 用于接收子组件传入的数据
- 在 Vue3中,增加了emits属性,父组件在使用子组件发射参数的时候能够得到友好的提示,同时可以对其进行验证(很少用)
非Prop的attribute
- 当子组件中设置了 prop进行接收父组件传进来的数据,则该attribute就是prop的attribute
- 但是 当父组件传入了一个attribute,是子组件中没有声明的,那就是非prop的attribute
- class没有在子组件的prop中声明,此时class就是 非prop的attribute
html
<childCom class="active"></childCom>
- 而 非prop的attribute会自动给子组件的根节点
vue
//父组件引用了子组件
<childCom class="active"></childCom>
-----------------------
//子组件
<template>
<div>
其他元素
</div>
</template>
//相当于在子组件中的div中添加了class="active"
- 如果我们不想,默认添加给根节点,同时还想将其给子组件中的其他元素
- 首先通过 inheritAttrs属性禁止非prop的attribute添加到根节点中
- 之后通过 $attrs.属性名进行获取
vue
<template>
<div>
<div :class="$attrs.class">{{ name }}</div>
<div v-bind="$attrs">{{ age }}</div>
</div>
</template>
<script>
export default {
//阻止非prop的attribute添加到根节点中
inheritAttrs: false,
props: ["name", "age"],
data() {
return {};
},
methods: {},
};
</script>
插槽 - Slot
子组件中的数据可以根据传入的数据进行变化
而子组件中的元素是否可以?
- 比如,现在有一个子组件,我们想让其根据不同的要求,显示 文本/按钮/图片等内容
- 此时我们就需要用到 slot插槽这个功能
基本使用
- 用法
- 在子组件中,写入插槽
- 父组件引用子组件的内部写入想要显示的元素即可
默认内容
当我们没有给子组件传入内容,但是又想让子组件默认显示一些内容,可以在slot中写入默认的元素
多个插槽-具名插槽
- 如果子组件中,有多个插槽,应当使用 具名插槽
- 在 子组件的插槽中 使用 name属性,给插槽起名字
- 父组件中传入元素的时候 使用 v-slot:插槽名字
- 不带name属性的slot,实际上有一个默认不显示的名字,为 default
vue
-----子组件
<template>
<div>
<div class="left">
<slot name="left"></slot>
</div>
<div class="right">
<slot name="right"></slot>
</div>
</div>
</template>
-------父组件
<template>
<childCom>
<!--需要使用template进行包裹-->
<template v-slot="left">
<button>
按钮
</button>
</template>
<template v-slot="right">
<span>
文本
</span>
</template>
</childCom>
</template>
- 具名插槽的简写 可以用 #代替v-slot
vue
-------父组件
<template>
<childCom>
<!--需要使用template进行包裹-->
<template #"left">
<button>
按钮
</button>
</template>
<template #"right">
<span>
文本
</span>
</template>
</childCom>
</template>
动态插槽名
当我们在父组件中使用 v-slot的时候,可以指定元素插入到哪个插槽中
但是我们不希望冒号后面的内容写死,即 v-slot:"right",right是动态的
vue
<AddComponts @add="countAdd">
<!--将冒号后面,写上 [],里面写入变量即可-->
<template v-slot:[posion]>
<button>123</button>
</template>
</AddComponts>
作用域插槽
- 首先看一下需求
- 父组件给子组件传递了data数据
- 子组件中有一个插槽
- 父组件引用了子组件的插槽,且填入元素
- 想让填入的元素中的数据,使用父组件传给子组件的数据
- 此时我们就可以使用作用域插槽
非父子组件通信
- 在实际开发中,我们有时候会遇到,非父子组件之间的传值
- 比如 App.vue向SearchCom.vue传值,或者MainCom.vue向FootCom.vue传值
- 通常我们在非父子组件通信的时候,使用的是 Provide/Inject(不常用)和事件总线
Provide和Inject
仅能用作有关联的组件,没有关联的组件不能使用
- Provide和Inject用于非父子组件之间共享数据
- 父组件有一个 provide选项来提供数据
- 子组件有一个 Inject选项来使用这些数据
- 同时父组件不知道要把数据传给谁,而子组件不知道数据的来源是哪里
- 通常提供数据的一方 provide写成函数的形式
- 若提供的数据为响应式的话,需要用到 computed函数
事件总线
以上方法的弊端就是,无法实现兄弟组件之间的通信,因此选用事件总线
- 在Vue2版本中,Vue是有事件总线的功能 <math xmlns="http://www.w3.org/1998/Math/MathML"> o n , on, </math>on,emit等
- 但是在Vue3版本中,取消了事件总线的功能,因此官方推荐使用了三方库 mitt
- 同时我们自己也可以自己封装一个事件总线的工具函数
- 在JS高级的文章中,有过封装
- 首先封装一个自己的事件总线工具
js
class zcEventBus {
constructor() {
this.obj = {};
}
//on事件需要接收两个参数,事件名称,回调函数
on(eventName, callBackFn) {
//采用对象的结构,一个事件名称,后面跟着数组,可以包含多个事件
//{eventName:[fn1,fn2]}
//但是第一次执行的时候,this.obj[eventName]不是一个数组,需要进行判断
let eventArr = this.obj[eventName];
//第一次肯定是undefined,当!eventArr的时候,为true
if (!eventArr) {
//将this.obj[eventName]初始化数组
eventArr = [];
this.obj[eventName] = eventArr;
}
//相当于在this.obj[eventName]中push了事件
eventArr.push(callBackFn);
}
//emit事件需要接收两个参数,事件名称,以及参数
emit(eventName, ...arg) {
console.log(eventName);
//需要遍历eventName对用的事件数组,并依次执行
//首先判断是否存在,不存在直接返回
let eventArr = this.obj[eventName];
if (!eventArr) return;
//若存在,则直接遍历执行
for (const fn of eventArr) {
fn(...arg);
}
}
}
export default new zcEventBus();
- 在组件中引用
js
import EventBus from "./components/EventBus.vue";
- 在提供数据的一方使用 emit方法
js
emitData() {
//传入事件的名称,以及参数
zcEventBus.emit("zcEventBus", "zhangcheng");
},
- 在接收数据的一方使用 on方法
js
created() {
zcEventBus.on("zcEventBus", (data) => {
console.log("父组件收到了数据", data);
});
},
- 规范的写法,应当在 unmounted生命周期中取消相应的事件总线