Vue2

使用Vue

引用Vue.js

1 放到body后,DOM加载完后引用

2 先引用vue.js 再引用自己的js

1
2
3
4
</body>
<script src="js/jquery-3.5.1.js"></script>
<script src="js/vue.js"></script>
<script src="js/lookUp.js"></script>

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="vue_test">
<h1>site : {{site}}</h1>
<h1>url : {{url}}</h1>
<h1>{{details()}}</h1>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue_test',
data: {
site: "Winter",
url: "www.winterlee.top.com",
},
methods: {
details: function() {
return this.site + " Winter";
}
}
})
</script>

1.Vue构造器中参数el与DOM中元素的id保持一致

2.data用于定义属性,存放数据和变量

3.methods用于定义函数

Vue指令

v-html和v-text

v-html和v-text用来辅助渲染DOM元素的文本内容。

v-text(类似innerText)v-html(类似 innerHTML)

使用该标签属性后,会覆盖标签内部内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<div id="app">
{{msg}}
<div v-html='html'></div>
<div v-text='name'></div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
msg: '!!!!!',
name: 'winter',
html: `<a href="http://www.winterlee.top">winter的博客</a>`
}
})
</script>

v-for

v-for 可以绑定数据到数组来渲染一个列表:

1
2
3
4
<label for="province" >省份:</label>
<select id="province" name="province" onChange="switchSubjects()" >
<option v-for="(province,index) in provinces" :value="province.value">{{province.name}}</option>
</select>

样例中provinces是Vue实例中的数据 province是其元素迭代别名

v-for中的**:key后面跟唯一标识,确保列表项正确排序复用**

