Vue2+Vue3学习日志记录

457 阅读16分钟

第一天

什么是vue

vue是一个用于构建用户界面的渐进式框架。

创建vue实例的步骤

  1. 准备容器
  2. 引包(官网)-- 开发版本 / 生产版本
  3. 创建Vue实例 new Vue()
  4. 指定配置项 el data => 渲染数据
    1. el => 通过选择器指定挂载点
    2. data => 提供数据

语法

  • 插值表达式{{}}
  • 核心特征:响应式。数据改变,视图自动更新

key的作用

v-for的默认行为会尝试原地修改元素(就地复用)。

key是给元素添加唯一标识,便于Vue进行列表项的正确排序复用。

key只能是字符串或者数字,具有唯一性。不推荐index

第二天

指令

常用的指令

  1. v-on
  2. v-bind
  3. v-show
  4. v-html
  5. v-if
  6. v-else-if
  7. v-else
  8. v-for
  9. v-model
  10. v-once
  11. v-pre
  12. v-text
  13. v-cloak
  14. 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简化代码

image-20240301102236057

image-20240301104052869

.sync修饰符

作用:可以实现子组件和父组件数据的双向绑定,简化代码

特点:props属性名可以自定义,非固定的value

场景:封装弹窗类的基础组件,visible属性 true显示 false隐藏

本质:就是 :属性名 和 @update:属性名 合写

步骤:

  1. 组件属性加上.sync修饰符
  2. 子组件中要修改传进来的props时,调用this.$emit("update:属性名", value)

image-20240301112328419

自定义指令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原理:

  1. 给当前组件模板的所有元素,都会被添加上一个自定义属性:data-v-hash值。利用这个hash值可以区分不同的组件。
  2. 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

image-20240301115409157

问题:代码执行之后,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(路径匹配的组件显示的位置)

image-20240301173628542

声名式导航 - 导航链接

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

image-20240304120049118

image-20240304120107532

页面跳转方式

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)

实例对象vue2vue3
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语法糖简化代码

image-20240308163938783

image-20240308164500443

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/createdsetup(直接在script里写)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
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原理:

image-20240310160311085

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:

  1. 提供更简单的API(去掉了mutation、modules)
  2. 提供符合、组合式风格的API(和Vue3新语法统一)
  3. 去掉了modules的概念,每一个store都是独立的模块(不用配置namespaced)
  4. 配合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=>this.emit => this.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 答案不完全