初识vue(中)

工程化开发

什么是前端工程化?

前端工程化开发是一种通过使用各种工具、技术和最佳实践来提高前端开发效率、质量和可维护性的方法。它旨在将前端开发从简单的代码编写转变为一个更系统化、自动化的过程,以便团队能够更高效地协作、管理代码库、处理依赖关系,并确保最终交付的前端应用具有稳定性和性能优势。

前端工程化的核心概念和实践包括:

  1. 模块化开发: 将前端代码拆分为小模块,每个模块专注于特定的功能。这有助于代码的可维护性、复用性和团队协作。
  2. 包管理器: 使用工具如npm或Yarn来管理项目所需的外部依赖库。这可以确保项目依赖的版本一致性,并简化依赖的安装过程。
  3. 构建工具: 使用工具如Webpack、Parcel或Rollup来自动化构建过程,包括合并、压缩、转换代码以及处理资源文件,从而减少手动操作,优化性能并生成生产就绪的代码。
  4. 代码分割和懒加载: 通过将代码拆分成更小的块,并在需要时按需加载,从而减少初始加载时间,提高页面性能。
  5. 自动化测试: 实施单元测试、集成测试和端到端测试,以确保代码在不同层面上的质量和稳定性。
  6. 版本控制: 使用工具如Git来管理代码版本,使团队能够协同开发、解决冲突,并轻松地回退到之前的版本。
  7. 代码风格检查和格式化: 使用工具如ESLint和Prettier来强制执行一致的代码风格和格式,以提高代码的可读性和一致性。
  8. 持续集成和持续交付: 集成自动化构建、测试和部署流程,使团队能够频繁地交付新功能和修复,从而减少发布时的风险。

为什么使用前端工程化开发?(好处)

  1. 提高开发效率: 自动化流程减少了重复性的手动操作,开发人员可以更专注于实际的编码和创意工作。
  2. 代码质量提升: 使用自动化测试和代码检查工具,可以降低bug率,改善代码质量。
  3. 可维护性增强: 模块化和标准化的开发实践使得代码更易于理解、扩展和维护。
  4. 团队协作改进: 通过版本控制、自动化构建和持续集成,团队成员能够更好地协同工作,减少冲突和交付问题。
  5. 性能优化: 使用构建工具和代码优化策略,可以减小代码体积,提高页面加载速度和性能。
  6. 降低风险: 自动化测试和持续交付减少了发布时的人为错误,使发布更加可靠。

基本使用步骤

1.全局安装: npm i @vue/cli -g

全局安装vue脚手架.png

2.查看vue版本 : vue --version (vue -V)

image-20230827114503341.png

3.创建项目框架: vue create vue-demo (项目名,不可以使用中文)

创建完项目之后会出现一个选择vue版本的界面,使用上下键选择对应的vue版本

image-20230827114930819.png

这个时候我们的脚手架就已经创建好了

image-20230827114737142.png

cd 进入创建项目的根目录, 打开

4.启动项目: npm run serve使用yarn的用 yarn serve运行项目

image-20230827115626536.png

打开这个本地地址就可以看见你的一个最初始的vue项目了

image-20230827134037443.png

脚手架目录文件

image-20230827140553976.png

当我们使用npm run serve或是 yarn serve 把项目跑起来的时候就会去找 package.json 里面的命令

image-20230827141523390.png

vue-cli-service serve是脚手架的命令,是对 webpack 的封装

等同于webpack serve,把项目跑起来之后就会去找项目的入口,也就是 main.js

来看一看 mian.js 里面的代码究竟干了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 导入 vue 构造函数(去node_modules里去找到 vue 的包,相当于之前在html文件里引入 vue 的核心包)
import Vue from 'vue'
// 2. 引入 app.vue 的组件
import App from './App.vue'
// 3. 设置生产环境的提示,不提示
Vue.config.productionTip = false
// 4. 创建 vue 的实例对象
new Vue({
render: h => h(App),//根据 app 组件来创建 DOM 元素并挂载到指定的容器中
// render 函数的完整写法:
//render(createElement) {
// return createElement(App)
}
// render 是一个函数 而调用 render 函数的时候传入的 参数createElement又是一个函数,用来创建对象,它在里面调用的时候会基于传过来的 app 模板来创建,最后返回
}).$mount('#app')
// .$mount('#app') 完全等同于之前的 el:'#app',设置挂载点,也就是 vue 管理的范围

// 最终渲染到 #app 盒子里面的内容完全取决于 app 模版里面的内容

组件化开发

组件(Component): 一个页面可以拆分成一个一个的组件,每一个组件都有着自己独立的结构, 样式, 行为

优点:便于维护,利于复用,提高开发效率

组件分类: 普通组件,根组件

image-20230827151110761.png