为什么加key:Vue 的默认行为会尝试原地修改元素(就地复用

v-if

v-if用来显示或者隐藏元素,并控制元素的创建和移除:

1
2
3
4
5
6
<div class="subject-row" id="type-0" v-if="type===0">
<button type="button" class="subject-button" name="subject" value="文史"
@click="onActivateWenke" id="wenke" :class="{'active':selectedWenke}">文科</button>
<button type="button" class="subject-button" name="subject" value="理工"
@click="onActivateLike" id="like" :class="{'active':selectedLike}">理科</button>
</div>

v-if后面跟着JS语句 其值决定是否隐藏该元素

还有v-else-if v-else 需要跟在v-if或者v-else-if后面

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
<body>

<div id="app">
<p v-if="gender === 1">性别:♂ 男</p>
<p v-else="">性别:♀ 女</p>
<hr>
<p v-if="score > 90">成绩评定A:奖励电脑一台</p>
<p v-else-if="score > 80">成绩评定B:奖励周末郊游</p>
<p v-else-if="score > 70">成绩评定C:奖励零食礼包</p>
<p v-else="">成绩评定D:惩罚一周不能玩手机</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>

const app = new Vue({
el: '#app',
data: {
gender: 1,
score: 100,
}
})
</script>

</body>

v-show和v-if的区别?

v-if用来显示或者隐藏元素,并控制元素的创建和移除

v-show切换css的display:none来控制显示隐藏

v-show适合频繁显示隐藏的,v-if则反之

v-on:click 和 v-on:change

  • <button v-on:事件名=”内联语句”>按钮
  • <button v-on:事件名=”处理函数”>按钮
  • <button v-on:事件名=”处理函数(实参)”>按钮
  • v-on: 简写为 @
1
<button type="button" @click="message">一键智能查询</button>

缩写是 @click和@change

v-bind 和 v-model

v-bind 绑定Vue数据到html元素

作用:动态设置html的标签属性 比如:src、url、title

1
2
3
4
<select id="province"  name="province" onChange="switchSubjects()" >
<option v-for="province in provinces" :value="province.value">{{province.name}}</option>
<!-- 其他省份 -->
</select>

这里的:value 实际是 v-bind:value的简写

v-model实现数据和html元素的双向绑定

作用:表单元素(input、radio、select)使用,双向绑定数据,可以快速 获取设置 表单元素内容

1
2
<label for="score">分数:</label>
<input type="number" id="score" name="score" placeholder="请输入您的分数" v-model="score">

改一个 变两个

指令修饰符

所谓指令修饰符就是通过.指明一些指令后缀

不同后缀封装不同的处理操作 用于简化代码

按键修饰符

例如

@keyup.enter —>当松开enter键的时候才触发

v-model修饰符

  • v-model.trim —>去除首位空格
  • v-model.number —>转数字

事件修饰符

  • @事件名.stop —> 阻止冒泡(例如点击子元素,阻止父元素也被点击)
  • @事件名.prevent —>阻止默认行为
  • @事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为

v-bind对样式的控制

v-bind控制class

语法

1
<div> :class = "对象/数组">这是一个div</div>

对象语法

当class动态绑定的是对象时,键就是类名,值就是布尔值,如果值是true,就有这个类,否则没有这个类

1
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>

适用场景:一个类名,来回切换

数组语法

当class动态绑定的是数组时 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表

1
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>

使用场景:需要对类批量添加或者删除时

v-bind控制style

语法

1
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>

注意 属性键名不能用横杠 需使用引号或者驼峰

v-model应用表单元素

1
2
3
4
5
6
输入框  input:text   ——> value
文本域 textarea ——> value
复选框 input:checkbox ——> checked
单选框 input:radio ——> checked
下拉菜单 select ——> value
...

计算属性computed

介绍

概念

基于现有的数据,计算出来的新属性依赖的数据变化,自动重新计算。

语法

  1. 声明在 computed 配置项中,一个计算属性对应一个函数
  2. 使用起来和普通属性一样使用

注意

  1. computed配置项和data配置项是同级
  2. computed中的计算属性虽然是函数的写法,但他依然是个属性
  3. computed中的计算属性不能和data中的属性同名
  4. 使用computed中的计算属性和使用data中的属性是一样的用法
  5. computed中计算属性内部的this依然指向的是Vue实例

computed的简易使用

1
2
3
4
5
6
7
8
9
10
11
12
const app = new Vue({
el: '#app',
computed:{
/*计算属性名(){
...
} */
totalCount() {
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
1
<p>总数:{{totalCount}} 个</p>

计算属性完整写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 
computed:{
对象名:{
get(){ 计算属性被调用时 get方法被调用

},
set(newValue){ 计算属性改变时set方法被调用,且计算属性的新值作为newValue传到set里
...
}
}
}
*/

computed: {
fullName:{
get(){
return this.firstName + this.lastName;
},
set(newValue){
this.firstName = (newValue.slice(0, 1))
this.lastName = (newValue.slice(1))
}
}
}

即 获取+设置

一个计算属性被获取时被调用,一个计算属性被设置时被调用

computed的优势-缓存特性

计算属性会对计算出来的结果,之后被调用会直接读取缓存

依赖项变化后,会重新计算再缓存

多次调用只计算一次

watch监视器

监视数据变化,执行一些业务逻辑

简单写法

1
2
3
4
5
6
7
8
data:{
words:'',
},
watch:{
words(newValue,oldValue){

},
}

完整写法(对多个数据监视或者有其他要求时)

  1. deep:true 对复杂类型进行深度监听 (对象中的属性,只要一个变了就被监听到)
  2. immediate:true 初始化 立刻执行一次(一进页面,立刻执行一次)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data: {
  obj: {
    words: '苹果',
    lang: 'italy'
  },
},

watch: {// watch 完整写法
  对象: {
deep: true, // 深度监视
immdiate:true,//一进页面,立即执行handler函数
    handler (newValue) {
      console.log(newValue)
    }
  }

防抖 延迟执行,一定时间没有执行后再请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
watch: {
obj:{
deep: true,
immediate: true,
handler (newValue, oldValue) {
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url:'https://applet-base-api-t.itheima.net/api/translate',
params:{
words: this.words,
lang: this.lang
}
})
this.result =res.data.data;
console.log(res.data.data);
},300)
}
}
}

Vue生命周期

生命周期:一个Vue实例从创建到销毁的整个过程

四个阶段创建,挂载,更新,销毁

1.创建阶段:创建响应式数据

2.挂载阶段:渲染模板

3.更新阶段:修改数据,更新视图

4.销毁阶段:销毁Vue实例

初始化渲染请求的时间 创建阶段结束后

操作dom的时间 挂载阶段结束后

Vue生命周期函数(钩子函数)

Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码

created 发送初始化渲染请求

mounted 操作dom

beforeUpdateupdated只有 修改数据才会被调用

beforeUpdate里 数据已经修改 但视图还没更新

updated里 数据已经修改 视图也已经更新

beforedestroy 释放Vue实例以外 的资源

Vue工程化开发版本对应

Vue2—VueRouter3.x—Vuex3.x

Vue3—VueRouter4.x—Vuex4.x

Vue工程化开发和脚手架

开发Vue的两种方式

  • 核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。

  • 工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue。

脚手架Vue CLI

基本概念

Vue CLI 是Vue官方提供的一个全局命令工具

可以帮助我们快速创建一个开发Vue项目的标准化基础架子。【集成了webpack配置】

好处:

  1. 零配置
  2. 内置babel等工具
  3. 标准化的webpack配置

使用步骤:

  1. 全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
  2. 查看vue/cli版本: vue –version
  3. 创建项目架子:vue create project-name(项目名不能使用中文)
  4. 启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)

VueCLi自定义创建项目

1.安装脚手架 (已安装)

1
npm i @vue/cli -g

2.创建项目

1
vue create hm-exp-mobile
  • 选项
1
2
3
4
5
Vue CLI v5.0.8
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features 选自定义

  • 选择Vue版本
  • 是否使用history模式 (项目目录不带#)
  • 选择css预处理
  • 选择eslint风格(代码规范校验工具)
  • 选择校验的时机
  • 配置文件的生成方式 独立的还是统一在package.json里
  • 是否保存预设

Vue项目目录介绍和运行流程

项目目录介绍

重点

  1. main.js 入口文件
  2. App.vue App根组件 (vue实例都在这写)
  3. index.html 模板文件

运行流程

main.js代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//导入Vue
import Vue from 'vue'
//导入App.vue
import App from './App.vue'

//当前出于生产环境还是开发环境
Vue.config.productionTip = false

new Vue({
// el:'#app' 和 $mount('#app)一样
// render: h => h(App) 和下方一样 h和create都是形参
render: (createElement) => {
return createElement(App)
}
}).$mount('#app')

组件化开发

组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。

好处:便于维护,利于复用 → 提升开发效率。

组件分类:普通组件、根组件。

​ 比如:下面这个页面,可以把所有的代码都写在一个页面中,但是这样显得代码比较混乱,难易维护。可以按模块进行组件划分

根组件 App.vue

根组件包裹所有组件

  • 三部分构成

    • template:结构 (有且只能一个根元素)
    • script: js逻辑
    • style: 样式 (可支持less,需要装包)
  • 让组件支持less

    (1) style标签,lang=”less” 开启less功能

    (2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D

示例代码

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
<!-- 结构 vue2只支持一个根节点-->
<template>
<div class="App" >
<!-- 头 -->
<WinterHeader></WinterHeader>
<WinterHeader></WinterHeader>
<!-- 组 -->

<!-- 底 -->
</div>
</template>

<script>
import WinterHeader from './components/WinterHeader.vue';
// 导出的是当前组件的配置项
// 里面可以提供 data(特殊) methods computed watch 生命周期八大钩子
export default {
components:{
WinterHeader: WinterHeader
}
}
</script>

<style lang="less">
/* 支持less 需要1 加上lang="less" 2 安装依赖包 less less-loader*/
.App {
width: 600px;
height: 700px;
background-color: skyblue;
margin: 0 auto;
}
</style>

普通组件的注册使用-局部注册

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

  1. 创建.vue文件

  2. 在使用的组件.vue内先导入再注册,最后使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 导入
    ...
    <script>
    import 组件对象 from '.vue文件路径'
    export default {
    components:{
    组件名: 组件对象,
    }
    }
    </script>
  3. 使用方式 当成html标签即可 <组件名></组件名>

  4. 组件名规范 —> 大驼峰命名法 如WinterHeader

普通组件的注册使用-全局注册

全局注册(在项目的任何组件中都能使用)

  1. 创建vue组件

  2. main.js中进行全局注册

    1
    2
    import 组件对象 from '.vue文件路径'
    vue.component('组件名', 组件对象)
  3. 使用方式 当成html标签即可 <组件名></组件名>

vscode vue不高亮 有的文件夹没有图标

微软账号改 发送邮箱

scoped解决样式冲突

scoped 让组件的样式是独立的,只作用于当前组件

scoped使用

给style添加scoped属性

1
2
3
<template> ... </template>
<script> ... </script>
<style scoped> ... </style>

scoped原理

当前组件内标签都被添加data-v-hash值 的属性

组件的数据 data必须写成函数

写成函数的目的是 让每一个组件实例的数据是独立的

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
<template> ... </template>
<script>
export default {
data () {
return {
数据1: '',
数据2: '',
}
}
}
</script>
<style > ... </style>

组件通信

组件通信:组件通信,就是指组件与组件之间的数据传递

组件关系: 父子关系,非父子关系

不同关系的组件通信

父子通信流程与示例

流程

  1. 父组件 在标签里:数据名="数据"传递子组件
  2. 子组件 props['数据名']接收数据,并通过$emit('方法', '数据新值')通知父组件修改
  3. 父组件@方法="父组件方法"

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父组件
<template>
<div class="app">
//1. 标签里`:数据名="数据"`传递给子组件
<Son :data="true" @changeData="handleChange"></Son>
//4. 父组件`@方法="父组件方法"`
</div>
</template>
<script>
methods: {
handleChange (newValue) {
this.data = newValue
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//子组件
<template>
<div class="son">
我是Son组件 {{data}}
</div>
<button @click="handleChange">改数据</button>
</template>
<script>
export default {
//2. 子组件 `props['数据名']`接收数据
props:['data'],
methods:{
handleChange () {
//3. 并通过`$emit('方法', '数据新值')`通知父组件修改
this.$emit('changeData', false)
},
}
}
</script>

非父子组件通信-EventBus 事件总线

步骤

  1. 创建一个所有组件都能访问的事件总线 (例如放在utils里 EventBus.js)

    1
    2
    3
    import Vue from 'vue'
    const Bus = new Vue()
    export default Bus
  2. B组件 (发送方),触发bus的¥emit事件

    1
    2
    3
    4
    5
    6
    <script>
    import Bus from '../utils/EventBus'
    export default {
    Bus.$emit('sendMsg','消息')
    }
    </script>
  3. A组件 (接收方),监听Bus的 ¥on事件

    1
    2
    3
    4
    5
    6
    7
    8
    <script>
    import Bus from '../utils/EventBus'
    export default {
    Bus.$on('sendMsg', (msg) => {
    this.msg = msg
    })
    }
    </script>

跨层级的非父子组件通信-provide & inject

特点:跨层级共享数据

简单类型 (非响应式)

复杂类型 (响应式)

步骤

  1. 父组件提供数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    export default {
    provide () {
    return {
    // 普通类型【非响应式】
    color: this.color,
    // 复杂类型【响应式】
    userInfo: {id: '1', name: 'winter'},
    }
    }
    }
    </script>
  2. 子/孙组件 inject获取数据

    1
    2
    3
    4
    5
    <script>
    export default {
    inject:['color', 'userInfo']
    }
    </script>

props校验

props概念:组件上注册的自定义属性,如<son :data1="" :data2=""><son>

使用方式

props类型校验

  • 类型校验
  • 非空校验
  • 默认值
  • 自定义校验

类型校验示例

1
2
3
4
5
6
7
8
9
//写在子组件script里
<script>
export default {
props:{
data1: String,
data2: Boolean
}
}
</script>

props完整校验写法

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
export default {
props:{
data1:{
type: 类型,  // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
data2: {
...
}
}
}
</script>

props & data、单向数据流

propsdata的区别

  • data 的数据是子组件自己的 → 随便改
  • prop 的数据是父组件外部的 → 不能直接改,要遵循 单向数据流

单向数据流

父级的数据更新,会向下流动到子级的props里,影响这个子组件。这个数据流动是单向的

v-model详解

v-model原理

v-model本质是语法糖。例如输入框中,就是value属性和input属性的合写

不同表单元素v-model的语法糖不一样

1
2
3
4
5
6
7
<template>
  <div id="app" >
    <input v-model="msg" type="text">
<!-- 等同 -->
    <input :value="msg" @input="msg = $event.target.value" type="text">
  </div>
</template>

v-model实现数据双向绑定,简化代码

前提 props通过value接收,事件触发input

示例

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<select :value="value" @change="handleChange">...</select>
</template>
<script>
export default {
props: {
value: String
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
}
</script>

父组件

1
<BaseSelect v-model="selectId"></BaseSelect>

Vue3 v-model原理

vue3中的v-model语法糖统一成了

:modelValue@update:modelValue

示例

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup>
defineProps({
modelValue: {
type: [Number, String]
}
})
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<el-select
:modelValue="modelValue"
@update:modelValue="emit('update:modelValue', $event)"
>
<el-option
v-for="channel in channelList"
:key="channel.id"
:label="channel.cate_name"
:value="channel.id"
></el-option>
</el-select>
</template>

父组件

1
<channel-select v-model="params.cate_id"></channel-select>

.sync修饰符

v-model相比也是语法糖,也用于父子组件数据的双向绑定,但是可以自定义props属性名

使用场景:封装表单元素用v-model,其他元素可以用.sync修饰符

本质:.sync修饰符 就是 :属性名@update:属性名 合写

示例:

1
2
3
4
//父组件
<template>
<select :visible:sync="isShow">...</select>
</template>

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
export default {
props: {
visible: Boolean
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
}
</script>

ref和$refs

作用:

  1. 专门获取组件内部的dom元素
  2. 获得组件内部的方法

获取dom示例:

1
2
3
4
5
6
7
8
9
10
11
//某组件
<template>
<div ref="chartRef">我是渲染图表的容器</div>
</template>
<script>
export default {
mounted () {
console.log(this.$refs.chartRef)
}
}
</script>

父组件获得子组件内部方法示例

1
2
3
4
5
6
7
8
9
10
11
//父组件
<template>
<son ref="chartRef">我是渲染图表的容器</son>
</template>
<script>
export default {
mounted () {
console.log(this.$refs.chartRef)
}
}
</script>

获得组件内部方法示例

1
2
3
4
5
6
7
8
9
10
11
//父组件
<template>
<son ref="chartRef">我是渲染图表的容器</son>
</template>
<script>
export default {
mounted () {
this.$refs.chartRef.method1()
}
}
</script>

Vue异步dom更新、$nextTick

Vue更新dom是异步的,所以用$nextTick

$nextTick 等DOM更新后,才会执行方法里的函数体

示例

1
2
3
4
this.$nextTick(() => {
  this.$refs.inp.focus()
//inp为refs的属性值
})

注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

自定义指令

概念:自己定义的指令,可以封装一些DOM操作,扩展额外的功能

注册

  • 全局注册 在main.js里

    1
    2
    3
    4
    5
    Vue.directive('指令名', {
    "inserted" (el) {
    el.focus()
    }
    })
  • 局部注册 当前组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    export default {
    directives: {
    "指令名": {
    inserted(el) {
    el.focus()
    }
    }
    }
    }
    </script>

inserted:被绑定元素插入父节点时调用的钩子函数

el:使用指令的那个DOM元素

使用

标签里添加属性v-指令名

自指令的值

1.在绑定指令时,可以通过v-指令名=值的形式为指令 绑定 具体的参数值

1
<div v-color="color">我是内容</div>

2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
export default {
directives: {
color: {
inserted (el, binding) {
el.style.color = binding.value
},
update (el, binding) {
el.style.color = binding.value
}
}
}
</script>

插槽

让我们在App.vue里使用组件内部结构支持自定义

默认插槽用法

  1. 组件内部需要定制的部分,使用<slot></slot>占位

  2. 使用组件时,<组件名></组件名>标签内部传入的结构会替换slot

示例代码

1
2
3
4
5
6
// 组件内部
<template>
<div class="dialog">
<slot></slot>
</div>
</template>
1
2
3
4
5
6
7
8
// App.vue
<template>
<div>
<MyDialog>
这部分内容会替换slot
</MyDialog>
</div>
</template>

插槽后备内容(默认值)

封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)

当外部使用组件时,不传内容,则会显示slot槽内的后备内容

具名插槽用法

适用情形:当一个组件内有多处结构,需要外部传入标签进行定制时

  • 用name属性区分名字

    1
    2
    3
    4
    5
    6
    7
    8
    <template>
    <div>
    <slot name="name1"></slot>
    </div>
    <div>
    <slot name="name2"></slot>
    </div>
    </template>
  • template配合v-slot:名字#名字分发标签

    1
    2
    3
    4
    5
    6
    7
    8
    <MyDialog>
    <template v-shot:name1>
    内容1
    </template>
    <template v-shot:name2>
    内容2
    </template>
    </MyDialog>

作用域插槽(给插槽绑定数据)

使用步骤

  1. 给slot标签以添加属性的方式传值

    1
    <slot :id="item.id" msg="测试文本"></slot>
  2. 所有添加的属性都会被收集到一个对象里

    1
    { id: 3, msg: '测试文本' }
  3. 在template中,通过#插槽名=obj接收,默认插槽的话用#default

    1
    2
    3
    4
    5
    <MyTable :list="list">
      <template #default="obj">
        <button @click="del(obj.id)">删除</button>
      </template>
    </MyTable>

路由入门

单页应用程序 SPA:是指所有的功能都在一个html页面上实现

vue经常用来开发单页应用

Vue路由访问路径组件的对应关系

路由使用步骤

  1. 下载VueRouter模块到当前工程,vue2使用3.x版本 vue3使用4.x版本

    1
    npm i vue-router@3.6.5
  2. main.js中引入VueRouter

    1
    import VueRouter from 'vue-router'
  3. main.js安装注册路由

    1
    Vue.use(VueRouter)
  4. main.js创建路由对象

    1
    const router = new VueRouter
  5. 将路由对象注入到Vue实例中

    1
    2
    3
    4
    new Vue({
      render: h => h(App),
      router:router
    }).$mount('#app')
  6. main.js创建需要的组件(views目录下),在步骤4里的路由对象配置路由规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import Find from './views/Find.vue'
    import My from './views/My.vue'
    import Friend from './views/Friend.vue'

    //path 组件对应的路径 component 用到的组件
    const router = new VueRouter({
    routes: [
    { path: '/find', component: Find},
    { path: '/my', component: My},
    { path: '/friend', component: Friend}
    ]
    })
  7. 在App.vue里配置导航路由出口(组件显示的位置)

    1
    2
    3
    4
    5
    6
    7
    8
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/friend">朋友</a>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>

路由进阶

封装路由

步骤

  1. 将原先main.js里 导入VueRouter 安装注册路由 创建路由对象 这几步放到单独的js文件里
  2. 再将单独的js文件导出到 main.js

路径简写:vue可以直接用@指代src目录

不需要webpack中设置解析路径别名alias (创建vue项目时已自动设置)

声明式导航 router-link组件 导航链接

替代a标签 设置to属性 无需# to="/路径值"

router-link组件也是对a标签进行封装,样式设置在a标签上就可以了

1
<router-link to='page1'>导航</router-link>

默认提供高亮类名

声明式导航 高亮类名

  • router-link-active

    模糊匹配(用的比较多) 可以匹配到以路径值开头的

  • router-link-exact-active

    精准匹配 只能匹配到路径值

声明式导航 自定义类名

在创建路由对象时 添加属性 linkActiveClass,linkExactActiveClass

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{ path: '/find', component: Find},
{ path: '/my', component: My},
{ path: '/friend', component: Friend}
],
linkActiveClass: 'active',
linkExactActiveClass: 'exact-active'
})

声明式导航 跳转传参

查询参数传参

  1. 配置router-link的属性to="/路径名?参数名=值"

  2. 使用 $route.query.参数名获得参数

动态路由传参

  1. 创建路由对象时,配置路由的path为/path/:参数

  2. 跳转时 to属性设置为 /path/参数名

  3. 接收时 $route.params.参数名获得参数

可选符 在创建路由对象配置path时 可以设置/path/参数?表明该参数可传可不传

多个参数用查询参数传参,单个参数用动态路由传参

vue路由 重定向

示例

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home'}
]
})

vue路由 404

创建路由对象时 配置在路由规则的最后面

1
2
3
4
5
6
7
8
import NotFound from '@/views/NotFound'

const router = new VueRouter({
  routes: [
    ...
    { path: '*', component: NotFound } //最后一个
  ]
})

vue路由 模式设置

路由的路径看起来不自然, 有#,更改路径形式的方法

创建路由对象时

1
2
3
4
const router = new VueRouter({
mode:'histroy', //默认是hash
routes:[]
})

vue路由 导航跳转

path路径跳转

push语法 跳转保留原先的地址 可以返回

replace 跳转不保留原先的地址 不可返回

1
2
3
4
5
6
7
//简单写法
this.$router.push('路由路径')

//完整写法
this.$router.push({
  path: '路由路径'
})

path路径传参 query传参

1
2
3
4
5
6
7
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
this.$router.push({
  name: '路由名字',
  query: {
    参数名: '参数值',
  }
})

path路径传参 动态路由params传参

1
2
3
4
this.$router.push('/路径/参数值')
this.$router.push({
  path: '/路径/参数值'
})

name命名跳转

特点:适合 path 路径长的场景

语法:

  • 路由规则,必须配置name配置项

    1
    { name: '路由名', path: '/path/xxx', component: XXX },
  • 通过name来进行跳转

    1
    2
    3
    this.$router.push({
      name: '路由名'
    })

name命名传参 query传参

1
2
3
4
5
6
7
this.$router.push({
  name: '路由名字',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

name命名传参 动态路由params传参

1
2
3
4
5
6
this.$router.push({
  name: '路由名字',
  params: {
    参数名: '参数值',
  }
})

跳转到上一页

$router.back()

vue嵌套路由

在一级路由下,配置children属性即可

1
2
3
4
5
6
7
8
9
10
11
const router = new Vue({
routes: [
{ path: '/first',
component: First,
children: [
{ path: '/second', component: Second },
{ path: '/duo', component: Duo }
]
}
],
})

路由拦截-全局前置守卫

路由导航守卫 - 全局前置守卫

1.所有的路由一旦被匹配到,都会先经过全局前置守卫

2.只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容

用途:一些页面不登陆不让看,点击那些页面时自动跳转到登录页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// vuerouter3.x的写法
router.beforeEach((to, from, next) => {
// 1. to 往哪里去, 到哪去的路由信息对象
// 2. from 从哪里来, 从哪来的路由信息对象
// 3. next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面
})
// vuerouter4.x写法
// 登录访问拦截
router.beforeEach((to,from) => {
//不再有next参数
//默认放行
//如果返回了具体路径 就会拦截到对应页面
//如果返回false 就会返回from
const userStore = useUserStore()
if (!userStore.token && to.path !== '/login') return '/login'
})

路由懒加载

打包时,js包会很大。

路由懒加载 & 异步组件, 不会一上来就将所有的组件都加载,而是访问到对应的路由了,才加载解析这个路由对应的所有组件

使用方式

1
2
3
4
//路由文件
import Login from '@/views/login'
//修改为
const Login = () => import ('@/views/login')

VueRouter4 路由代码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createRouter, createWebHistory } from 'vue-router'

// createRouter 创建路由实例,===> new VueRouter()
// 1. history模式: createWebHistory() http://xxx/user
// 2. hash模式: createWebHashHistory() http://xxx/#/user

// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 ’/‘
// https://vitejs.dev/guide/build.html#public-base-path

// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts 添加配置 base: my-path,路由这就会加上 my-path 前缀了

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: []
})

export default router

Vue3 组合式API中获取路由

1
2
3
4
5
6
7
<script setup>
import {useRouter, useRoute} from 'vue-router'
//router用来获取路由对象
const router = useRouter()
//route用来获得当前页面的路由参数
const route = useRoute()
</script>

组件缓存 keep-alive

keep-alive的三个属性

  • include 组件名数组, 只有匹配的组件会被缓存
  • exclude 组件名数组, 匹配的组件不会被缓存
  • max 最多可以缓存多少组件实例

示例:

App.vue

1
2
3
4
5
6
7
<template>
<div class="h5-wrapper">
<keep-alive :include="['LayoutPage']">
<router-view></router-view>
</keep-alive>
</div>
</template>

Layout.vue

1
2
3
4
5
<script>
export default {
name: 'LayoutPage',
}
</script>

额外的两个生命周期函数

keep-alive的使用会触发两个生命周期函数

activated 当组件被激活(使用)的时候触发 → 进入这个页面的时候触发

deactivated 当组件不被使用的时候触发 → 离开这个页面的时候触发

组件缓存后不会执行组件的created, mounted, destroyed 等钩子了

所以其提供了actived 和deactived钩子,帮我们实现业务需求。

ESlint代码规范

JavaScript Standard Style 规范说明

多种eslint规范里的一种

https://standardjs.com/rules-zhcn.html

  • 字符串使用单引号 – 需要转义的地方除外
  • 无分号没什么不好。不骗你!
  • 关键字后加空格 if (condition) { ... }
  • 函数名后加空格 function name (arg) { ... }
  • 坚持使用全等 === 摒弃 == 一但在需要检查 null || undefined 时可以使用 obj == null

自动修正 ESlint插件

  1. vscode里安装ESlint插件

  2. 在vscode里的setting.json里添加配置

    1
    2
    3
    4
    5
    6
    // 当保存的时候,eslint自动帮我们修复错误
    "editor.codeActionsOnSave": {
    "source.fixAll": true
    },
    // 保存代码,不自动格式化
    "editor.formatOnSave": false

prettier

格式化代码插件 可以不使用vscode插件 创建vue项目时选择即可

配置文件 .eslintrc.cjs

添加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 80, // 每行宽度至多80字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
}
],
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)
}
],
'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
// 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
}
  1. prettier 风格配置 https://prettier.io

    1. 单引号

    2. 不使用分号

    3. 每行宽度至多80字符

    4. 不加对象|数组最后逗号

    5. 换行符号不限制(win mac 不一致)

  2. vue组件名称多单词组成(忽略index.vue)

  3. props解构(关闭)

