第一天
什么是vue
vue是一个用于构建用户界面的渐进式框架。
创建vue实例的步骤
- 准备容器
- 引包(官网)-- 开发版本 / 生产版本
- 创建Vue实例 new Vue()
- 指定配置项 el data => 渲染数据
- el => 通过选择器指定挂载点
- data => 提供数据
语法
- 插值表达式{{}}
- 核心特征:响应式。数据改变,视图自动更新
key的作用
v-for的默认行为会尝试原地修改元素(就地复用)。
key是给元素添加唯一标识,便于Vue进行列表项的正确排序复用。
key只能是字符串或者数字,具有唯一性。不推荐index
第二天
指令
常用的指令
- v-on
- v-bind
- v-show
- v-html
- v-if
- v-else-if
- v-else
- v-for
- v-model
- v-once
- v-pre
- v-text
- v-cloak
- v-slot
指令修饰符
通过**.**指明一些指令后缀,不同后缀封装了不同的处理操作。
按键修饰符
@keyup.enter 键盘回车监听
v-model修饰符
v-model.trim
v-model.number
事件修饰符
@事件名.stop
@事件名.prevent
v-bind操作class
语法: :class="对象/数组"
1.绑定的值是对象
// 键就是类名,值是布尔值。值为true才有这个类
<div :class="{类名1:布尔值,类名2:布尔值}"></div>
2.绑定的值是数组
// 绑定多个类名
<div :class="[类名1,类名2]"></div>
v-bind操作style
语法 :style="样式对象"
<div :style="{css属性1: css属性值, css属性2:css属性值2}"></div>
v-model原理
提供数据的双向绑定。$event:获取事件的形参
原理:本质上就是一个语法糖。例如input输入框:就是value属性和input事件的合写
表单类组件封装 & v-model简化代码
.sync修饰符
作用:可以实现子组件和父组件数据的双向绑定,简化代码
特点:props属性名可以自定义,非固定的value
场景:封装弹窗类的基础组件,visible属性 true显示 false隐藏
本质:就是 :属性名 和 @update:属性名 合写
步骤:
- 组件属性加上.sync修饰符
- 子组件中要修改传进来的props时,调用this.$emit("update:属性名", value)
自定义指令directive
全局注册
<input type="text" v-focus />
Vue.directive('focus',{
// inserted:指令所在元素挂载完成后执行
inserted(el){
// el 是指令所绑定的元素
el.focus()
}
})
局部注册
<input type="text" v-focus />
export default {
directives: {
focus: {
inserted(el){
el.focus();
}
}
}
}
指令的值(传值)
<div v-color="'red'">我是div</div>
Vue.directive('color',{
// binding.value 就是指令的值
// 元素渲染完成会触发
inserted(el, binding){
el.style.color = binding.value
},
// 元素更新时候会触发
update(el, binding){
el.style.color = binding.value;
}
})
计算属性computed
基于现有数据,计算出来的新属性。依赖的数据变化,自动重新计算。
使用起来和普通的属性一样。{{ 计算属性 }}
缓存特性
默认写法
计算属性的默认写法,只能读取访问,不能修改。
设置this.fullName 为 某某值 时会报错
computed: {
fullName(){
return this.firstName + this.lastName
}
}
完整写法
既能读取,又能修改。
设置this.fullName为某某值的时候,不会报错。能够赋值成功
computed: {
fullName:{
// 读取
get(){
return this.firstName + this.lastName
},
// 设置,入参为新值
set(value){
this.firstName = value.slice(0,1);
this.lastName = value.slice(1)
}
}
}
侦听器watch
监听数据变化,执行一些业务逻辑或者异步操作
简单写法
简单数据类型,直接监视
new Vue({
data: {
words: '苹果',
obj:{
words: '苹果'
}
},
watch: {
// 该方法在数据变化时候,会自动执行
// 简单类型的直接使用属性名
words(newValue, oldValue){
// 执行业务逻辑操作
},
// 引用类型的要找到对于的监听对象
"obj.words"(newValue, oldValue){
// 执行业务逻辑操作
}
}
})
完整写法
添加额外配置项
1.deep:true 对复杂类型进行深度监视
2.immediate: true 初始化立即执行一次handler方法
new Vue({
data: {
obj:{
words: '苹果',
lang: 'en_us'
}
},
watch: {
// 该方法在数据变化时候,会自动执行
obj: {
deep: true, // 深度监视,对象里的数据发生变化都能侦听到
immediate: true, // 初始化时候立即执行一次handler方法
handler(newValue, oldValue){
// 执行业务逻辑操作
},
}
}
})
第三天
生命周期
生命周期钩子
- 创建阶段:beforeCreate / created
- 挂载阶段:beforeMount / mounted
- 更新阶段:beforeUpdate / updated
- 销毁阶段:bedoreDestory / destoryed
发送初始化渲染请求,最早不能早于created。mounted之后才能操作dom
脚手架创建项目
方式一
全局安装vue: npm i @vue/cli -g
查看vue版本:vue --version
创建项目架子:vue create project-name
启动项目:npm run serve
方式二
执行命令:npm create vue@latest
搭配的打包工具是vite
示例组件中使用的是组合式API,而不是选项式API
组件化开发
一个页面可以拆分成一个个的组件,每个组件都有自己独立的行为、样式、结构。
组件的注册使用
局部注册
import 组件对象 from '.vue文件';
export default {
components: {
'组件名': 组件对象
}
}
全局注册
在main.js中调用Vue.component注册全局组件
import 组件对象 from '.vue文件';
Vue.component('组件名', 组件对象)
组件样式冲突 -- scoped
scoped原理:
- 给当前组件模板的所有元素,都会被添加上一个自定义属性:data-v-hash值。利用这个hash值可以区分不同的组件。
- css选择器后边,会自动处理添加上了属性选择器:如div[data-v-hash值]
最终效果:必须时当前组件的元素,才会有这个自定义属性,才会被这个样式作用到。
data是一个函数
一个组件的data必须时一个函数,保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次data函数,得到一个新的对象。
props讲解
什么是props
props是组件上注册的一些 自定义属性,向子组件传递数据。单项数据流。可以传递任意数量、任意类型的数据。
使用一个对象绑定多个props
使用没有参数的v-bind。
// 以下两种方式完全等价
<BlogPost v-bind="post" />
<BlogPost :id="post.id" :title="post.title" />
export default {
data() {
return {
post: {
id: 1,
title: 'My Journey with Vue'
}
}
}
}
props校验
①非空校验:为true的时候必传。
②类型校验:可设置单个,多个的话可设置数组。
③设置默认值:基本类型直接设置;引用类型的为工厂函数。解析到的为undefined时候生效。
④自定义校验:validate(value){ ... return 是否校验通过 }
export default {
props: {
// 基础类型检查
//(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值。传来的为undefined或者未传递,都会被改为default的值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
// 在 3.4+ 中完整的 props 作为第二个参数传入
propF: {
validator(value, props) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
}
}
props和data
相同点:两者都可以给组件提供数据。
不同点:data是组件自己的,可以随意改。props是外部的,遵循单向数据流,不能直接改。
组件通信
组件之间的数据传递。
props和$emit
第一步:props父传子
父中给子添加属性传值,子props接收,使用。
<ChildComponent :item='item' @sonClick="sonclick" />
第二步: $emit子触发父
通过$emit,向父组件发送消息通知。父添加消息监听,实现处理逻辑
this.$emit('sonClick', params)
// 子组件
<template>
<div>
<div>{{ title }}</div>
<button @click="handleClick">click</button>
</div>
</template>
<script>
export default {
props: ['title'],
methods: {
handleClick(){
this.$emit('appClick', {id: 1});
}
}
}
</script>
// 父组件
<template>
<div>
<HeaderVue
:title="title"
@appClick="appClick"
></HeaderVue>
</div>
</template>
<script>
import HeaderVue from './components/HeaderVue.vue';
export default {
name: 'App',
data(){
return {
title: 'appTitle'
}
},
components: {
HeaderVue: HeaderVue
},
methods: {
appClick(params){
console.log('appClick', params);
}
}
}
</script>
event bus事件总线
1.创建一个都能被访问到的事件总线(空的vue实例)
创建一个新的Vue实例后,使用的时候有两种处理方式(推荐第二种方式,减少引入的步骤):
1)把Bus导出,在组件中引入Bus使用 => 需新建一个js文件
2)把Bus挂载到Vue的prototype上,在组件中通过this.Bus使用 => 直接在main.js文件操作
import Vue from 'vue';
const Bus = new Vue();
// 方式1
export default Bus;
// 方式2
Vue.prototype.Bus = Bus;
2.组件接收方,监听Bus实例的事件($on)
组件created钩子函数里订阅消息,并触发回调函数。
// 方式1
import Bus from 'Bus文件路径';
created(){
Bus.$on('sendMsg', (msg)=>{ ...处理逻辑 })
}
// 方式2
created(){
this.Bus.$on('sendMsg', (msg)=>{ ...处理逻辑 })
}
3.组件发送方,触发Bus实例的事件($emit)
触发接收方订阅的消息,可以传入参数。
// 方式1
import Bus from 'Bus文件路径';
methods: {
handleClick(){
Bus.$emit('sendMsg', params)
}
}
// 方式2
methods: {
handleClick(){
this.Bus.$emit('sendMsg', params)
}
}
provide & inject
跨层级共享数据,要有嵌套关系,子孙后代组件。
1)父组件provide提供数据
注意:provide提供的数据,推荐提供引用类型的数据(响应式)。基本类型的是非响应式的(父组件数据改了,子孙组件不会重新渲染)
export default{
provide(){
return {
color: this.color, // 非响应式
userInfo: this.userInfo // 响应式
}
}
}
2)子/孙组件inject取值使用
export default{
inject: ['color', 'userInfo'],
created(){
console.log( this.color, this.userInfo )
}
}
vuex
依赖相关
支持less
npm install less less-loader -D (安装到开发环境)
第四天
ref和$refs
ref获取dom元素
查找范围:当前组件内,更为准确稳定。当前组件存在多个相同ref的dom元素,获取时候,后者覆盖前者。
1)dom元素上绑定ref属性
2)通过this.$refs.ref属性 获取dom元素
// 添加ref属性
<div ref="divRef"></div>
// 获取dom元素
mounted(){
let div = this.$refs.divRef;
}
ref获取组件
1)目标组件加上ref属性
<BaseInput ref="baseInput" />
2)通过this.$refs.xxx,获取目标组件,然后可以调用目标组件内部的方法。
this.$refs.baseInput.组件方法()
vue异步更新、$nextTick
问题:代码执行之后,dom没有同步更新?
原因:vue 是 异步更新 DOM(提升性能)
解决办法:$nextTick:更DOM更新完成之后,才会触发执行此方法里的函数体。
// this.$nextTick(函数体)
this.$nextTick( ()=>{
this.$refs.inp.focus()
})
插槽
让组件内部的一些结构支持自定义。分为 默认插槽 和 具名插槽 两类。
默认插槽
1)先在组件内使用slot标签占位
<div>
下边是插槽
<slot></slot> // 渲染的时候,这儿会被组件标签内元素替换掉
</div>
2)使用组件时,传入具体标签内容
<ChildComponent>我是插槽的内容</ChildComponent>
后备内容(默认值)
slot标签内放置默认内容
<div>
下边是插槽
<slot>我是插槽的默认内容</slot>
</div>
具名插槽
多个slot使用name属性区分名字。插槽指定name之后,就是具名插槽,只支持内容定向分发。
1)组件内预留多个slot插槽,设置不同的name属性
<template>
<div>
<h3>标题</h3>
<slot name="header"></slot>
<h3>内容</h3>
<slot name="body"></slot>
</div>
</template>
2)使用组件时候,组件标签内通过template标签 v-slot:插槽名 或者 #插槽名 来指定对于的插槽位置
<SlotA>
<template v-slot:header>我是header</template>
<template #body>我是body</template>
</SlotA>
作用域插槽
定义slot插槽的同时是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用。
通过作用域插槽 传值绑定。
1)给slot标签,以添加属性名的方式传值
<slot :id="id" msg="msg"></slot>
2)所有添加的属性值都会被收集到一个对象中
{id: id, msg: msg}
3)在template中,通过 #插槽名=obj 接收,默认插槽名为default
// 此处接收到的obj就是上边的对象
<SlotComponent #default='obj'></SlotComponent>
路由 vueRouter
路由:路径和组件的映射关系。
vueRouter的使用(5+2)
5个基础步骤:
1)下载: vue2 => vueRouter 3.x vuex 3.x;vue3 =>vueRouter4.x vuex4.x
npm install vue-router@3.6.5
2)引入
import VueRouter from 'vue-router';
3)安装注册
Vue.use(VueRouter)
4)创建路由对象
const router = new VueRouter()
5)注入,将路由对象注入到new Vue实例中,建立关联
new Vue({
render: h => h(App),
router
}).$mount('#app')
2个核心步骤:
1)创建需要的组件(view目录),配置路由规则
const router = new VueRouter({
mode: 'history', // 路由模式:history、hash
linkActiveClass: '类名', // router-link 模糊匹配的自定义类名
linkExactActiveClass: '类名', // router-link 精准匹配的自定义类名
routes: [
// 支持属性: path、 name、 component、 redirect
{ path: '/', redirect: '/info' }, // 路由重定向
{ path: '/music/:id?', component: MyMusic }, // 声名式导航--动态路由(?为参数可选符)
{ path: '/info', component: MyInfo },
{ path: '/base', component: MyBase, name: 'ba' }, // 给路由起名字
{
path: 'home',
component: Home,
children:[
{ path: '路径', component: 组件 }
]
}
{ path: '*', component: NoutFound }, // 404路由
]
})
2)配置导航,配置路由出口router-view(路径匹配的组件显示的位置)
声名式导航 - 导航链接
vue-router提供了一个全局组件router-link(取代a标签)
1)能跳转:配置to属性指定路径,无需#
2)能高亮,默认就会提供高亮类型(class为router-link-active和router-link-exact-active),可以直接设置高亮样式
tab name
声名式导航 - 两个类名
两个类名的区别?
router-link-active
1)模糊匹配(用的多):to="my" 可以匹配/my开头的路径,如'/my/info','/my/music'
router-link-exact-active
1)精准匹配,完全相同的。
自如何定义这两个class?
在创建vueRouter实例的时候,可以自定义这两个类名:
1)linkActiveClass:自定义模糊匹配的类名router-link-active
2)linkExactActiveClass:自定义精确匹配的类名router-link-exact-active
const router = new VueRouter({
routes: [ ... ],
linkActiveClass: '类名'
linkExactActiveClass: '类名'
})
声名式导航 - 跳转传参
在跳转路由时,传递参数。
1)查询参数传参(适合多个参数)
① 传参:to="/path?参数名=值"
② 取值:this.$route.query.参数名
2)动态路由传参(适合单个参数)
① 配置动态路由:{ path: '/search/:参数名', component: Search }
默认参数名是必传的,否则渲染不到对于的组件。可配置参数可选符,这样参数就非必传了。
动态路由参数可选符:参数名后加?。{ path: '/search/:参数名?', component: Search }
② 传参:to="/search/参数值"
③ 取值:this.$route.params.参数名
路由 - 重定向
语法:{ path: '匹配路径', redirect: '重定向路径' }
路由 - 404
位置在路由规则最后边。
语法: { path: '*', component: NotFound }
路由导航守卫 - 全局前置守卫
1)所有的路由在真正被访问之前(解析渲染对应组件页面前),都会先经过全局前置守卫
2)只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容
// to: 往哪儿去,到哪去的路由信息对象
// from: 从哪儿来,从哪儿来的路由信息对象
// next(): 是否放行
// 1)如果next()调用,就是放行
// 2)next(路径) 拦截到某个路径页面
// 需要鉴权的页面路由
const authUrls = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
console.log(to, from, next)
if (!authUrls.includes(to.path)) {
next()
return
}
const token = store.getters.token
if (token) {
next()
} else {
// 没token
next('/login')
}
})
路由 - 模式设置
vueRouter实例中配置mode:
const router = new VueRouter({
mode: 'history',
routes: [ ... ],
linkActiveClass: '类名'
linkExactActiveClass: '类名'
})
- hash模式(默认)
- history模式(常用):需要后台配置访问规则
二级路由
在路由规则中配置children属性。其余配置与一级路由一致。
设置默认的二级路由:通过redirect重定向来实现
路由返回
this.$router.back()
编程式导航 - 基本跳转
编程式导航:用js代码实现跳转。
① path路径跳转
this.$router.push('路径')
或者
this.$router.push({ path: '路径' })
② name命名路由跳转(适合path路径长的场景)
1)配置路由时,给路由规则起一个名字 :name
{ path: 'article', name:'ar', component: MyArticle }
2)跳转时候,通过指定name来实现。
this.$router.push({
name: 'ar'
})
编程式导航 - 路由传参
同声名式导航,都支持查询参数和动态路由传参。
查询参数传参
// 完整版,更适合传参、
this.$router.push({
path: '路径',
query: query
})
// query会被拼接到页面url里
效果等同于:
// 简写版
this.$router.push('路径?参数键值对')
取值:
this.$route.query
动态路由传参
this.$router.push({
path: '路径/动态参数'
})
// 页面url里会包含参数信息
取值:
this.$route.params.动态参数名。
params传参
this.$router.push({
path: '路径',
params: params
})
// query不会被拼接到页面url里
取值:
this.$route.params
页面跳转方式
1)push => 在历史中push一个
2)replace => 替换当前的路由,之前的历史不会改变
组件缓存keep-alive
keep-alive组件:是vue内置的一个组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive是一个抽象组件,它自身不会被渲染曾dom元素,也不会出现在父组件链中。
keep-alive的有点:减少加载时间和性能消耗,防止重复渲染DOM,提高用户体验性。
<keep-alive :includes="['collectPage']">
<router-view></router-view>
</keep-alive>
keep-alive的三个属性:
1)include:组件名数组,包括的组件
2)exclude:组件名数组,不包含的组件
3)max:最多可以缓存多少组件
被缓存的组件会多两个生命周期函数:
1)activated:路由组件被激活时候触发
2)deactivated:路由组件失活时触发
Eslint代码规范
代码规范:一套写代码的约定规则
Javascript Standrad Style 规范说明:standardjs.com/rules-zhcn
手动修复
1)Eslint规则表:zh-hans.eslint.org/docs/latest…
自动修复(vscode)
1)安装eslint插件
2)添加配置项
vscode设置 => 打开文件 => 添加如下配置
// 保存的时候,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
"source.fixAll": true
},
// 保存代码,不自动格式化
"editor.formatOnSave": false
第五天
vuex
vuex概述
vuex是一个vue的状态管理工具(主要是多组件共享的数据)。
优势:
1)数据集中化管理。
2)响应式变化
3)操作简洁(vuex提供了一些辅助函数)
创建仓库
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store();
// 导出
export default store;
// main.js
import store from '@/store'
new Vue({
render: h => h(App),
store // 导入挂载
}).$mount('#app')
// 访问仓库
this.$store
核心概念
state状态
state里放置的都是公共数据。
提供数据
// 提供数据
new Vue.Store({
state: {
title: '仓库标题',
count: 1
}
})
访问数据
1)通过store直接访问
this.$store.state.count
2)通过辅助函数访问
mutations
vuex同样遵循单向数据流,组件中不能直接修改仓库的数据。
mutations必须是同步的(便于检测数据变化,记录调试)
可以配置strict:true开启严格模式,来提示vuex使用过程中的报错(上线时候需要移除,会消耗性能)。
new Vue.Store({
// 开启严格模式
strict: true
})
要修改store中的数据,要通过mutations来处理。
1)定义mutations对象,对象中存放修改state的方法
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
addCount (state, value) {
state.count += value
}
}
})
2)组件中通过commit提交调用mutations
调用mutations并传参(也叫提交载荷payload)。
this.$store.commit('addCount', 2)
actions
处理异步操作。
1)提供actions方法
actions里不能直接操作state,要改state还是需要commit mutations
context:上下文,也就是store
actions: {
setAsyncCount (context, num){
setTimeout(()=>{
context.commit('changeCount', num)
}, 1000)
}
}
2)页面里dispatch调用actions的异步方法
this.$store.dispatch('setAsyncCount', 100)
getters
getter类似于计算属性:只有get,没有set。
除了state之外,有时我们还需要从state中派生出一些状态,这些状态都是依赖state的,此时就会用到getters
1)定义getters
getters函数接收两个参数:
1)state: 当前的state
2)getters:当前的getters
getters:{
// state就是store中的数据
filterList(state, getters){
// getters必须要有一个返回值
return state.list.filter(item => item > 5)
}
}
2)使用getters
① 通过$store访问
this.$store.getters.filterList
②通过辅助函数mapGetters映射
module(进阶语法)
vuex使用的是单一状态树,应用所有的状态都会集中到一个比较大的对象里,当应用变得比较复杂的时候,store对象就会变得比较臃肿,难以维护。
模块拆分
创建对应的模块文件,例如:user模块:store/modules/user.js
子模块的状态,还是会挂到根级别的state中,属性名就是模块名
// user.js
// state可以直接定义成对象
const state = {
name: 'jerry'
}
// state也可以通过函数生成(官方更推荐)
function state () {
return {
name:'jerry'
}
}
const actions = {}
const mutations = {}
const getters = {}
export default {
state,
getters,
actions,
mutations
}
store的根文件里通过modules将模块文件整合在一起
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
modules:{
user
}
})
export default store
访问state
① 直接通过模块名访问:this.$store.state.模块名.xxx
② 通过mapState映射
1)默认根级别的映射同mapState里的:mapState(['xxx'])
2)子模块的映射:
方式一:映射到模块层级,拿到模块中全部的数据
computed: {
...mapState(['user'])
}
// 组件使用
user.name
方式二:直接映射模块中的数据:mapState('模块名',['xxx'])
需要注意的是,要开启命名空间(让模块有名字)
export default{
namespaced: true, // 开启命名空间
state,
actions,
mutations,
getters
}
// 组件里
computed: {
...mapState('user', ['name'])
}
访问getters
① 直接通过模块名访问:this.$store.getters['模块名/xxx']
注意:这个跟state里有区别
② 通过mapGetters映射
跟访问state一样
方式一:默认根级别的映射:mapGetters(['xxx'])
方式二:子模块的映射:mapGetters('模块名',['xxx']) -- 需要开启命名空间
访问mutations
模块中的mutations和actions默认会被挂载到全局,需要开启命名空间,才会挂载到子模块。
① 直接通过模块名访问:this.$store.commit('模块名/xxx', 参数)
② 通过mapMutations映射
方式一:默认根级别的映射:mapMutations(['xxx'])
方式二:子模块的映射:mapMutations('模块名',['xxx']) -- 需要开启命名空间
访问actions
同访问mutations逻辑。
① 直接通过模块名访问:this.$store.dispatch('模块名/xxx', 参数)
② 通过mapActions映射
方式一:默认根级别的映射:mapActions(['xxx'])
方式二:子模块的映射:mapActions('模块名',['xxx']) -- 需要开启命名空间
如何跨模块访问?
dispatch或者commit的时候,传入第三个参数:{ root: true },访问全局内容
dispatch('模块名/方法名', 参数, { root: true })
commit('模块名/方法名', 参数, { root: true })
辅助函数
mapState
把state中的状态提取出来,映射到组件computed中。
mapState是辅助函数,帮助我们把store中的数据自动映射到组件的计算属性中去。得到的返回值格式类似:
{ count () { return this.$store.state.count }}
import { mapState } from 'vuex';
new Vue({
computed: {
// 可以直接在组件里使用count和title
...mapState(['count','title'])
}
})
mapMutations
把mutations中的方法提取出来,映射到组件 methods 中。
1)mutations方法映射到组件methods里
import { mapMutations } from 'vuex';
methods: {
// 等同于:
// addCount(n){ this.$store.commit('addCount',n)}
...mapMutations(['addCount'])
}
2)组件里调用方法,可传参
<input type="text" @input="addCount($event.target.value)" />
mapActions
把actions里的方法提取出来,映射到组件的methods对象里。
1)actions方法映射到组件methods里
import { mapActions } from 'vuex'
methods: {
// 等同于
// changeCount(params){ this.$store.dispatch('changeCount', params)}
...mapActions(['changeCount'])
}
2)组件里调用方法,可传参
<button @click="changeCount('100')">异步增加100</button>
mapGetters
把getters里的方法提取出来,映射到组件的computed对象里。
1)getters方法映射到组件computed里
import { mapGetters } from 'vuex';
computed: {
// 等同于
// filterList(){ return this.$store.getters.filterList}
...mapGetters(['filterList'])
}
2)组件里调用方法
<div>大于5的有:{{ filterList }}</div>
mixins
混入(逻辑复用)。
此处编写的就是Vue实例的配置项,通过mixins语法,可以直接混入到组件内部。
例如:data、methods、computed、生命周期等等
注意点:
1)如果此处和组件内,提供了同名的data或者methods等,组件内部的优先级更高。
2)如果都编写了生命周期函数,则mixins里和component里的都会执行
// mixins/confirmLogin.js
expport default {
data(){
return {
title: '标题'
}
},
methods: {
sayHi () {
console.log('你好')
}
}
}
// 组件内
export default {
// mixins之后,组件内部就可以直接使用mixins文件里的数据和方法了
mixins: [confirmLogin],
data(){
return {
...
}
},
created(){
this.sayHi();
console.log(this.title)
},
methods: {
...
}
}
第六天-项目实战
常用组件库:
pc:element-ui(vue2)、element-plus(vue3)、ant-design-vue(vue2/vue3)
mobile:vant-ui(有2、3、4版本,2-vue2,3-4-vue3)、mint-ui(饿了么)、cube-ui(滴滴)
vant-ui:分为 全部导入 和 按需导入(更推荐) 两种模式。
代码整理:
1)按需导入组件时,可以将使用组件的逻辑都放到一个文件里,然后在main.js里引入即可。
// vant-ui.js
import Vue from 'vue'
import { Button, Switch } from 'vant'
import 'vant/lib/index.css'
Vue.use(Button)
Vue.use(Switch)
项目中的vw适配
vant-contrib.gitee.io/vant/v2/#/z…
基于postcss插件实现项目的vw适配:postcss-px-to-viewport
// postcss.config.js 添加如下配置
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375,
},
},
};
vant组件使用记录
Tabbar & TabbarItem
1)配置路由模式
Tabbar加router属性。TabbarItem加to属性。会默认高亮tab
Toast
两种使用方式
① 导入调用(组件内和非组件中都可以)
import { Toast } from 'vant'
Toast('提示内容')
② 通过this直接调用(必须在组件内)
本质:将方法、注册挂在到了Vue原型上:Vue.prototype.$toast=xxx
import { Toast } from 'vant'
Vue.use(Toast)
this.$toast('提示内容')
封装request
axios: www.axios-http.cn/
配置拦截器
import store from '@/store'
import axios from 'axios'
import { Toast } from 'vant'
// 创建 axios 实例,将来对创建出来的实例,进行自定义配置
// 好处:不会污染原始的 axios 实例
const instance = axios.create({
baseURL: 'http://cba.itlike.com/public/index.php?s=/api/',
timeout: 5000
})
// 自定义配置 - 请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 开启loading,禁止背景点击 (节流处理,防止多次无效触发)
Toast.loading({
message: '加载中...',
forbidClick: true, // 禁止背景点击
loadingType: 'spinner', // 配置loading图标
duration: 0 // 不会自动消失
})
// 只要有token,就在请求时携带,便于请求需要授权的接口
const token = store.getters.token
if (token) {
config.headers['Access-Token'] = token
config.headers.platform = 'H5'
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么 (默认axios会多包装一层data,需要响应拦截器中处理一下)
const res = response.data
if (res.status !== 200) {
// 给错误提示, Toast 默认是单例模式,后面的 Toast调用了,会将前一个 Toast 效果覆盖
// 同时只能存在一个 Toast
Toast(res.message)
// 抛出一个错误的promise
return Promise.reject(res.message)
} else {
// 正确情况,直接走业务核心逻辑,清除loading效果
Toast.clear()
}
return res
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
// 导出配置好的实例
export default instance
打包发布
打包的作用
1)语法降级
2)将多个文件压缩成一个文件
3)less、sass、ts等语法的解析
4)...
配置相对路径
打包后的文件要部署到服务器的根目录,否则资源会加载不到(html代码里引用的资源都是'/'开头的,绝对路径)。打包后的文件直接在浏览器访问时加在不出来的。
若是想要配置成相对路径(可在浏览器直接打开):
1)在vue.config.js里添加如下配置:
publicPath: './'
2)重新打包。
直接浏览器打开打包出的html文件,可以加载出来。部署的时候,可以放到子目录,也能正常加载。
打包优化
路由懒加载
1)异步组件改造
const Detail = () => import('@/views/detail');
const Pay = () => import('@/views/pay');
2)路由中应用
const router = new VueRouter({
routes: [
{ path: '/detail', component: Detail },
{ path: '/pay', component: Pay }
]
})
第七天 - vue3
vue3的优势:
1)更容易维护:组合式API、更好的ts支持。(Vue2为选项式API)
2)更快的速度:重写diff算法、模板编译优化、更高效的组件初始化
3)更小的体积:按需引入、良好的TreeShaking
4)更优的数据响应式:底层为proxy(vue2为:Object.defineProperty)
| 实例对象 | vue2 | vue3 |
|---|---|---|
| vue实例 | new Vue({}) | createApp() |
| 路由实例 | new VueRouter({}) | createRouter() |
| store实例 | new Vuex.Store({}) | createStore() |
创建项目
1)node版本 >= 16
2)执行命令:npm create vue@latest
组合式API
setup函数
1)执行时机:比beforeCreated更早。
2)setup函数中拿不到this(this是undefined)
3)数据和函数,需要在setup最后return,才能在模板中使用
4)可以通过setup语法糖简化代码
reactive 和 ref
reactive作用:接受对象类型数据的参数传入,并返回一个响应式对象
ref作用:接受简单类型数据或对象类型的参数入参,返回一个响应式对象
- 本质:是在原有传入的数据的基础上,外层包了一层对象,成了复杂类型。之后再借助reactive实现了响应式。
- 注意点:脚本中访问和修改数据,要通过**.value**。template中不需要加。
推荐:声明数据统一使用ref:既支持简单类型,又支持引用类型
<script setup>
import { ref, reactive } from 'vue';
// count被包成了一个对象
const count = ref(0)
const state = reactive({
name: 'jerry'
})
const addCount = () => count.value ++
const changeName = (name) => {
state.name = name
}
</script>
<template>
{{ count }}
<button @click="addCount">+</button>
{{ state.name }}
<button @click="changeName('zhangsan')">改成张三</button>
</template>
computed
1)计算属性中不应该有“副作用”
比如异步请求/操作dom等
2)避免直接修改计算属性的值
计算属性应该是只读的,特殊情况可以配置get、set
<script setup>
import { computed, ref } from 'vue';
// 通常用法,默认只有get
const baseList = ref([0,1,2,3,4,5,6])
const addData = () => {
baseList.value.push(baseList.value.length)
}
// 执行函数在回调中return基于响应式数据计算出的值,用变量接收
const oddList = computed(()=>{
return baseList.value.filter(i => i%2 === 0)
})
// 计算属性同样支持get set(同vue2)
const operateBaseList = computed({
get: () => baseList.value,
set: (val) => baseList.value = val
})
// 设置了计算属性的set后,才支持给计算属性赋值
operateBaseList.value = [2,3,4]
</script>
<template>
<div>{{ oddList }}</div>
<button @click="addData">添加数据</button>
{{ operateBaseList }}
</template>
watch
基本用法
作用:侦听一个或者多个数据的变化
import { watch, ref } from 'vue'
const count = ref(0)
// 监听单个数据
watch(count, (newValue, oldValue)=>{
console.log('值发生了变化')
})
// 监听多个数据
watch([count, name], ([newCount, newName],[oldCount, oldName])=>{
console.log('count或者name发生变化了')
})
immediate && deep
配置watch的第三个参数。
const info = ref({age: 20, name: 'sz'})
watch(info, (newValue, oldValue)=>{
console.log('info对象发生了变化')
}, {
immediate: true, // 进入页面立即执行一次
deep: true // 深度监视,复杂类型
})
精确侦听对象的某个属性
第一个入参为回调函数,返回要监听的具体属性
const info = ref({age: 20, name: 'sz'})
watch(
()=>info.value.age,
(newValue, oldValue)=>{ console.log('age变化了')}
)
生命周期函数
| 选项式API | 组合式API |
|---|---|
| beforeCreate/created | setup(直接在script里写) |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount(vue2为beforeDestory) | onBeforeUnmount |
| unmounted(vue2为destoryed) | onUnmounted |
<script setup>
// beforeCreate和created的相关代码:直接封装,直接调用
const getList = () => { ... }
// 一进入页面直接请求
getList()
// 同一个周期函数可以调用多次,会按照顺序依次执行
onMounted(){
console.log('mounted生命周期函数-逻辑1')
}
onMounted(){
console.log('mounted生命周期函数-逻辑2')
}
</script>
父子传递数据
1)父组件中给子组件绑定属性
2)子组件通过props接收: defineProps & defineEmits
通过defineProps“编译器宏”接收子组件传递的数据
通过defineEmits“编译器宏”触发父组件上的函数
// 父组件 子组件直接导入使用,不需要注册
<script>
import SonComponent from './components/son-compoennt.vue'
import { ref } from 'vue'
const name = ref('zhangsan')
const changeName = (value) => {
name.value = value
}
</script>
<SonComponent
:name="name"
:changeNameBySon="(value)=>changeName(value)"
/>
// 子组件
<script>
const props = defineProps({
name: String
})
// js中要通过props来访问,html中直接访问
console.log(props.name)
// defineEmits
const emit = defineEmits(['change-name-by-son'])
const changeName = () => {
emit('change-name-by-son', '王二')
}
</script>
<template>
<div>{{ name}}</div>
<button @click="changeName">子组件改变父组件name</button>
</template>
defineProps原理:
provide & inject
实现跨层级组件响应式通信。
1)顶层组件通过provide函数提供数据
provide('key', 数据或者函数)
2)底层组件通过inject函数获取数据
const key = inject('key')
// 底层组件 => bottom-com.vue
<script setup>
import { inject } from 'vue'
const userInfo = inject('userInfo')
const changeName = inject('changeUseName')
</script>
<template>
// 顶层到底层
<div>{{ userInfo.name }}</div>
// 底层到顶层
<button @click="changeName('王二')">更改name</button>
</template>
// 顶层组件
<script setup>
import BottomCom from '@/components/bottom-com.vue'
import { ref, provide } from 'vue'
const userInfo = ref({name: 'zhangsan'})
// 向底层组件提供数据
provide('userInfo', userInfo)
// 向底层组件提供一个修改自身数据的回调函数
provide('changeName', (value)=>{
userInfo.value.name = value
})
</script>
<template>
<BottomCom />
</template>
模板引用和defineExpose
模板引用
通过ref标识获取真实的dom对象或者组件实例对象。
<script>
import { onMounted, ref } from 'vue'
const ref1 = ref(null)
onMounted(()=>{
console.log(ref1.value)
})
</script>
<template>
<div ref="ref1">我是div</div>
</template>
defineExpose宏函数
1)默认情况下在**
2)可以通过defineExpose编译宏来指定哪些方法和属性允许访问。
// 子组件
<script setup>
const changeName = () => {}
const ref1 = ref(null)
const info = ref({ age: 20})
const name = ref('张三')
// defineExpose指定暴露出哪些方法和属性
defineExpose({
changeName, info
})
</script>
<template>
<div ref="ref1">{{ info.age }}</div>
</template>
// 父组件
<script setup>
const childRef = ref(null)
onMounted(()=>{
console.log(childref.value.changeName)
console.log(childref.value.info.age)
console.log(childref.value.sonName) //undefined
})
</script>
<template>
<SonComponent ref="childRef"/>
</template>
Vue3.3新特性
注意:vue版本要不低于3.3
defineOptions
defineOptions宏函数,主要是用来定义Options API的选项,可以定义任意的选项。
但是:props、emit、expose、slots除外(因为这些都有对应的defineXXX来实现)
<script setup>
defineOptions({
name: 'componentName',
inheritAttrs: false,
... //更多自定义属性
})
</script>
defineModel
实验性质的API,如果要使用的话,需要进行额外的配置(3.4版本的,不配置也可以生效):
// vite.config.js
vue({
script: {
defineModel: true
}
})
defineModel主要是针对v-model来使用的。
1)在vue2中,v-model是:value和@input组合的简写。
2)在vue3中,v-model是:modelValue和@update:modelValue的简写(写着比较麻烦)。
<Child v-model="count"></Child>
// 相当于
<Child :modelValue="count" @update:modelValue="count++"></Child>
所以出现了defineModel函数,来简化这一步骤。
// son-component.vue
<script setup>
// 获取v-model绑定的值
const modelValue = defineModel()
// 直接更改v-model绑定的父组件的值
modelValue.value++
</script>
<template>
// 或者 更改v-model绑定的父组件的值
<button @click="modelValue++">+</button>
</template>
// parent-compoennt.vue
<SonComponent v-model="count"></SonComponent>
零碎点记录
1)setup选项:允许在script里直接编写组合式API
2)组件引入后不需要注册,可直接在template中引用
3)template不再要求唯一根元素,可以存在多个。
4)script里同一个周期函数可以定义多个,不会相互冲突
第八天 -- Pinia
Pinia是Vue的最新的状态管理工具,是Vuex的替代品
相比较于Vuex:
- 提供更简单的API(去掉了mutation、modules)
- 提供符合、组合式风格的API(和Vue3新语法统一)
- 去掉了modules的概念,每一个store都是独立的模块(不用配置namespaced)
- 配合Typescript更加友好,提供可靠的类型推断
引入Pinia、创建实例
引入Pinia:
npm install pinia
创建实例并挂载到Vue上:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './app.vue'
const pinia = createPinia()
const app = create.app()
app.use(pinia)
app.$mount('#app')
创建仓库,基本使用方法
1)创建仓库:defineStore('仓库名', 回调函数)
2)定义state、 actions、 getters
组合式API模式:
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
选项式API模式(更常用,推荐使用):
// 创建仓库 store/counter.js
import { defineStore } from "pinia";
import { computed, ref } from "vue";
const useCounterStore = defineStore('counter', () => {
// ref 就是 state
const count = ref(110)
// 方法 就是 actions
const changeCount = (value) => {
count.value += value
}
// computed 就是 getters
const isOdd = computed(()=>{
return count.value % 2 === 0
})
// 暴露出相关API和状态数据
return {
count,
isOdd,
changeCount
}
})
export default useCounterStore
//组件使用仓库 App.vue
<script setup>
import useCounterStore from '@/store/counter'
const counterStore = useCounterStore()
const handleAdd = () => {
counterStore.changeCount(2)
}
</script>
<template>
<div>
<h3>son1 - {{ counterStore.count }}</h3>
<h3>是否是偶数:{{ counterStore.isOdd }}</h3>
<button @click="handleAdd">+</button>
</div>
</template>
action异步实现
编写方式:异步action函数的写法和组件中获取异步数据的写法完全一致
import { defineStore } from 'pinia'
import { ref } from 'vue'
const useCounterStore = defineStore('counter',()=>{
const count = ref(0)
const changeCount = () => {
// 编写异步逻辑
setTimeout(()=>{
// 获取数据后更改数据
count.value = 1
}, 2000)
}
})
storeToRefs方法
引入store中的数据或者计算属性,直接解构的时候,会丢失响应式。如下面的写法:
// 这种相当于在组件里重新定义了两个变量,给变量赋值。因此会丢失响应式
const { count, doubleCount } = counterStore;
为了解决上边的问题,可以引入pinia提供的函数:storeToRefs。使用方法如下:
注意:
1)当你只使用store的数据而不调用任何的action时,它会非常有用。
2)如果你要解构store中的action,则可以直接解构,不需要storeToRefs包裹。
// 它将为每一个响应式属性创建引用
// count 和 doubleCount是响应式的ref
const { count, doubleCount } = storeToRefs(counterStore)
// 如果要使用store中的action,可以直接解构
const { changeCount } = counterStore
pinia持久化
利用第三方插件:pinia-plugin-persistedstate来完成
官网:prazdevs.github.io/pinia-plugi…
1)安装
2)引入
3)注册到pinia上: pinia.use(插件名)
4)在store文件里配置:persist: true
5)可以设置缓存local还是session、缓存哪些属性、等等
pinia延伸
1.storeToRefs、toRefs、toRef有啥区别?
storeToRefs和toRefs都主要是用在解构的步骤里。
1)toRefs:
作用:
将响应式对象中的所有属性转换为单独的响应式数据,对象为普通对象,且值是关联的。
会做两件事:
① 把一个响应式对象转变为普通对象
② 对该普通对象的每个属性都做一次ref操作,这样每个属性都是响应式的
说明:
① reactive对象直接解构,解构出的属性都是非响应式的,失去了响应式能力
② 用toRefs可以将对象里的每个属性都变成响应式的
③ toRefs后,可以通过**.value**来访问或者修改值
<script setup>
import { reactive, toRefs } from 'vue';
const state = reactive({
name: 'jerry',
age: 20
})
let { age } = state;
let { name } = toRefs(state);
const changeData = () => {
// name.value = 'herry' // 响应式的
// age = 21 // 非响应式的
}
</script>
<template>
<div>
<div>{{ age }}</div>
<div>{{ name }}</div>
<button @click="changeData">改state</button>
</div>
</template>
2)storeToRefs
storeToRefs的作用基本和toRefs差不多,都是在数据解构的时候,将对象中的每个属性都变成响应式的数据。
区别就是:toRefs是在Vue3中使用的;storeToRefs是在pinia中使用的。
// 它将为每一个响应式属性创建引用
// count 和 doubleCount是响应式的ref
const { count, doubleCount } = storeToRefs(counterStore)
// 如果要使用store中的action,可以直接解构
const { changeCount } = counterStore
3)toRef
额外知识点记录
防抖 -- 延迟执行
可使用setTimeout延迟执行。函数执行的时候先清除延时器(clearTimeout(定时器))。
// 此处是vue的watch对象里的代码
words(newValue, oldValue){
// 每次都先清除,后赋值
clearTimeout(this.timer);
this.timer = setTimeout(()=>{
// 要延迟执行的逻辑
}, 300)
},
vue中$emit用到的场景?
1)组件通信:props和emit('func name', params)
2)事件总线:bus.$emit('function name', params)
3).sync修饰符:this.$emit('update:属性名', value)
DOM事件流的三个阶段?
前端如何模拟后端接口?
json-server插件
vue 项目报Uncaught runtime errors: 导致项目崩溃
使用@vue/cli脚手架构建的项目,再npm run serve运行后,一旦提示报错,就会出现Uncaught runtime errors提示,遮盖了整个页面。这个报错只会再开发环境出现,是webpack-dev-server弄出来的。
// 当出现编译错误或者警告时,再浏览器中显示全屏覆盖
overlay: true
解决办法:
再vue.config.js或者webpack.config.js里添加如下配置:
devServer: {
client: {
overlay: false
}
}
重新启动项目,就没有覆盖全屏的错误提示了。
vue2 和 vue3中v-model实现逻辑区别
1)在vue2中,v-model是:value和@input组合的简写。
2)在vue3中,v-model是:modelValue和@update:modelValue的简写
待办记录
1)pinia延伸 1 答案不完全