Vue

Hello Vue2

"let's us learn Vue2"

Posted by HZY on November 20, 2025
写在前面

本篇文章为作者再次学习vue的学习记录 目标效果为:能够做出一个“社团管理系统”的ui 之后配合作者的后端基础,争取在寒假中完成此项目 本篇文章持续更新,不会再开一篇新文章

Vue是什么

一个 构建用户界面 的 渐进式 框架

  • 构建用户界面 :基于数据动态渲染页面
  • 渐进式 :循序渐进的学习
  • 框架 : 一套完整的项目解决方案,提升开发效率

Vue上手

  • 准备容器
  • 引包(开发/生产)
  • 创建实例
  • 指定配置:
    • el :挂载点
    • data :数据

插值表达式

  • 可以是js语法,可以是算式
    • 可以使用函数,入toUpperCase()
    • 可以使用三元运算符
    • 不能使用语句 if,for
    • 不能在标签属性使用

响应式数据

  • 访问 : name.data
  • 修改 : name.data = value

开发者工具

在极简插件下载插件

Vue指令

带有v-前缀的 的特殊 标签属性

  • v-html = “表达式” –> InnerHtml
    1
    2
    3
    4
    
     解释:我们如果想把<a>标签放入一个div里面
     如果只是在msg中写<a>
     那么只会被解析为字符串
     解决办法就是设置属性v-html = "msg"
    
  • v-show = “表达式” 表达式的值是true才显示
  • v-if = “ 表达式” 表达式的值是true才显示

v-show本身还存在,只是给了css中dispaly进行赋值:适用于频繁切换隐藏;v-if则是是否需要创建或益处这个元素节点

  • v-else
  • v-else-if
  • v-on : 注册事件 可以用@代替 (实现计时器)

v-on: 事件名 = “内联语句” 可以直接用@替换v-on:

v-on : 事件名 = “methods” 注意使用变量的是否可以使用this,或者通过实例去访问

@click= fn 这样其实就可以了,

@click = fn(a,b) 这样就可以传入参数了

  • v-bind : 属性名 = “表达式”

v-bind :src=”imgurl”

  • v-for =” “ : 基于数据循环,可以多次渲染

this.tasks = this.tasks.filter(item=>item.id!==id)

  • v-model=’ ‘ : 双向数据绑定,给表单数据使用

指令修饰符

  • 按键修饰符
    • @keyup.enter=”fn” : 想当于fn(e){if(e.key===)}
  • 我去
    • v-model.trim=”var”
    • v-model.number=””
  • 阻止冒泡
    • @click.stop=””
  • 阻止默认行为
    • @click.prevent=””

样式控制

  • 增强class
    1
    2
    
    :class = "{类名1:布尔1,类名:布尔2}"
    :class = "[类名1类名2类名3]"
    
  • 增强style ``` js :style=”样式对象” :style = “{css属性名1:val1,name2:val2}”

:style = “{width=percent+’%’}”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意对于font-size来说,因为-不被识别,所以可以使用‘’括起来,或者转为驼峰命名法
#### v-model与表单元素
* input
* textarea
* input type="radio" :记得设置name和value
* select option:记得设置val
#### 计算属性:会自动计算
1. 声明在computed中
2. 和变量一样使用

``` js 
computed:{
	totalCount(){
		//直接随便用
		//复习一下reduce函数
		let total = this.list.reduce((sum,item)=>sum+item.num,0)
		return  结果
	}
}

computed计算属性 与 methods方法

  • 计算属性是有缓存的

这也是为什么能作为变量直接使用的原因,但是对于moethod,每次都需要调用数据,就是执行了一次函数,所以肯定不能看作为变量。

  • 计算属性作为变量的可变性,可修改性
1
2
3
4
5
6
7
8
9
10
computed{
	Example : {
		get(){

		},
		set(val){

		}
	}
}

watch监视器

监视数据变化,执行一些业务逻辑或异步请求

  • 简单写法:
1
2
3
4
5
6
7
8
watch:{
	数据属性名(newval,oldval){

	},
	'对象属性名'(newval,oldval){

	}
}

忘记了js中对接口发请求的方式了,这里老师写了一个防抖的程序,这个方面我还没有学会

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  watch :{
	'obj.words'(newValue){
		clearTimeout(this.timer)
		this.timer = setTimeout(async()=>{
			const res = await axios({
				url:'',
				params:{
					words:newValue
				}
			})
			this.result = res.data.data
		},1000)
	}
  }
  • 完整写法
    1. deep : true 对复杂类型进行深度监视
    2. immediate : true 初始化立刻执行
1
2
3
4
5
6
7
8
9
10
11
watch : {
	obj : {
		deep : true,
		handler(newval){
			//业务逻辑
		},
		immediate : true 
	}

}

Js里面的常用函数

这个部分我可能要多次来写了,因为,在vue开发过程中发现了很多问题,就是js基础不牢固

  • list.filter : 过滤,保留true
  • list.every : 是否全部满足
  • list.reduce : 累加,汇总,合并
  • list.forEach : 遍历数组但不返回
  • list.map : 根据原有数据返回一个新数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
const newlist = this.list.filter(ele => item.isLive)
<li v-for="elem in list.filter(t=>t.islive)"  :key="elem.id"></li>

const result = this.list.every(item=> 条件)
 
const result = this.list.reduce((newval,item)=>{return nextval;},primVal)
const sum = this.nums.reduce((all,n)=>all+n,0)

list.forEach((item,index,array)=>{
	//进行操作
});

const result = list.map((item,index,array)=>{
	retrun 
})
const name = users.map(user=>user.name) //提取字段
const newdata = this.list.map((elem,index)=>{
	id : index+1.
	name : elem.name
})
  • 转存json到本地 : 持久化操作 (这个部分我跳过了好多次,需要及时复习)
1
2
3
4
5
localStorage.setItem('list',JSON.stringify(newValue))

fruitList : JSON.parse(localStorage.getItem('list')) || []
//好好想想我为什么要|| []  
//当用户把localStorage清除了,fruitList就成了null,整个页面都会崩溃

请分析我上面为什么要使用 || []

Vue的生命周期 和 生命周期 的四个阶段

定义 : 一个Vue实例从 创建 到 销毁 的整个状态 生命周期四个阶段 : 创建->挂载->更新->销毁

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

  • beforeCreate()
  • created() : 发送初始化渲染请求
  • beforeMount()
  • mounted() : 此步就已经完成的页面的渲染,可以操作DOM
  • beforeUpdate()
  • updated() : 修改数据,更新视图
  • beforeDestory() : 释放资源
  • destoryed()
1
2
3
4
5
6
7
8
9
10
11
12
	
	async created(){
		const res = await axios.get(url)
		//渲染页面完成前 拿到数据
	}

	//进入页面后搜索框获取焦点
	//vue中 autofocus不管用
	async mounted(){
		document.querySelector('#id').focus()
	}

接口使用

1
2
3
4
5
6
7
8
9
10
11
async add(){
	const res = await axios.post("url",{
		//your data
	})
}
async del(){
	const res = await axios.del("url",{
		//necessity
	})
}

Vue Cli

1
2
vue create [ProjectName]
vue run [ServerName]
  • /public
    • index.html //模板文件
  • /src
    • assets
    • components
    • App.vue
    • main.js
      1. 导入Vue
      2. 导入App.vue
      3. Vue实例化
  • package.json

组件化开发 & 根组件

  • 组件化 :一个页面可以拆分成一个个组件,每个组件有着自己独立的结构,样式,行为
  • 根组件: App.vue
    1. template : 结构
    2. script : js逻辑
    3. style : 样式(可以支持less)
      • <style lang="less">

普通组件的注册使用

  1. 局部注册
    • components创建vue文件
    • 导入 & 局部注册
    1
    2
    3
    4
    5
    6
    7
    8
    
         import VueComponet form './components/VueComponent'
         export default{
             components : {
                 VueComponent : VueComponent,
                 VueComponent //如果是驼峰式,就可以这样省略着写了
             }
         }
    	
    
  2. 全局注册 : 在所有组件内都能使用
    • 创建vue文件
    • 在main.js中进行全局注册
    1
    2
    3
    
         import HmButton from './components/HmButton'
         vue.component('HmButton',HmButton)
    
    

组件的三大组成部分

  • 结构
    • 只有一个根组件
  • 样式
    • 全局样式:默认样式为全局样式
    • 局部样式:给style加上scoped属性
  • 逻辑
    • data 必须是一个函数(组件的数据必须由函数提供)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
          <script>
              export default{
                  data : {
                      return {
                      //这里面写东西
                      count : 999
                      }
                  }
              }
          </script>
    

组件通信

  • 组件内的数据本来是独立的
  • 通过组件通信传递数据
  • 组件关系
    • 父子关系 props | &emit
      1. props : 任意类型
      1
      2
      3
      4
      
        //父组件
        <Son :name="data_name"> 
        //子组件
        props : ['name'] 
      
      1. $emit
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        //子组件
        methods:{
            handleClick(){
                this.&emit('消息名称,类似click','')
            }
        }
        //父组件
        <Son :title="data_name" @消息名称 = "fn">
        methods : {
            fn(){
      
            }
        }
      
    • 非父子关系 provide | inject | eventbus
      1. eventbus
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
        //先创建EventBus.js
        import Vue from 'vue'
        const Bus = new Vue()
        export default Bus
        //发送方
        import Bus from '../utils/EventBus'
        export default {
            methods : {
                clickSend(){
                    Bus.$emit('sendMsg',data)
                }
            }
        }
        //接收方收听Bus
        import Bus from '../utils/EventBus'
        export default{
            create(){
                Bus.$on('snedMsg',(data)=>{
      
                })
            }
        } 
      
      1. provide & inject
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
        //跨层级访问数据
        export default{
            provide(){ 
                //简单类型:非响应式
                color : this.color
                //复杂类型:响应式的
                data : {
      
                }
            }
        }
      		 
        export default{
            inject : ['color']
        }
      
    • 通用方案 vuex

prop校验

  • 类型校验

    1
    2
    3
    4
    5
    
      props:{
          w : Number
          str : String
          bool : Boolean
      }
    
  • 非空校验
  • 默认值
  • 自定义校验
1
2
3
4
5
6
7
8
9
10
11
12
props : {
	value : {
		type : Number,
		required : true,
		default : 默认值,
		validator(value){
			//自行校验逻辑
			return bool
		} 
	}
}

prop & data

单向数据流

  • data 是自己的数据,随便改
  • prop 的数据是别人的,不能随便改

v-model的原理

  • 原理: 本质就是语法糖。value + input = button 中的 v-model
  • 双向绑定,其实就是触发事件和数据绑定
  • $event 用于在模板中,获取事件的形参

表单类组件的封装 & v-model简化代码

  1. 父传子: 数据由父传子得来,但是这样就不能进行model双向绑定了,所以需要进行 v-model 拆解绑定数据
  2. 子传父: 监听输入,子传父 传值给父组件修改
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
<BaseSelect :cityId="selectdi" @changeId='selectId=$event.'/>

<select :vlaue="cityId" @change="handleChange" />

methods:{
	handleChange(e){
		//v-model默认
		// cityId = $e.target.value

		//修改 更麻烦的双向绑定
		this.$emit('changeId',e.target.value)
		
	}
}


//子组件使用value和input字段定义后,父组件可以直接 v-model
<BaseSelect v-model='selectid'/> 


<select :value='value' @change ='handleChange' />
 props : {
	value : String
 }
 methods : {
	handleChange(e){
		this.$emit('input',e.target.value)
	}
 }


 //

.sync 简写$用于父子双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<BaseDialog   :visible.sync = "isShow"/>
//相当于下面
<BaseDialog   :visibel="isShow" @update:visiable="isShow=$event">

//子组件
props : {
	visible:Boolean
}

methods : {
	handleChange(){
		this.$emit('update:visible',false)
	}
}

ref $ref

  • 获取dom元素或组件实例
  • 查找范围->当前组件内
    • 最好在moutned 中定义函数 document.querySelector(‘className’)
  • 获取dom元素
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    //我们使用ref来替代class
    <div ref="mychart"/>
    
    export default{
        mounted(){
            const mychart = echarts.init(this.$ref.mychart)
        }
    }
    
    
  • 获取表单元素

    1
    2
    3
    4
    5
    
          <BaseForm ref="baseForm"/>
          //在父对象中
          this.$ref.baseForm.组件的方法()
          //举个例子
          this.$ref.baseForm.getValue()
    

Vue异步更新 与 $nextTick()

对于vue的异步更新DOM,我们一些需求可能不能很好执行: 比如创建dom后不能及时获取dom,因为vue还没更新dom

1
2
3
4
5
6
7
8
methods:{
	handleChange(){
		this.isShow = false
		this.$nextTick(){
			this.&ref.inf.focus()  //进行操作
		}
	}
}

自定义指令

  • 全局注册
1
2
3
4
5
 Vue.directives('focus',{
	inserted(el){
		el.focus()
	}
 })
  • 局部注册
1
2
3
4
5
6
7
directives : {
	focus :{
		inserted(el){
			el.focus()
		}
	}
}
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
//案例1
color : {
	inserted(el,binding){
		el.style.color = binding.value 
	},
	update(el,binding){
		el.style.color = binding.value
	}
}

//案例2:请求等待
<div>
	<li v-for=""   v-loading="isLoading">  </li>
</div>
data(){
	return {
		isLoading : true
	}
}


directives : {
	loading : {
		inserted (el,binding){
			isLoading?el.styleList.add():el.styleList.remove()
		},
		update (el,binding){
			isLoading?el.styleList.add():el.styleList.remove()
		}
	}
}

插槽

作用:让组件内部的一些结构支持自定义 需求:让组件的内容不被定义,可修改

1
2
3
4
5
6
7
8
9
//组件内部用slot直接占坑
<slot> 
	我是默认内容如果你不传内容就是我
</slot>

//在使用组件的时候随便加
<TinyButton>
	<div>我是神迷奇男子</div>
</TinyButton>
  • 具名插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//写name属性
<slot name="slot1">  

</slot>

//使用时候
<template v-slot:slot1> 
	
</template>

<template #slot1>

</template>

  • 作用域插槽(是一个传参语法)
1
2
3
4
5
6
7
8
//以添加属性的方式传值
//以对象的形式传过去
<slot :row="item" msg='this is a test'> </slot>

<template #default="obj"> </template>
//也可以直接解构
<template #default="{row}"> </template>

SPA(Single Page Application)

  • 单页 (文档|系统|内部|移动端)
    1. 性能高
    2. 体验好
    3. 首屏加载慢
    4. SEO差
  • 多页 (公司官网|电商网站)
    1. 整页性能低
    2. 首屏加载快
    3. SEO快

#### vue的路由 路径和组件的映射关系

#### VueRouter(5+2)

  • 基础5步使用路由

yarn add vue-router@3.6.5 import VueRouter from ‘vue-router’ Vue.use(VueRouter) const router = new VueRouter new Vue({ render : h => h(App), router : router }).$mount(‘#app’)

  • 核心步骤
    1. 创建组件(views目录)
    1
    2
    3
    4
    5
    6
    7
    
          const router = new VueRouter({
              routes : [
                  {path : '/find' , component: Find},
                  {path : '/my' , component: My},
                  {path : '/friend',component: Friend}
              ]
          })
    
    1. 配置导航
    1
    2
    
          <a href='#/find'> </a>
          <router-view></router-view>
    

views 和 components

  • 页面组件
    • src/views
    • 页面展示
    • 配合路由
  • 复用组件
    • src/components
    • 展示数据
    • 多用于复用

路由的封装抽离

在src/router/index.js配置有关路由的东西

@即为src目录 ‘@src/views/···’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Find from '../views/Find.vue'
import Friend from '../views/Friend.vue'
import My from '../views/My.vue'


import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
  routes : [
    {path:'/find',component:Find},
    {path:'/friend',component:Friend},
    {path:'/my',component:My},
  ]
})

export default router

声明式导航

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<router-link to="/"> </router-link>
//用router-link来替换a
<a src="#/"> </a> 
//希望你已经理解了上面的不同了

//更改样式
a.router-link-active{
	//模糊匹配
}

router-link-exact-active{
	//精确匹配
}

//我们在router可以改这个高亮类名
const router = new VueRouter({
	routes:[],
	linkActiveClass : '',
	linkExactActiveClass : ''
})	

  • 跳转传参
    1. 查询参数传参
      • get
      • to=”/path?key=value”
      • $route.query.key
    2. 动态路由传参
      • post
      • 在routes: [{path: ‘/search/:words’}]匹配多个路径
      • to=”/path/参数”
      • $route.params.参数
1
2
3
4
5
6
7
8
9
10
11
 

//发请求,在created里面
created(){
	this.$route.query.key
}

//对于法2
/search/:words
/search/:words?
//这两种写法完全不同,第一种强制必须传递参数,没有参数就会显示空白

查询参数比较适合多个参数,可以用&分隔,动态比较简洁,适合传递单个参数

重定向|模式设置

1
2
3
4
5
6
7
8
9
10
11
12
13
//重定向
{path:'/',redirect:'/home'}

//404
{path:'*',component: NotFind}

//hash路由 带有井号
//history路由  不带井号  
const router = new VueRouter({
	routes,
	mode : 'history'
	//注意一旦采用history模式,后台需要配置访问规则
})

基本调转

按钮如何实现跳转

  • path 路径跳转
  • name命名路由跳转
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
//直接在函数里面写
this.$router.push('/search')

this.$router.push({
	path : '/search'
})


{name:'goHome' ,path:'/search',component:MySearch}
this.$router.push({
	name : 'goHome'
})

//查询传参
this.$router.push(`/search?key=${value}`)
this.$router.push({
	path : '/search',
	query : {
		key : this.value
	}
})
//动态路由传参
this.$router.push(`/search/${value}`)
this.$router.push({
	path : `/search/${value}`
})