忽略报错

例如用了element-plus组件库的自动导入后,用到一些组件会报未定义的错误

配置文件 .eslintrc.cjs

添加

1
2
3
4
5
6
7
globals: {
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly'
...
//要用到的组件再做添加
}

Vuex

Vuex概述

概念:Vuex是一个Vue的状态管理工具,状态就是数据

使用场景:多个组件都要用同一份数据

优势

  • 数据集中化管理,
  • 响应式变化
  • 操作简洁

vuex的使用

步骤

  1. 安装Vuex

  2. 新建vuex模块文件

    新建store文件夹,新建index.js文件存放vuex

  3. 创建仓库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 导入 vue
    import Vue from 'vue'
    // 导入 vuex
    import Vuex from 'vuex'
    // vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
    Vue.use(Vuex)

    // 创建仓库 store
    const store = new Vuex.Store()

    // 导出仓库
    export default store
  4. 导入到main.js中并挂载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import Vue from 'vue'
    import App from './App.vue'
    import store from './store'

    Vue.config.productionTip = false

    new Vue({
    render: h => h(App),
    store
    }).$mount('#app')

核心概念-state状态

State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。

打开项目中的store.js文件,在state对象中可以添加我们要共享的数据。

1
2
3
4
5
6
7
8
9
10
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data,
// 区别:
// 1.data 是组件自己的数据,
// 2.state 中的数据整个vue项目的组件都能访问到
state: {
count: 101
}
})

