单页面应用程序

在一个web服务器上,只有一个web页面。

所有的功能都在这个页面上实现。

单页面应用是很复杂的。

vue-cli

用于开发vue的标准工具。简化了基于web-pack创建工程化vue项目的过程。

安装和使用

1
npm i -g @vue/cli

快速生成工程化的Vue项目

1
vue create 项目名称

image-20230807154634338

使用上下箭头选择预设

此处选择Manually select features,回车。

image-20230807154839856

使用空格勾选需要的功能。

  • 勾选CSS Pre-processors,可以方便使用less书写css
  • 建议取消勾选Linter / Formatter,这个是用于约束代码风格的。

image-20230807155515503

选择Vue版本,选择2.x

image-20230807155745924

选择css预处理器,选择Less

image-20230807155819776

选择配置文件的存放方式(独立的或放在一起)。

选择默认的In dedicated config files,独立创建。

image-20230807160026798

是否保存当前这套预设。输入y,回车。

为当前预设设置名称,回车。

vue项目的运行流程

通过main.jsApp.vue渲染到index.html页面中。

第一个Vue.js项目

进入使用vue-cli创建的vue项目。

vue项目的构成

1
2
3
4
5
6
7
8
9
10
demo-first
├── README.md
├── babel.config.js
├── jsconfig.json
├── node_modules
├── package-lock.json
├── package.json
├── public
├── src
└── vue.config.js

src目录

1
2
3
4
5
6
7
src
├── App.vue
├── assets #用于存放静态资源
│ └── logo.png
├── components #用于存放组件文件
│ └── HelloWorld.vue
└── main.js #项目的入口文件

Vue入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
//导入vue包,用于创建vue对象。
import Vue from 'vue'
//导入组件,用于调用和渲染。
import App from './App.vue'

Vue.config.productionTip = false
//创建Vue对象
new Vue({
//将render函数指定的组件渲染到HTML页面。
render: h => h(App),
}).$mount('#app')

//注:$mount()等价于el

组件中的内容,会替换掉HTML中,Vue所绑定的作用域标签。

换句话说,“<div id=“app”></div>”相当于是一个占位符。

Vue组件化开发

组件文件的后缀名为.vue

vue组件的三个组成部分:

  • templete组件的模板结构
  • script组件的javascript行为
  • 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
//默认导出。
export default {
name: 'App',
components: {
HelloWorld
},
//data节点,在组件中定义方式改变了,必须是函数形式,并返回 {}。
data(){
return {
//在此处定义数据
},
methods:{
printThis(){
console.log(this); //组件中的this,不再指向vue实例,而是指向组件实例。
}
},
watch:{
...
},
computed:{
...
},
filters:{
...
}
}
}
</script>
<!--> lang属性用于启用css预处理器<-->
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

组件之间的关系

image-20230808140859094

父子关系

组件在使用时,会产生父子关系。谁调用谁,谁是谁的父亲。

A调用B和C,所以A是B、C的父亲。

兄弟关系

后代关系

组件的使用方式

  1. 使用import 组件名 from 组件位置导入组件。
  2. components节点注册组件。
  3. 以标签的形式使用导入的组件。
1
2
3
<templete>
<组件名></组件名>
</templete>

全局组件与私有组件

components节点中注册的主键为私有组件。


main.js入口文件中,通过Vue.component()方法将组件注册为全局组件。

1
2
import 全局组件 from 路径
Vue.component('组件名称',全局组件)

组件的自定义属性

用于声明自定义属性props。

允许使用者通过自定义属性,为当前组件指定初始值。

创建props组件

1
2
3
4
5
6
7
8
9
10
<template>
<div>props: init={{ init }}</div>
</template>

<script>
export default {
props:['init']
};
</script>

使用数组的形式定义props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="left">
我是左组件
<Props init="15"></Props>
</div>
</template>

<style>
.left {
border: 1px solid;
float: left;
padding: 10px;
}
</style>

!!! info Props赋数值的快捷方法
使用v-bind::来绑定自定义属性。

1
<Props :init="15"></Props>

​ 这样操作,15就是数值型数据了,不再是字符型。

使用对象形式定义props

设置默认值,使用default节点

检查值类型,使用type节点

设置Props为必填,使用required节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>props: init={{ init }}</div>
</template>

<script>
export default {
props:{
init:{ //使用对象形式定义props。
default:0 ,//设置默认值。
type:Number ,// [Number|String|Object],检查数据类型。
required:true //bool,设置该属性是否为必填。
}
}
};
</script>