组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

components.png

组件的组成和基本使用

组成:

template结构(有且只能有一个根元素)

scriptjs逻辑

style样式(支持less,sass,需要装包)

image-20230827174118047.png
基本使用流程:

①创建 vue 组件

vue 组件的命名遵循大驼峰命名法(如:GoodsHeader),或是烤串命名法(如:goods-header)

tips:

当创建完 vue 组件可直接到需要使用的组件内使用,无需导入和注册,方便快捷

②导入到需要使用的组件内

③注册组件

组件注册的两种方式:

局部注册: 只能在注册的组件内使用

全局注册: 所有组件内都能使用

④使用组件

直接当成普通的 html 标签使用

组件命名的时候使用大驼峰命名法,在使用的时候推荐使用烤串命名法

局部组件注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
<div class="box">
<p>我是根组件</p>
<!-- 使用组件 -->
<dl-header></dl-header>
<dl-main></dl-main>
<dl-footer></dl-footer>
</div>
</template>
<script>
//1. 导入组件
import DlHeader from './components/dl-header.vue'
import DlMain from './components/dl-main.vue'
import DlFooter from './components/dl-footer.vue'
// 注册组件
// 组件中的 js 默认导出一个对象,就是组件对象
export default ({
components: {
DlHeader,
DlMain,
DlFooter,
},
})
</script>

<style lang="less">
.box {
background-color: purple;
width: 600px;
height: 500px;
display: flex;
justify-content: space-between;
flex-direction: column;
margin: 0 auto;
p {
color: #fff;
font-size: 20px;
text-align: center;
}
}
</style>

image-20230827162758205.png

全局组件注册

使用: 在 main.js 内导入,并全局注册 Vue.component(组件名, 组件对象)

一般都用局部注册,如果发现确实是通用组件,再定义到全局

1
2
3
4
5
// 在 main.js 中导入需要全局注册的组件
import ProjectButton from './components/search'
// 调用 Vue.component 进行全局注册
// Vue.component('组件名', 组件对象)
Vue.component('search', search)

组件的样式冲突

默认情况:写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。

  1. 全局样式: 默认组件中的样式会作用到全局
  2. 局部样式: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组件

scoped原理

  1. 当前组件内标签都被添加 data-v-hash值 的属性
  2. css 选择器都被添加 [data-v-hash值] 的属性选择器

最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

image-20230827172419839.png

image-20230827172441501.png

组件中的 data

一个组件的 data 选项必须是一个函数

保证每个组件实例,维护独立的一份数据对象

组件通讯

组件通讯就是指组件与组件之间的数据传递

组件关系分类

父子关系

image-20230827175236435.png

非父子关系

image-20230827175247512.png

父子通讯流程图

  1. 父组件通过 props 将数据传递给子组件
  2. 子组件利用 $emit 通知父组件修改更新

image-20230827175544075.png

父向子传值

父向子传值.png

子向父传值

image-20230827221950163.png

prop

prop 是子组件用来接受父组件传递过来的数据的一个自定义属性。

父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 “prop“,说白了就是接收到父组件传递过来的数据

props是单向数据流,只能从父组件传递到子组件,子组件不能直接修改props中的值,这也是为什么不能在子组件中直接修改数据需要通过触发事件的方式与父组件进行沟通,让父组件去修改数据

prop校验

语法: ①类型校验 ②非空校验 ③默认值 ④自定义校验

Vue中,prop是一种用于父组件向子组件传递数据的机制。通过在子组件中定义props属性,可以声明需要从父组件接收的数据,并在子组件中使用这些数据

特点:

  • 可以传递任意数量的 prop
  • 可以传递任意类型的 prop

当接收到的数据不符合要求的时候就需要进行类型校验,并给予开发者提示

基础写法的类型校验

1
2
3
props: {
校验的属性名: 类型 // Number String Boolean ...
}

完整写法的类型校验

1
2
3
4
5
6
7
8
9
10
11
props: {
  校验的属性名: {
    type: 类型,  // Number String Boolean ...
    required: true, // 是否必填
    default: 默认值, // 默认值
    validator (value) {
      // 自定义校验逻辑,通过返回true,不通过返回false并报错
      return 是否通过校验
    }
  }
}

为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组

深入v-model 原理

v-model 本质是一个语法糖,在内部实现了两个功能:
1. 使用 v-bind 绑定了 value 属性,获取 value 值
2. 使用 v-on 监听 input 事件 , 一旦 input 框的内容改变就把最新的值赋给 msg2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="app">
<input type="text" v-model="msg1" />
<input type="text" :value="msg2" @input="handleInput"/>
<input type="text" :value="msg2" @input="msg2=$event.target.value"/>
//在事件处理函数中第一个参数默认就是事件对象, 在模版中可以使用 $event 表示事件处理函数的第一个参数, $ event 就是 vue 在模版中定义的一个变量,写法更加的简单
//v-model 本质是一个语法糖,在内部实现了两个功能:
1. 使用 v-bind 绑定了 value 属性,获取 value 值
2. 使用 v-on 监听 input 事件 , 一旦 input 框的内容改变就把最新的值赋给 msg2
<br />
</div>
</template>