获取公共数据state的语法

1
2
3
4
5
6
7
8
获取 store:
1.Vue模板中获取 this.$store
2.js文件中获取 import 导入 store


模板中: {{ $store.state.xxx }}
组件逻辑中: this.$store.state.xxx
JS模块中: store.state.xxx

辅助函数mapState

概念

mapState能够把store中的数据映射到组件的计算属性computed里

使用步骤 (在需要使用state的组件里)

  1. 从vuex中导入mapstate函数

    1
    import { mapState } from 'vuex'
  2. 采用数组形式引入state属性

1
mapState(['count'])
  1. 利用展开运算符将导出的状态映射给计算属性

    1
    2
    3
    computed: {
    ...mapState(['count'])
    }

​ 接下来要使用state中的count,就和使用state里的count一样

核心概念-mutations

在开发时,vuex应当遵循单向数据流,组件中不应该能直接修改仓库的数据

但默认可以修改,可以在开发时在创建store对象时,添加属性strict: true

上线时需 关闭严格模式

所以需要mutations来修改数据

步骤

  1. store对象中定义mutations对象,对象中存档修改state的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //mutations对象的方法里,第一个参数永远都是state
    const store = new Vuex.Store({
    strict: true,
    state: {
    title: 'Winter',
    count: 100
    },
    mutations: {
    addCount (state) {
    state.count++
    }
    }
    })
  2. 组件中提交调用mutations里的方法

    1
    this.$store.commit('addCount')

mutations传参语法

mutations里定义方法 方法名 (state, 参数)

组件传参this.$store.commit('方法名', 参数)

注意:提交的参数只能有一个 所以如果有多个参数可以提交一个对象

辅助函数-mapMutations

mapState将state 映射到组件计算属性computed

mapMutations将mutations里的方法 映射到组件方法method

核心概念-actions

state用于存放数据,mutations用于同步更新数据 而action负责异步操作

步骤

  1. 定义actions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //此处未分模块,context可以当成store仓库
    mutations: {
      changeCount (state, newCount) {
        state.count = newCount
      }
    }
    actions: {
    setAsyncCount (context, num) {
    // 一秒后, 给一个数, 去修改 num
    setTimeout(() => {
    context.commit('changeCount', num)
    }, 1000)
    }
    },
  2. 组件中通过dispatch调用

    this.$store.dispatch('setAsyncCount', 参数)

辅助函数 - mapActions

mapState将state 映射到组件计算属性computed

mapMutations将mutations里的同步方法 映射到组件方法method

mapActions将actions里的异步方法 映射到组件方法methods

使用

1
2
3
4
5
import { mapActions } from 'vuex'
methods: {
...mapActions(['changeCountAction'])
}
//changeCountAction就被映射到了methods里 直接this.changeCountAction使用就可以

