Vue3.0(二):Vue组件化基础 - 脚手架

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>

注册局部组件

全局组件往往是在程序一开始的时候,就会全局组件注册完成,但是有些组件我们并为使用过,在打包的时候,会增加打包文件的体积

因此局部组件是开发中常用的

  • 通过 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生命周期中取消相应的事件总线
相关推荐
zhougl99635 分钟前
html处理Base文件流
linux·前端·html
花花鱼39 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_42 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷6 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript