写在前面
本篇文章为作者再次学习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)
}
}
- 完整写法
- deep : true 对复杂类型进行深度监视
- 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
- 导入Vue
- 导入App.vue
- Vue实例化
- package.json
组件化开发 & 根组件
- 组件化 :一个页面可以拆分成一个个组件,每个组件有着自己独立的结构,样式,行为
- 根组件: App.vue
- template : 结构
- script : js逻辑
- style : 样式(可以支持less)
<style lang="less">
普通组件的注册使用
- 局部注册
- components创建vue文件
- 导入 & 局部注册
1 2 3 4 5 6 7 8
import VueComponet form './components/VueComponent' export default{ components : { VueComponent : VueComponent, VueComponent //如果是驼峰式,就可以这样省略着写了 } }
- 全局注册 : 在所有组件内都能使用
- 创建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
- props : 任意类型
1 2 3 4
//父组件 <Son :name="data_name"> //子组件 props : ['name']
- $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
- 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)=>{ }) } }
- 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
- 父子关系
props | &emit
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简化代码
- 父传子: 数据由父传子得来,但是这样就不能进行model双向绑定了,所以需要进行 v-model 拆解绑定数据
- 子传父: 监听输入,子传父 传值给父组件修改
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)
- 单页 (文档|系统|内部|移动端)
- 性能高
- 体验好
- 首屏加载慢
- SEO差
- 多页 (公司官网|电商网站)
- 整页性能低
- 首屏加载快
- 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’)
- 核心步骤
- 创建组件(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 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 : ''
})
- 跳转传参
- 查询参数传参
- get
- to=”/path?key=value”
- $route.query.key
- 动态路由传参
- 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
- 状态管理工具,状态即数据
- 管理多组件共享的数据
- 多组件通用数据
- 多个组件共同维护数据
- 共同维护,集中管理
- 响应式变化
- 操作简洁
- 安装vuex
注意2->3 3,3->4 4
- 新建store模块
- 创建 Vue.use()
- 在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
- vuex同样遵循单项数据流,组件中不能直接修改仓库的数据
- 传参只能传一个,要传多个,就包对象
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 完成异步操作
- 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