核心概念-getters

getters用于从state中筛选出符合条件的一些数据

步骤

  1. 在store对象中定义getters

    1
    2
    3
    4
    5
    getters: {
    // getters函数的第一个参数是 state
    // 必须要有返回值
    filterList: state => state.list.filter(item => item > 5)
    }
  2. 使用getters

    1. 原生方式 $store this.$store.getters.filterList

    2. mapGetters 映射到计算属性

      1
      2
      3
      4
      import { mapgetters } from 'vuex'
      computed: {
      ...mapgetters(['filterList'])
      }

核心概念-module

将vuex模块化 便于管理数据和维护

步骤

  1. store目录下 新建modules文件夹,将模块放在这里(例如@/store/modules/user.js)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const state = {
    userInfo: {
    name: 'zs',
    age: 18
    }
    }

    const mutations = {}

    const actions = {}

    const getters = {}

    export default {
    state,
    mutations,
    actions,
    getters
    }
  2. 在index.js里导入

    1
    2
    3
    4
    5
    6
    7
    import user from './modules/user'

    const store = new Vuex.Store({
    modules:{
    user
    }
    })

使用模块的数据state

  1. 通过模块名直接访问$store.state.模块名.数据
  2. 通过mapstate映射
    1. 把整个模块映射 mapState['模块名']
    2. 映射某个数据 mapstate('模块名', ['数据']) 需要在对应模块导出时开启命名空间 namespaced: true

使用模块筛选过的数据 getters

  1. 通过模块名访问 $store.getters('[模块名/数据]')
  2. 子模块的映射 mapGetters('模块名', ['数据']) 也需要开启命名空间

使用模块中的同步方法 mutations

  1. 直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)
  2. 通过 mapMutations 映射
    1. 默认根级别的映射 mapMutations([ 'xxx' ])
    2. 子模块的映射 mapMutations('模块名', ['xxx']) - 需要开启命名空间

使用模块中的异步方法 actions

  1. 直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
  2. 通过 mapActions 映射
    1. 默认根级别的映射 mapActions([ 'xxx' ])
    2. 子模块的映射 mapActions('模块名', ['xxx']) - 需要开启命名空间

storage存储模块-Vuex持久化处理

一些登录凭证token和用户的信息userId 做一个持久化处理 封装到模块

  1. 新建 utils/storage.js 封装方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const INFO_KEY = 'hm_shopping_info'

    // 获取个人信息
    export const getInfo = () => {
    const result = localStorage.getItem(INFO_KEY)
    return result ? JSON.parse(result) : {
    token: '',
    userId: ''
    }
    }

    // 设置个人信息
    export const setInfo = (info) => {
    localStorage.setItem(INFO_KEY, JSON.stringify(info))
    }

    // 移除个人信息
    export const removeInfo = () => {
    localStorage.removeItem(INFO_KEY)
    }
  2. vuex user模块调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import { getInfo, setInfo } from '@/utils/storage'
    export default {
    namespaced: true,
    state () {
    return {
    userInfo: getInfo()
    }
    },
    mutations: {
    setUserInfo (state, obj) {
    state.userInfo = obj
    setInfo(obj)
    }
    },
    actions: {}
    }

第三方组件库 vant-ui

地址https://vant-contrib.gitee.io/vant/v2/#/zh-CN/

常用的组件库还有以下几种:

pc: element-ui element-plus iview ant-design

移动:vant-ui Mint UI (饿了么) Cube UI (滴滴)

安装

vue2项目 npm i vant@latest-v2 -S

组件全部导入(不推荐)

在main.js注册

1
2
3
import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)

按需导入(推荐)

  1. 安装插件 npm i babel-plugin-import -D

  2. babel.config.js中配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = {
    presets: [
    '@vue/cli-plugin-babel/preset'
    ],
    plugins: [
    ['import', {
    libraryName: 'vant',
    libraryDirectory: 'es',
    style: true
    }, 'vant']
    ]
    }
  3. 可将引入组件的步骤抽离到单独的js文件中 如utils/vant-ui.js

    1
    2
    3
    4
    5
    import Vue from 'vue'
    import { Button, Icon } from 'vant'

    Vue.use(Button)
    Vue.use(Icon)
  4. 在main.js中导入

1
2
// 导入按需导入的配置文件
import '@/utils/vant-ui'

按需导入中的全局使用

  1. 在main.js中导入并注册需要的组件

    1
    2
    import { Toast } from 'vant'
    Vue.use(Toast)
  2. 之后在其他组件里直接使用 无需导入

    1
    2
    3
    4
    5
    6
    7
    <script>
    export default {
    created () {
    this.$Toast('登录')
    }
    }
    </script>

移动端适配: vw适配

官方说明:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/advanced-usage

安装 npm i postcss-px-to-viewport@1.1.1 -D

为请求、响应拦截器添加loading

作用:用户提示,防止用户多次点击节流

  1. 请求时,打开 loading
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
Toast.loading({
message: '请求中...',
forbidClick: true,
loadingType: 'spinner',
duration: 0
})
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
  1. 响应时,关闭 loading
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 添加响应拦截器
request.interceptors.response.use(function (response) {
const res = response.data
if (res.status !== 200) {
Toast(res.message)
return Promise.reject(res.message)
} else {
// 清除 loading 中的效果
Toast.clear()
}
// 对响应数据做点什么
return res
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})

