Vue基础 笔记
vue用法、指令、组件、路由、vuex
- 构建用户界面的渐进式前端框架
- 特性
- 数据驱动视图
- 当页面中数据发生变化时会驱动页面视图自动渲染更新
- 优点:只需维护数据,vue自动渲染页面结构
- 缺点:单向的数据绑定
- 双向数据绑定
网页中,form表单负责采集数据,Ajax负责提交数据
js数据变化会自动渲染到页面上,页面上表单采集的数据的变化会被vue自动获取到,并更新到js数据中
- 数据驱动视图
原来渲染数据:操作DOM
Vue: 自动监听数据变化,自动渲染页面结构
MVVM
是vue实现数据驱动视图和双向数据绑定的核心原理,model、view、viewmodel
- model 表示页面渲染时所依赖的数据源
- view表示当前页面所渲染的dom结构
- viewmodel表示vue的实例,他是mvvm的核心,将model和view连接在一起
创建vue实例,初始化渲染
<div id="app">
{{msg}},{{count}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 引入vuejs,在全局环境中就有了vue构造函数,可创建实例 -->
<script>
const app=new Vue({
el:'#app',
data:{
msg:'你好',
count:'666'
}
})
</script>
插值表达式 {{}}
利用表达式进行插值,将数据渲染到页面中
- 支持表达式>,不是语句 if for
- 标签属性中不能用插值
- 数据要存在
响应式
数据的响应式处理:数据变化,视图自动更新
data中的数据会被添加到实例上
Vue指令
带有 v-前缀 的特殊标签属性
- v-html
- v-show
控制元素显示隐藏,适合频繁切换显示和隐藏的场景
- v-if
-
控制元素显示隐藏(条件渲染)
底层原理(v-show和v-if)
- v-show:通过切换css的display:none来控制是否隐藏
- v-if:根据判断条件来 创建 和 移除
- v-else和v-else-if
辅助v-if进行判断渲染
注:需要与v-if一起使用
<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>=70">成绩评定B:奖励周末郊游</p>
<p v-else-if="score>=60">成绩评定C:奖励零食礼包</p>
<p v-else>成绩评定D:惩罚一周不能玩手机</p>
</div>
<script src="../../vue_ehyay/js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
gender:1,
score:59
}
})
- v-on
注册事件=添加监听+提供处理逻辑
简写: @事件名
v-on:事件名=“内联语句”
<body>
<div id="app">
<button v-on:click="count--">-</button>
<span>{{count}}</span>
<button @click="count++">+</button>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
count:100
}
})
</script>
</body>
v-on:事件名=“methods中的函数名”
<body>
<div id="app">
<button @click="fn">切换显示隐藏</button>
<h1 v-show="isShow">黑马程序员</h1>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
isShow:true
},
methods:{
fn(){
// console.log(app.isShow);
//让提供的所有methods中的函数,this都指向当前实例
//可以将app改为this
//this.isShow=!this.isShow
app.isShow=!app.isShow
}
}
})
</script>
</body>
v-on 传参
- v-bind
动态设置html的标签属性
<div id="app" v-bind:title="msg">你好1</div>
<div id="app" :title="msg">你好2</div>
v-bind对于样式控制的增强-操作css
v-on和v-bind和v-model的区别
- v-on 事件绑定,简写@
v-on绑定的是事件(函数)是vue中methods中的数据,触发v-on绑定,就会执行其所绑定的事件。- v-bind 属性绑定,简写:
v-bind的绑定只是单向的,他会将data中的数据投影到绑定的地方,在被绑定的地方对数据修改时,data中的原始数据是不会改变的- v-model双向绑定
//对象;适用场景->一个类名,来回切换
<div class="box" v-bind:class="{pink:true,big:true}">黑马程序员</div>
//数组;适用场景->批量添加或删除
<div class="box" :class="['pink','big']">黑马程序员</div>
- v-for
基于数据循环,多次渲染整个元素-》
v-for=“(item,index) in 数组”
<body>
<div id="app">
<h3>小黑水果店</h3>
<ul>
<li v-for="(item,index) in list">{{item}}-{{index}}</li>
</ul>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: ['西瓜', '苹果', '鸭梨']
}
})
</script>
</body>
v-for中的key,key属性=“唯一标识”,便于vue进行列表项的正确排序复用
- key只能为字符串或数字类型
- key的值必须具有唯一性
- 建议采用id,不推荐index,因为index会变化,不对应
- v-model
给表单元素使用,使用双向数据绑定-》可以快速获取或设置表单元素的内容
- 数据变化-》视图自动更新
- 视图变化-》数据自动更新
unshift修改原数组添加
应用于其他表单元素
它会根据控件类型自动选取正确方法来更新元素
指令修饰符
通过"."指明一些指令后缀,不同后缀封装了不同的处理操作
计算属性
基于现有数据,计算出来的新属性。依赖的数据变化,会重新计算
当多个数据影响一条数据时,用computed
computed作用
- 封装了一段对于数据的处理,求得一个结果
- 作为属性,直接使用-》this.计算属性 {{计算属性}}
- 缓存特性(提升性能):计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算->并再次缓存
methods方法
给实例提供一个方法,调用以处理业务逻辑
计算属性完整写法(修改)
- 默认的computed,只能读取访问,不能修改
computed: {
//简写
// fullname(){
// return this.firstname+this.lastname
// }
//完整写法 get+set
fullname:{
//fullname被获取求值时,执行get
get(){
return this.firstname+this.lastname
},
//fullname被修改赋值时,执行set
//修改的值,传递给set方法的形参
set(value){
//console.log(value);
this.firstname=value.slice(0,1)
this.lastname=value.slice(1)
}
}
}
watch 侦听器(监视器)
监视数据变化,执行一些业务逻辑或异步操作
- 简单写法-》简单数据类型,直接监视
const app = new Vue({
el: '#app',
data: {
words: '',
obj: {
names: ''
}
},
// 具体讲解:(1) watch语法 (2) 具体业务实现
watch:{
//简单数据类型
words(newValue){
console.log('变化了',newValue);
},
// 复杂数据类型
'obj.names'(newValue,oldValue){
console.log('变化了',newValue,oldValue);
}
}
- 完整写法-》添加额外配置项
- deep:true 对复杂类型深度监视
- immediate:true 初始化立刻执行一次handler方法
Vue生命周期
一个Vue实例从创建到销毁的整个过程
四个阶段、八个钩子
- 创建、挂载、更新、销毁
生命周期函数(钩子函数)
Vue生命周期过程中,会自动运行一些函数,被称为生命周期钩子,让开发者可以在特定阶段运行自己的代码
created 应用
响应式数据准备好,可以发送初始化渲染请求
mounted应用
模板渲染完成,可以开始操作DOM
操作DOM(例:文本框获取焦点)
工程化开发&脚手架Vue CLI
工程化开发:基于webpack架子进行开发
vue cli 是vue官方提供的全局命令工具
可以帮助我们快速创建一个开发vue项目的标准化基础架子
目录
组件化开发&根组件
组件分类:普通组件、根组件
App.vue文件(单文件组件)的三个组成部分
- 结构 template:只能有一个根元素
- 行为 script :js逻辑
- 样式 style:可支持less,装包
让style支持less
- 给style加上 lang=“less”
- 安装依赖包 less less-loader
- yarn add less less-loader -D /// npm i less lessloader -D(开发依赖)
普通组件的注册使用(局部)
局部注册:仅能在注册的组件内使用
- 创建.vue文件
- 在使用的组件内导入并注册
- components:{组件名:组件对象}
使用:当成html标签使用
普通组件的注册使用(全局)
全局注册:所有组件内都能使用
- 创建 .vue文件
- main.js中全局注册
- Vue.component(组件名,组件对象)
一般用局部注册,通用组件采用全局注册
scoped样式冲突
组件三大组成部分
- 结构 template:只能有一个根元素
- 行为 script :el根实例独有,data是一个函数,其他配置项一致
- 样式 style: 默认为全局样式,即影响所有组件的局部样式;scoped下样式,只作用于当前组件
scoped原理:
- 给当前组件模板的所有元素,都添加一个自定义属性 data-v-hash值
- css选择器后面,被自动处理,添加上了[data-v-hash] 的属性选择器
最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到
data是一个函数
一个组件的data必须是一个函数。保证每个组件实例,维护独立的一份数据对象。
- 每次创建新的组件实例,都会重新执行一次data函数,得到一个新对象。
组件通信
组件与组件之间的数据传递
- 组件的数据是独立的,无法直接访问其他组件的数据
- 想用其他组件的数据-》组件通信
组件关系分类:
-
父子关系
-
非父子关系
父子通信流程图
父->子 props
父组件通过props将数据传递给子组件
- 父中给子添加属性传值
- 子props接收
- 使用
子->父 $emit
- 子$emit发送消息
- 父中给子添加消息监听
- 父中实现处理函数
prop详解
组件上注册的一些自定义属性
作用:向子组件传递数据
特点:可传递任意数量的prop;可传递任意类型的prop
prop校验
为prop指定验证要求,不符合要求,提示错误信息
- 类型校验
- 非空校验
- 默认值
- 自定义校验
export default {
//props: ["w"],
// 1.基础写法(类型校验)
// props:{
// w:Number
// }
// 2.完整写法(类型、是否必填、默认值、自定义校验)
props:{
w:{
type:Number,
//required:true, // 非空
default:30, // 默认值
validator(value){
//自定义校验逻辑
if(value>=0&&value<=100){
return true
}else{
console.error('传入的prop w 必须是...');
return false
}
}
}
}
}
prop &data 单向数据流
谁的数据谁负责
共同点:都可以给组件提供数据
区别:
- data的数据是自己的-》随便改
- prop的数据是外部的-》不直接更改,要遵循 单向数据流
单向数据流:父组件的prop更新,会单向向下流动,影响到子组件
持久化存储逻辑:watch监听变化,往本地存储,进入页面优先读取本地
非父子通信(拓展)-event bus 事件总线
作用:非父子组件之间,进行简易消息传递(复杂:vuex)
非父子通信(拓展)-provide&inject
作用:跨层级共享数据
v-model 原理
- 原理 v-on和v-bind的语法糖
- 表单类组件封装
父传子做渲染 不能用v-model直接监听,因为数据是父亲的,需要做拆解
- 父组件v-model简化代码,实现子组件和父组件双向绑定
.sync修饰符
v-model和.sync的区别:
- 一个组件上只能有一个v-model,.sync可以有多个
- v-model 等价于 value(数据->视图)和@input(视图->数据)
- .sync 等价于 @update:num 相当于添加了一个事件监听,回调函数中,会把监听到的值赋给数据
ref和$refs
利用ref和$refs可以用于获取dom元素,或组件实例
特点:查找范围-》当前组件内(更精确稳定)
Vue异步更新、$nextTick
$nextTick: 等DOM更新后,才会触发执行此方法里的函数体
自定义指令
内置指令:v-html、v-for、v-model…
自定义指令:v-focus、v-loading、v-lazy…
每个指令有着自己独立的功能
- 自定义指令:自己定义的指令,可以封装一些dom操作,扩展额外功能
inserted 指的是,指令所绑定的元素被添加到页面中时,会自动调用
v-focus指令封装
// 全局注册指令
Vue.directive('focus',{
// inserted 会在指令所在的元素被插入到页面中时触发
inserted(el){
// el就是指令所绑定的元素
console.log(el);
el.focus()
}
})
案例:给不同标签设置不同的颜色
指令值的语法:
- v-指令名=“指令值”,通过等号 可以绑定指令的值
- 通过binding.value可以拿到指令的值
- 通过update钩子,监听指令值的变化,更新dom
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>
<script>
export default {
data () {
return {
color1:'red',
color2:'blue'
}
},
directives:{
color:{
inserted(el,binding){
// console.log(el);
el.style.color=binding.value
},
// update在指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update(el,binding){
console.log('指令的值修改了')
el.style.color=binding.value
}
}
}
}
</script>
v-loading 指令封装
场景:实际开发过程中,发送请求需要使用,在请求的数据未回来时,页面会处于空白状态=》用户体验不好
directives:{
loading:{
inserted(el,binding){
binding.value? el.classList.add('loading'):el.classList.remove('loading')
},
update(el,binding){
binding.value?el.classList.add('loading'):el.classList.remove('loading')
}
}
}
插槽
默认插槽(只能完成一个定制位置)
让组件内部的一些结构支持自定义
插槽基本语法:
- 组件内需要定制的结构部分,改用占位
- 使用组件时,标签内部,传入结构替换slot
<!--在使用组件时,组件标签内填入内容-->
<MyDialog>你确认要退出本系统么?</MyDialog>
<MyDialog>你确认要删除本系统么?</MyDialog>
插槽-后备内容(默认值)
给插槽设置默认内容,防止内容为空时,用户体验不好
语法:在标签内,放置内容,作为默认显示内容
具名插槽
(具有名字)组件内定制多处结构
v-slot:插槽名 =》 简化 #插槽名
语法:
- 多个slot使用name属性区分名字
<div class="dialog">
<div class="dialog-header">
<!-- 一旦插槽起了名字就是具名插槽,只支持定向分发 -->
<slot name="head"></slot>
</div>
<div class="dialog-content">
<slot name="main"></slot>
</div>
<div class="dialog-footer">
<slot name="footer"></slot>
<!-- <button>按钮</button> -->
</div>
</div>
- template配合v-slot:名字来分发对应标签
<MyDialog>
<!-- 用template标签包裹 -->
<template v-slot:head>
<div>标题</div>
</template>
<template v-slot:main>
<div>内容</div>
</template>
<template #footer>
<button>确认</button>
<button>删除</button>
</template>
</MyDialog>
作用域插槽
是插槽的一个传参语法;定义slot插槽的同时,是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用
场景:封装表格组件
步骤:
- 给slot标签,以添加属性的方式传值
- 所有属性都会被收集到一个对象中
- template中,通过
#插槽名="obj"接收
// MyTable.vue
<td>
//定义插槽的同时可以传值
<slot :row="item" msg="测试数据"></slot>
// 会把所有的属性添加到一个对象中
</td>
// App.vue中
<div>
<MyTable :data="list">
//通过template,#插槽名="变量名" 接收
<template #default='obj'>
<button @click="del(obj.row.id)">删除</button>
//{{obj}}
</template>
</MyTable>
<MyTable :data="list2">
<template #default='{row}'>
//用解构赋值
<button @click="read(row)">查看</button>
</template>
</MyTable>
</div>
单页应用程序&路由介绍
单页:所有功能在一个html页面上实现,性能高,开发效率高,用户体验好;学习成本高,首屏加载慢,SEO(搜索引擎优化)差
应用场景:系统类网站、内部网站、文档类网站、移动端站点
- Vue中的路由: 路径和组件的映射关系;根据路由就能知道不同路径的,应该匹配渲染哪个组件
路由的基本使用
VueRouter (插件)
作用:修改地址栏路径时,切换显示匹配的组件
使用(5+2)
Vue2 Vuerouter3.x Vuex3.x
Vue3 Vuerouter4.x Vuex4.x
- 5个基础步骤
- 2个核心步骤
-
创建需要的组件(views目录),配置路由规则
-
配置导航,配置路由出口(路径匹配的组件显示的位置)
路由目录存放问题(组件分类)
(为什么要把路由放到views中)
- src/views
- 页面组件:页面展示,配合路由用
- src/components
- 复用组件:展示数据,常用于封装复用
路由模块封装
拆分模块,利于维护
@指代src目录,绝对路径
用router-link替代a标签
vue-router提供的全局组件,用于替换a标签
语法:<router-link to="/路径值"></router-link>
优点:
- 能跳转
- 能高亮-router link自动给当前导航添加了两个高亮类名
- router-link-active (模糊匹配)以它开头即可匹配
- router-link-exact-active(精确匹配)
自定义匹配类名
声明式导航-跳转传参
目标:在跳转路由时,进行传值
- 查询参数传参
更适合传多个参数
- 语法:
to="/path?参数名=值&参数名2=值"
- 对应页面组件接收传递过来的值
$route.query.参数名
// home.vue
<div class="hot-link">
热门搜索:
<router-link to="/search?key=黑马程序员">黑马程序员</router-link>
<router-link to="/search?key=前端培训">前端培训</router-link>
<router-link to="/search?key=如何成为前端大牛">如何成为前端大牛</router-link>
</div>
// search.vue
<p>搜索关键字: {{ $route.query.key }} </p>
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取
console.log(this.$route.query.key);
}
- 动态路由传参
简洁,适合传单个参数
- 配置动态路由
- 配置导航链接
to="/path/参数值"
- 对应页面组件接收传递过来的值
$route.params.参数名
// router/index.js
{ path: '/search/:words', component: Search }
// home.vue
<router-link to="/search/黑马程序员">黑马程序员</router-link>
// search.vue
<p>搜索关键字: {{ $route.params.words }} </p>
动态路由参数可选符
路由重定向
重定向->匹配path后,强制跳转path路径
场景:一般网页打开的根路径页面为空白,需要强制跳转到一个有内容展示的页面
- 语法:
path:匹配路径,redirect:重定向到的路径
vue路由404
vue模式设置
一旦采用history模式,地址栏没有#,需要后台配置访问规则
编程式导航-基本跳转
点击按钮跳转如何实现?
语法:
- path路径跳转(更适合传参)
- name命名路由跳转(适合path路径长的场景)
注:需要给路由起名
编程式导航-路由传参
- path路径跳转传参(query传参)
完整写法更适合传参
- name命名路由跳转传参
声明式导航和编程式导航的区别
- 声明式导航就是
<router-link>
标签,是直接渲染到页面的,比如a链接。是通过router-link的to属性到达对应的组件中。语法:<router-link :to="...">
,这时就面临一个问题,要让这个组件显示到哪里,这是就要用到<router-view>
标签,来达到想要的结果。- 编程式导航
router.push({})
是用在js处理逻辑后需要页面跳转,比如点击button按钮跳转。是封装在API方法里的,需要用到时调用方法即可。
案例 跳转详情页传参
- 查询参数传参?参数=参数值 =》this.$route.query.参数名
- 动态路由传参 改造路由=》/路径/参数 =》this.$route.params.参数名
组件缓存 keep-alive
问题:从面经 点到详情页,又点返回,数据重新加载了 -》希望回到原来的位置
原因:路由跳转后,组件被销毁了,返回回来后组件又被重建了,所以数据被重新加载了
keep-alive是vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们。keep-alive是一个抽象组件,它自身不会渲染成一个DOM元素,也不会出现在父组件链中
优点:在组件切换过程中,把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性
- keep-alive的属性
- include:组件名数组,只有匹配的组件会被缓存
- exclude:组件名数组,任何匹配的组件都不会被缓存
- max :最多可以缓存多少组件实例
<keep-alive :include="['LayoutPage']">
//包裹了keep-alive,不加任何属性,一级路由匹配的组件都会被缓存-->
<router-view></router-view>
</keep-alive>
- keep-alive 触发两个生命周期函数
- 一旦组件缓存了,组件不会执行created和mounted钩子
- activated 当组件被激活(使用)的时候触发->进入页面触发
- deactivated 当组件不被使用的时候触发 ->离开页面触发
vue-cli 自定义项目
ESlint代码规范
写代码的约定规则
解决ESlint与格式化工具prettier冲突:
https://blog.youkuaiyun.com/Leo_zjk/article/details/121094901
Vuex
Vuex是一个vue的状态管理工具,状态就是数据,即vuex是插件,可以帮我们管理vue通用的数据(多组件共享的数据)
使用场景:
- 某个状态在很多个组件来使用(个人信息)
- 多个组件共同维护一份数据(购物车)
优势:
- 共同维护一份数据,数据集中化管理
- 响应式变化
- 操作简洁(vuex提供了以下辅助函数)
验证
// 验证路由配置成功
// console.log(this.$router)
// 验证vuex配置成功
// console.log(this.$store)
state状态
- 提供数据:
state写成函数形式,里面return一个对象也可以
- 使用数据
- 通过store直接访问
- 通过辅助函数(简化)
原:
- mapState是辅助函数,帮助我们把store中的数据自动映射到组件的计算属性中
mutations
目标:明确vuex同样遵循单向数据流,组件中不能直接修改仓库中的数据
- 利用mutations可以修改state的数据
- 定义mutation对象,对象中存放修改state的方法
- 组件methods中提交调用mutation
this.$store.commit('addCount')
- mutation可以传递参数
this.$store.commit('xxx',参数)
- 提供mutations函数
- 页面中提交调用mutation
<template>
<button @click="handleAdd(1)">值+1</button>
</template>
<script>
methods: {
handleAdd(n) {
this.$store.commit('addCount', n)
// console.log(this.$store.state.count)
}
</script>
mutation参数只能有一个,如果需要多个,包装成一个对象
- mapMutations,把位于mutations中的方法提取出来,映射到组件methods中
actions
目标:处理异步操作
mutations必须是同步的(便于监测数据变化,记录调试)
// /store/index.js 文件
// actions处理异步
// 不能之间操作state,操作state,还是需要commit mutation
actions: {
// context 上下文,此处未分模块,可以当成store仓库
// context.commit('mutation名字',额外参数)
changeCountAction(context, num) {
// 这里是setTimeout模仿异步,真实场景中是发请求
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
}
// Son1.vue
<button @click="handleChange()">一秒后修改成666</button>
handleChange() {
// this.$store.dispatch('action名字',额外参数)
this.$store.dispatch('changeCountAction', 666)
}
mapActions
getters
类似于计算属性,有时需要从state中派生出一些状态,会用到getters
例如:
-
定义getters
-
访问getters
- 通过store访问getters
{{$store.state.filterList}}
- 通过辅助函数mapGetters映射
- 通过store访问getters
模块module(进阶语法)
vuex使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得复杂时,store对象就有可能变得相当臃肿。(项目太大时,vuex难以维护)
- 模块拆分
访问模块中state的访问语法
//测试访问模块中的state,原生
<div>{{ $store.state.user.userInfo.uname }}</div>
<div>{{ $store.state.setting.theme }}</div>
//测试访问模块中的state,简写
computed: {
...mapState(['count', 'user', 'setting']),
...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc'])
}
访问模块中getters的访问语法
// 测试访问模块中的getters,原生
<div>{{ $store.getters['user/UpperCaseName'] }}</div>
// 测试访问模块中的getters,简写
<div>{{UpperCaseName}}</div>
computed: {
...mapGetters('user', ['UpperCaseName'])
},
掌握模块中mutation的调用语法
默认模块中的mutation和actions会被挂载到全局,需要开启命名空间,才会挂载到子模块
mapState mapGetters 都是映射属性
mapMutations mapActions都是映射方法
掌握模块中action的调用语法
购物车案例
json-server
json-server
基于json-server工具,准备后端接口服务环境
- 安装全局工具 json-server
npm i json-server -g // yarn global add json-server
- 代码根目录新建一个db目录
- 将index.json移入db目录
- 进入db目录,执行命令,启动后端接口服务
json-server --watch index.json