// 带name的路由传参
this.$router.push({
	name : 'router_name',
	query : {
		key : value
	}
})

this.$router.push({
	name : 'router_name',
	params : {
		key : value
	}
})

二级路由

直接在一级路由里面配置children

1
2
3
4
5
routes : [
	{path : ' ',components: ,children : [
		{path:'',components: }
	]}
]
  • 返回上一级

    $router.back()

缓存

问题: 原因:

  • keep-alive
    • 可以包裹动态组件,缓存不活动的组件
1
2
3
4
5
6
7
8
9
10
//直接包裹,所有路由组件都会被缓存
<keep-alive>
	<router-view> </router-view>
</keep-alive>
//include  组件名数组
//exclude
//max
<keep-alive :include="['LayoutPage']">
	<router-view> </router-view>
</keep-alive>

被包裹之后,被缓存的组件会多两个生命周期钩子

  • actived :
  • deactived :

VueCil自定义创建项目

1
2
vue create project1

ESlint代码规范

  • 字符串单引号
  • 无分号
  • 关键字后加空格
  • 函数名后加空格
  • 坚持使用===

坚持代码规范,学会看报错

  • 自动修正工具
    • ESLint
    1
    2
    3
    4
    5
    
          //在vscode设置里面配置
          "editor.codeActionsOnSave":{
              "source.fixAll" : true
          },
          "editor.formatOnSave": false
    

Vuex

  • 状态管理工具,状态即数据
  • 管理多组件共享的数据
  1. 多组件通用数据
  2. 多个组件共同维护数据
  • 共同维护,集中管理
  • 响应式变化
  • 操作简洁
  1. 安装vuex

    注意2->3 3,3->4 4

  2. 新建store模块
  3. 创建 Vue.use()
  4. 在main.js导入挂载

    state|mutations|mapMutations

    • 直接访问
1
2
3
4
5
6
7
8
9
10
11
//提供
const store = new Vuex.Store({
	state : {
		title : '大标题',
		count : 100
	}
})

//使用,在任意组件都可以访问到
this.$store.state.title
this.$store.state.title
  • mapState辅助函数
1
2
3
4
5
6
import {mapState} from 'vuex'
 

computed : {
	 ...mapState(['count','title'])
}
  • mutations
    1. vuex同样遵循单项数据流,组件中不能直接修改仓库的数据
    2. 传参只能传一个,要传多个,就包对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const store = new Vuex.store({
	strict : true,
	state:{

	},
	mutations : {
		addCount (state,n){
			state.count += 1
		}
	}
})

//下面的代码是错的,但是能运行,开启严格模式就会报错
this.$store.state.count++

//正确示范,带提交载荷payload
this.$store.commit('addCount',5)

  • mapMutations
1
2
3
4
5
6
7
8
import {mapMutations} from 'vuex'

methods : {
	 ...mapMutations(['subcount' ])
}

this.subcount(n)

actions | getter

  • actions 完成异步操作
    1. mutations必须是同步的,异步用actions完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//这个逻辑很烦,就是说actions不能直接调用state然后更改,必须通过commit来调用mutations里面的方法然后进行更改
mutations : {
	changeCount (state,newCount){
		state.count = newCount
	}
}

actions : {
	setAsyncCount(context,num){
		setTimeout(()=>{
			context.commit('changeCount',num)
		},1000)
	}
}


//调用,这些操作还是真的烦,后面Pinia会让这些全部变为白雪
handleChange () {
	this.$store.dispatch('changeCountAction',666)
}
  • mapActions
1
2
3
4
methods : {
	...mapActions(['changeCountAction'])
}

  • getters 相当于计算属性
1
2
3
4
5
6
7
8
9
10
11
getters : {
	filterList () {
		return state.list.filter(item => item>5)
	}
}
//直接访问
this.$store.getters.filterList
//辅助函数
computed : {
	...mapGetters(['filterList'])
}

模块module

创建store/modules/user.js

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
//index.js
import user from './modules/user'

modules : {
	user,
}


//user.js
const store = {

}
const mutations = {}
const actions = {}
const getters = {}
export default {
	namespaced : true,
	state,
	mutations,
	actions,
	getters
}
//访问
this.$store.模块名.xxx
this.$store.getters['模块名/zzzzz']

...mapState('模块名',['xxx']) //需要开启命名空间
...mapGetters['user',['zzzzz']]

$store.commit('模块名/xxx'额外参数)
$store.dispatch('模块名'['xxx'])

json-server

1
2
3
npm install -h json-server
#创建json文件 放在db文件夹
json-server --watch index.json