第三方组件库 element-plus

官方文档: https://element-plus.org/zh-CN/

  • 安装
1
$ pnpm add element-plus

自动按需:

  1. 安装插件
1
pnpm add -D unplugin-vue-components unplugin-auto-import
  1. 然后把下列代码插入到你的 ViteWebpack 的配置文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
...
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
})
  1. 直接使用
1
2
3
4
5
6
7
8
9
10
<template>
<div>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
...
</div>
</template>

表单校验规则

表单校验需要进行的配置

  1. el-form组件上绑定form数据对象

    1
    2
    3
    4
    5
    6
    7
    const formModel = ref({
    username: '',
    password: '',
    repassword: ''
    })

    <el-form :model="formModel" >
  2. el-form上绑定rules规则对象

    1
    2
    3
    4
    5
    <el-form :rules="rules" >

    const rules = {
    ...
    }
  3. 表单元素上v-model绑定form数据对象的子属性

    1
    2
    3
    4
    5
    <el-input
    v-model="formModel.username"
    :prefix-icon="User"
    placeholder="请输入用户名"
    ></el-input>
  4. prop绑定校验规则到元素el-form-item

    1
    2
    3
    4
    5
    6
    7
    <el-form-item prop="username">
    <el-input
    v-model="formModel.username"
    :prefix-icon="User"
    placeholder="请输入用户名"
    ></el-input>
    </el-form-item>

校验规则

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
const rules = {
// 为空就会报message的错误,触发时机为username改变时
// 长度小于5或者大于10就会触发报错,触发时机为 失焦
username: [
{ required: true, message: '请输入用户名', trigger: 'change' },
{ min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur'}
],
// 正则校验 pattern放正则规则
password: [
{ required: true, message: '请输入密码', trigger: 'change' },
{
pattern: /^\S{6,15}$/,
message: '密码必须是6-15位非空字符',
trigger: 'blur'
}
],
repassword: [
{ required: true, message: '请输入密码', trigger: 'change' },
{
pattern: /^\S{6,15}$/,
message: '密码必须是6-15位非空字符',
trigger: 'blur'
},
// 自定义校验, validator(rule, value, callback)
// rule 当前校验相关的信息
// value 所校验的表单元素的值
// callback 无论成功和失败,都需要回调
// -callback() 校验成功
// -callback(new Error(错误信息)) 校验失败
{
validator: (rule, value, callback) => {
if (value !== formModel.value.password) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}

表单预校验

validate是表单暴露的函数,可以表单所有规则的检测,要使用它只需

1
2
3
4
5
6
// 定义响应式变量
const form = ref()
// ref=form以调用内部方法
<el-form ref=form></el-form>
// 调用内部方法validate
form.value.validate()

表格结构与渲染数据

label用来显示到用户

data绑定数据 数组对象

prop用来接收对象中的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
<el-table :data="articleList" style="width: 100%">
<el-table-column prop="title" label="文章标题" width="width">
<template #default="{ row }">
<el-link type="primary" :underline="false">{{ row.title }}</el-link>
</template>
</el-table-column>
<el-table-column prop="cate_name" label="分类" width="width">
</el-table-column>
<el-table-column prop="pub_date" label="发表时间" width="width">
</el-table-column>
<el-table-column prop="state" label="状态" width="width">
</el-table-column>
</el-table>

表格自定义插槽

1
2
3
4
5
<el-table-column prop="title" label="文章标题" width="width">
<template #default="{ row }">
<el-link type="primary" :underline="false">{{ row.title }}</el-link>
</template>
</el-table-column>

table的每个column都可以自定义内容 这里的row就是表单中data里的数组对象的每个item

默认显示中文

App.vue 中直接导入设置成中文即可

1
2
3
4
5
6
7
8
9
10
<script setup>
import zh from 'element-plus/es/locale/lang/zh-cn.mjs'
</script>

<template>
<!-- 国际化处理 -->
<el-config-provider :locale="zh">
<router-view />
</el-config-provider>
</template>

Vue3

Vue3优势

vue3更容易维护

组合式API 更好的typesript支持

更快的速度

重写difff算法 模板编译优化 组件初始化更高效

更小的体积

良好的treeshaking 按需引入

更好的数据响应式

proxy

create-vue搭建vue3项目

vue-cli用来创建vue2 底层为webpack

create-vue用来创建vue3 底层为vite

npm init vue@latest

执行命令 安装并执行create-vue

组合式API setup选项

1. 执行时机

在beforeCreate之前自动执行 这也导致setup函数无法获取到this

2. 写法

  1. 组合式api放在setup选项里

  2. 数据和函数需要在setup最后return 才能在模板中使用

3. <script setup>语法糖

不必return 也不用加导出语句export default

组合式API reactive和ref函数

1. reactive

接受对象类型数据的参数传入并返回一个响应式的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
// 导入
import { reactive } from 'vue'
// 执行函数 传入参数 变量接收
const state = reactive({
msg:'this is msg'
})
const setSate = ()=>{
// 修改数据更新视图
state.msg = 'this is new msg'
}
</script>

<template>
{{ state.msg }}
<button @click="setState">change msg</button>
</template>

2. ref

接收简单类型或者对象类型的数据传入并返回一个响应式的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
// 导入
import { ref } from 'vue'
// 执行函数 传入参数 变量接收
const count = ref(0)
const setCount = ()=>{
// 修改数据更新视图必须加上.value
count.value++
}
</script>

<template>
<button @click="setCount">{{count}}</button>
</template>

3. reactive 对比 ref

  1. 都是用来生成响应式数据
  2. 不同点
    1. reactive不能处理简单类型的数据
    2. ref参数类型支持更好,但是js中必须通过.value做访问修改,模板中使用不需要加value
    3. ref函数内部的实现依赖于reactive函数,本质还是将简单类型数据变成了对象
  3. 在实际工作中的推荐
    1. 推荐使用ref函数,减少记忆负担

组合式API computed属性

思想与vue2一致 只是写法不同

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
// 导入
import {ref, computed } from 'vue'
// 原始数据
const count = ref(0)
// 计算属性
const doubleCount = computed(()=>count.value * 2)

// 原始数据
const list = ref([1,2,3,4,5,6,7,8])
// 计算属性list
const filterList = computed(item=>item > 2)
</script>

组合式API watch属性

侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate控制立刻执行,deep开启深度侦听

1. 侦听单个数据

1
2
3
4
5
6
7
8
9
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
})
</script>

2. 侦听多个数据

侦听多个数据,第一个参数可以改写成数组的写法

1
2
3
4
5
6
7
8
9
10
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('cp')
// 2. 调用watch 侦听变化
watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName])
})
</script>