<script>
export default {
data() {
return {
msg1: '',
msg2: '',
}
},
methods:{
handleInput(e) {
this.msg2 = e.target.value
}
}
}
</script>

.sync修饰符

作用:用于实现父组件和子组件之间的数据的双向绑定,简化代码,在本质上和 v-model一样是一个语法糖

在原来不使用.sync语法的时候,在子组件内是无法直接修改父组件传递过来的数据的,只能向

事件的名字可以自己随意的定义,不必在必须规定为 value 类型
本质: : 属性名update:属性名的合写形式

当我们在属性名,后面使用了.sync 修饰符 它在后面做了两件事:

1 .把数据传递给子组件

2 .同时监听了 update:××× (后面的×××就是传给子组件的属性名)的事件

要实现父子组件之间的双向数据绑定就直接在属性名的后面使用 .sync 修饰符号就可以了

封装弹框类组件案例

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="app">
<button @click="openDialog">退出按钮</button>
<BaseDialog :isShow.sync="isShow"></BaseDialog> 优雅
<BaseDialog :isShow="isShow" @changeShow="(isShow=false)"></BaseDialog> 传统
</div>
</template>
<script>
import BaseDialog from './components/BaseDialog.vue'
export default {
data() {
return {
isShow: false,
}
},
methods: {
openDialog() {
this.isShow = true
}
},
components: {
BaseDialog,
},
}
</script>

<style>
</style>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<!-- 使用接收到的数据给盒子设置默认的隐藏状态为 false -->
<div v-if="isShow" class="base-dialog-wrap">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button @click="$emit('update:changeShow',false)" class="close">x</button> 传统
<button @click="$emit('changeShow',false)" class="close">x</button> 优雅
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button>确认</button>
<button @click="$emit('update:changeShow',false)">取消</button> 传统
<button @click="$emit('changeShow',false)">取消</button> 优雅
</div>
</div>
</div>
</template>

<script>
export default {
// 使用 props 接收父组件传递过来的数据
props: {
isShow: {
type: Boolean,
required: true,
},
},
};
</script>

ref和$refs

ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 \$refs 对象进行注册。

如果在普通的DOM元素上使用,引用信息就是DOM元素;

1
2
3
4
5
<template>
<div>
<input type="text" ref="textInput" />
</div>
</template>

使用this.$refs.textInput来访问这个元素,并操作它

1
this.$refs.textInput.focus(); // 将焦点设置到文本输入框

如果用在子组件上,引用信息就是组件实例

1
2
3
4
5
<template>
<div>
<my-component ref="myComponentRef"></my-component>
</div>
</template>

想要获取到元素使用this.$refs.name来获取元素如果是普通的dom元素则获取到的是dom元素

如果是子组件则获取到的是组件实例,可以使用组件的所有方法

vue的异步更新,$nextTick

$nextTick 是一个用于在 DOM 更新队列被清空之后执行回调的方法。

Vue的更新是异步的,当你修改数据后,Vue并不会立即进行DOM更新,而是会将这些更新操作放入一个队列中,然后在下一个事件循环周期中进行更新,这样可以有效地减少DOM操作的次数,提高性能。

有时候,你可能需要在DOM更新完成后执行一些操作,比如在某个元素渲染完成后获取其几何信息,或者在更新后执行一些动画效果。这时就可以使用 $nextTick 方法来确保你的回调在DOM更新之后被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<p ref="message">{{ message }}</p>
<button @click="updateMessage">更新信息</button>
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello, Vue!'
};
},
methods: {
updateMessage() {
this.message = '我已经不是原来的我了,我变心了';
this.$nextTick(() => {
// 在DOM更新后执行这里的回调
console.log(this.$refs.message.textContent); // 输出:我已经不是原来的我了,我变心了
});
}
}
};
</script>

自定义指令

自定义指令:大白话就是自己定义的指令,封装dom操作,扩展额外功能,使用时就像使用 v-for 这些指令一样直接在标签上就可以使用

封装一个自动获取焦点的 自定义指令 v-focus

和组件注册的方式一样有全局注册局部注册

全局注册

main.js 注册

1
2
3
4
5
6
7
8
9
// Vue.directive(指令名,配置对象)

// inserted() 是指令的钩子函数,被指令绑定了的元素会在被插入到 DOM 树上时自动执行
//执行时会将绑定该指令的元素传过来,然后就可以对其进行操作了
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})