!!! warning 自定义属性是只读的
自定义属性值不能被直接修改,Vue会报错,建议将属性转储到data节点

Props的意义

提高组件的复用性。

组件的自定义事件

在组件中,使用$emit()方法触发自定义事件。

这个事件能够被父组件监听。

1
this.$emit("事件名",参数1,参数2,...);

父组件监听子组件

1
<p v-on:事件名="处理函数"></p>

事件命名规则建议

推荐使用烤串命名法,不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以没有必要使用驼峰命名法首字母大写命名法。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

组件样式冲突

理想情况下,组件中的样式应该只作用在本组件中。

实际上,组件中的样式会影响所有的组件。

解决方案

实现原理:在所有本组件使用的标签中,添加自定义属性,然后在样式中结合属性选择器选择器[自定义属性]进行样式设计。

Vue的方案:在style标签上添加一个scoped自定义属性

1
2
3
<style lang='less' scoped>
...
</style>

scoped有一个缺点,无法在父组件中修改子组件。

这时就需要使用/deep/

常常会用在使用第三方组件库,需要修改样式的时候。

实现原理:将原本的属性选择器与后代选择器结合。选择器[自定义属性]变为 [自定义属性] 选择器

1
2
3
4
5
<style lang='less' scoped>
/deep/选择器{
...
}
</style>

Vue组件的实例对象

在组件中使用组件,可以理解成创建实例的过程。

1
2
3
<template>
<组件></组件> //创建了一个组件实例对象
</template>

组件的生命周期和生命周期函数

组件的生命周期是指一个组件从创建、运行、销毁的整个过程,强调的是一个时间段。

生命周期函数是指在某个生命周期的时间点执行的函数,强调的是一个时间点。

组件创建前

beforeCreate()

当前的props、data、methods节点都未被创建。

访问不了数据,也调用不了方法。

组件创建后<!重要>

created()

props、data、methods节点已初始化完毕。

但是,组件的模板结构还未生成,不能操作DOM树。

发起请求

一般情况,在created()阶段发起网络请求获取数据。

组件渲染到页面之前

beforeMount()

该阶段将内存中编译好的HTML结构渲染到浏览器,但在这个阶段DOM树还未被创建,因此不能操作DOM结构。

组件渲染到页面之后

mounted()

该阶段已经将内存中的HTML结构,渲染到了浏览器中。此时浏览器已经创建好了组件的DOM结构。

组件数据更新前

beforeUpdate()

该阶段在数据发生改变,但还未开始重新渲染。

在这个阶段,可以获取到渲染之前的旧数据。

组件数据更新后

updated()

该阶段,已经根据数据重新渲染了页面,数据和结构都是最新的。

操作DOM的操作应该在这个阶段进行。

组件销毁前

beforeDestory

v-if命令可以控制组件的创建和销毁。

组件销毁后

destoryed

组件间的数据共享

结合组件之间的关系树形图。

父组件向子组件

使用自定义属性Props

子组件向父组件

使用自定义事件$emit()

兄弟组件相互传递

使用EventBus方案

  1. 创建eventBus.js 文件
1
2
import Vue from 'vue'
export default new Vue()
  1. 在兄弟组件中导入eventBus.js文件
1
2
3
4
5
import bus from './eventBus.js'
//发送数据方
bus.$emit('事件',参数)
//接收数据方
bus.$on('事件',事件处理函数)

ref引用

jQurey简化了程序员操作DOM的过程,但不建议在vue中使用。

在vue中,程序员几乎不再需要操作DOM,只需要维护数据。

在少数情况下,ref用于方便程序员在vue中操作DOM树以及子组件


vue组件实例的$refs属性

ref引用的使用

1
2
<p ref="DOM引用名1"></p>
<组件 ref="DOM引用名2"></组件>
1
2
this.$refs.DOM引用名1.DOM操作
//this.$refs.DOM引用名1.style.color=yellow;

ref值的唯一性

ref的值不能重复。

vue组件实例的$nextTick(cb)属性

当页面完全渲染完毕后,在执行回调。

以下代码,会报undefined错误。

原因是:flag值发生变化后,立即执行了操作DOM的代码,但此时页面还未完全渲染完毕。

1
<input v-if="flag" type="text" ref="inputRef"/>
1
2
this.flag=true
this.$refs.inputRef.focus()

解决方法:使用$nextTick(cb)属性,在页面完全渲染完毕后执行回调函数。

1
2
3
4
this.flag=true
this.$nextTick(()=>{
this.$refs.inputRef.focus()
})