3. immediate

在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调

1
2
3
4
5
6
7
8
9
10
11
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
},{
immediate: true
})
</script>

4. deep

通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep

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
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const state = ref({ count: 0 })
// 2. 监听对象state
watch(state, ()=>{
console.log('数据变化了')
})
const changeStateByCount = ()=>{
// 直接修改不会引发回调执行
state.value.count++
}
</script>

<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const state = ref({ count: 0 })
// 2. 监听对象state 并开启deep
watch(state, ()=>{
console.log('数据变化了')
},{deep:true})
const changeStateByCount = ()=>{
// 此时修改可以触发回调
state.value.count++
}
</script>

5. 精确侦听对象的某个属性

1
2
3
4
5
6
7
const userInfo = {
name: 'winter',
age: '23'
}
watch(() => userInfo.value.age, (newvalue, oldvalue) => {
console.log(newValue,oldvalue)
})

组合式API 生命周期函数

beforeCreatedcreated的相关代码 一律放在setup

选项式API的钩子在组合式API里也能用

vue3 同一个生命周期函数可以调用多次

Vue3的销毁不再是destroyed

组合式API 父子通信

1. 父传子

步骤

  1. 父组件绑定属性传值

  2. 子组件通过defineProps方法接收

示例

  1. 父组件<SonCom name="winter" :age="age"></SonCom>

  2. 子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script setup>
    const props = defineProps({
    name: String,
    age: Number
    })
    console.log(props.name);
    </script>
    <template lang="">
    <div>
    我是子组件 {{name}} {{age}}
    </div>
    </template>

2. 子传父

步骤

  1. 父组件在子组件标签中通过@绑定事件
  2. 子组件内部通过defineEmits生成emit方法 并触发事件传递参数

示例

  1. 父组件 <SonCom :age="age" @addWinterAge="addAge"></SonCom>

  2. 子组件

1
2
3
4
5
6
<script setup>
const emit = defineEmits(['addWinterAge'])
</script>
<template>
<button @click="emit('addWinterAge')">加年龄</button>
</template>

组合式API - 模板引用

1. 基本使用

概念:通过 ref标识 获取真实的 dom对象或者组件实例对象

步骤

  1. 调用ref函数生成ref对象
  2. 通过ref标识绑定ref对象到标签

示例

1
2
3
4
5
6
7
<script>
const inp = ref(null)
</script>
<template>
<input ref="inp" type="text">
</template>
//这样就拿到了输入框dom

2. defineExpose

默认情况下在 <script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法容许访问

示例

1
2
3
4
5
6
7
8
子组件
<script setup>
import {ref} from 'vue'
const count = ref(0)
defineExpose({
count
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
父组件
<script setup>
import {ref} from 'vue'
const testRef = ref(null)
const getCom = () => {
console.log(testRef.value.count)
}
</script>
<template>
<div>
<button @click="getCom">点击后输出子组件的数据count</button>
<testCom ref="testRef"></testCom>
</div>
</template>

组合式API provide和inject

作用场景:顶层组件向底层组件传递数据和方法,实现跨层级通信

步骤

  1. 顶层组件通过 provide 函数提供数据和修改数据的方法
  2. 底层组件通过 inject 函数提供数据

示例代码

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
import CenterBottom from './components/center-bottom.vue'
import { provide, ref } from 'vue'
const count = ref(100)
provide('count', count)
provide('changeCount', (newCount) => {
count.value = newCount
})
</script>
<template>
<div>
<CenterBottom></CenterBottom>
</div>
</template>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
import { inject } from 'vue'
const themeColor = inject('theme-color')
const count = inject('count')
const clickFn = inject('changeCount')
</script>
<template>
<div>
底层组件-{{ themeColor }}-{{ count }}
<button @click="clickFn(10)">更新count</button>
</div>
</template>

defineOptions

defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项。可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)

1
2
3
4
5
6
<script setup>
defineoptions({
name:'',
props:[...],
})
</script>

defineModel

优化自定义组件的v-model用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true
}
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
1
2
3
4
<script setup>
const modelValue = defineModel
modelValue.value++
</script>

Pinia

概念:Pinia是Vue最新状态管理库,Pinia去掉了mutation和modules,配合typescript更友好

Pinia使用步骤

如果使用vite创建vue项目时可以直接勾选pinia,也可以手动设置

手动设置 npm i pinia

Vue项目入口文件main.js中设置Pinia

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

创建store

1
2
3
4
5
6
7
8
9
10
11
12
import { defineStore } from 'pinia'
import { ref, computed} from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const addCount = () => count.value++
const subCount = () => count.value--
const double = computed(() => count.value * 2)
const msg = ref('winterlee')
return {count, msg, addCount, subCount, double}
},{
persist: true
})

pinia支持组合式api用法,因此可以用ref来指明state,用computed来指明getters,直接定义函数来代替actions

storeToRefs

从store中不能直接解构数据 解构需要用到storeToRefs

1
2
3
4
// 数据不会是响应式
const { count } = counterStore
// 数据保持响应式
const { count } = storeToRefs(counterStore)

从store中可以直接解构方法

1
const {addCount, SubCount} = couterStore

Pinia持久化插件

官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

  1. 安装插件 pinia-plugin-persistedstate
1
npm i pinia-plugin-persistedstate
  1. 使用 main.js
1
2
3
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
  1. 配置 store/counter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
...
return {
count,
doubleCount,
increment
}
}, {
persist: true
})
  1. 其他配置,看官网文档即可

Pinia - 配置仓库统一管理

pinia 独立维护

- 现在:初始化代码在 main.js 中,仓库代码在 stores 中,代码分散职能不单一

- 优化:由 stores 统一维护,在 stores/index.js 中完成 pinia 初始化,交付 main.js 使用

实现

​ src/stores/index.js

1
2
3
4
5
6
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)

export default pinia

​ main.js

1
2
3
4
5
6
7
8
9
10
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import '@/assets/main.scss'
import pinia from './stores'
const app = createApp(App)
app.use(pinia)
app.use(router)

app.mount('#app')

仓库 统一导出

- 现在:使用一个仓库 import { useUserStore } from ./stores/user.js 不同仓库路径不一致

- 优化:由 stores/index.js 统一导出,导入路径统一 ./stores,而且仓库维护在 stores/modules 中

实现

​ src/stores/index.js

1
2
3
4
5
6
7
8
9
10
11
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)

export default pinia

// import { useUserStore } from '@/stores/modules/user'
// export { useUserStore }

export * from './modules/user'