局部注册

1
2
3
4
5
6
7
8
9
10
   directives: {
// 指令名: 配置对象
focus: {
// inserted 函数: 当使用指令的元素被插入到 DOM 中时触发, 自动执行
inserted(el) {
// console.log('我被插入到 DOM 啦!', el)
el.focus() // DOM 操作 封装到此处了!
}
}
}

注册完成了之后就可以在想要使用该组件的地方使用 v-focus 即可

自定义指令的值

依据传入不同的值显示不同的颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>

<script>
export default {
data () {
return {
color1: 'gray',
color2: 'orange'
}
},
}
</script>

<style>

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.directive('color', {
// 参数1: el 指令绑定的元素对象
// 参数2: binding 指令绑定的参数, 对象
inserted(el, binding) {
el.style.color = binding.value
},
// update 钩子: 指令的值发生变化时自动触发
// 参数1: el 指令绑定的元素对象
// 参数2: binding 指令绑定的参数, 对象
update(el, binding) {
el.style.color = binding.value
}
})

插槽

插槽:让组件内部的结构支持自定义,说白了就是我在这里挖个坑,你可以往这里面填东西,至于要填什么东西你自己来决定

默认插槽

插槽的基本使用语法:

1.在组件内在需要定制结构的部分使用标签占位置

2.在使用组件时,在标签内部,传入需要定制的结构替换 slot

1
2
3
4
5
6
7
<template>
<div>
<MyDialog>
<img src="https://s1.aigei.com/src/img/gif/a2/a23e1935964d4465bcfe1f17625fc8a6.gif?imageMogr2/auto-orient/thumbnail/!240x225r/gravity/Center/crop/240x225/quality/85/&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:jwsq63qB0OOfuMOr-KKnCye7ln4=" alt="">
</MyDialog>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>

<div class="dialog-content">
<slot></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>

默认值

通过插槽完成了内容的定制,传入什么就显示什么.如果不传,则会是空白

设置默认值也非常的简单,只需要在slot标签内写入你想要设置的默认值即可,如果没有就会显示默认值的内容,如果传了内容,默认值就会失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>

<div class="dialog-content">
<slot><h1>这里是默认值</h1></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>

具名插槽

具名插槽顾名思义,就是有名字的插槽

一个组件内有多处结构需要从外部传入标签进行定制

使用步骤:

  1. 给每个 slot 标签设置 name 属性

  2. 使用插槽时通过 template 标签结合 v-slot 指令来设置需要分发的插槽

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <template>
    <div>
    <MyDialog>
    <!-- 如果不指定插槽名字, 默认名字叫 default -->
    <!-- <p>嘿嘿嘿</p> -->
    <template #header>
    <h3>温馨提示!</h3>
    </template>

    <template #content>
    <p>恭喜您中了 500 万美金的大奖,请到缅北 妙瓦底 <a href="https://www.alipay.com">kk园区</a> 领取!</p>

    </template>

    <!-- v-slot: 可以简写成 # -->
    <template #footer>
    <button><a href="www.aliyun.com">点击前往</a></button>
    </template>
    </MyDialog>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <template>
    <div class="dialog">
    <div class="dialog-header">
    <slot name="header"></slot>
    <span class="close">✖️</span>
    </div>

    <div class="dialog-content">
    <slot name="content"></slot>
    </div>
    <div class="dialog-footer">
    <slot name="footer"></slot>
    </div>
    </div>
    </template>

作用域插槽

作用域插槽其实就时可以带数据的插槽,即可以携带参数的插槽

简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。

基本使用步骤:

  1. 在子组件的 slot 标签上使用自定义属性,携带数据传递给父组件
  2. 父组件使用 template 配合 v-slot 获取传递过来的数据
1
2
3
4
5
6
7
<td>
<!--
注意: :item 相当于往插槽上加了一个自定义属性, 最终以对象包裹属性的形式传递过去
例如: 此处传过去的数据格式 { item: item, index: index }
-->
<slot :index="index" :item="item"></slot>
</td>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<MyTable :data="list">
<!--
定义形参接收
插槽默认名字叫 default
可以简写为 #default="变量名",这个变量名保存的是由子组件传递过来的数据,
//一般通常情况下直接在这里对传递过来的对象进行解构操作,方便后面的使用
如果不用 # 也可以不指定名字, 直接写为 v-slot="变量名"
-->
完整写法: v-slot:default = '变量名'
简写形式: #default = '变量名'
不指定插槽名,接收数据:v-solt = '变量名' // 对象解构 v-solt = { 属性名1,属性名2 , ...}
<template v-slot="scope">
<button @click="del(scope.item.id)">删除</button>
</template>
</MyTable>