Vue基础知识要点
Vue笔记
基础篇
Vue3简介
API的区别
vue2使用的选项式API,选项式API以组件不同方面(数据、方法、生命周期钩子)以选项的形式进行组织。
vue3使用的组合式API,允许开发者以函数的形式组织逻辑,将相关逻辑组合在一起
Vue3相比Vue2的好处
性能提升
打包大小减少
初次渲染更快,更新渲染快
内存减少
源码的升级
使用Proxy代替defineProperty实现响应式
重写虚拟DOM实现和Tree-Shaking。
拥抱TypeScript
Vue3可以更好地支持TypeScript
新特性
Composition Api(组合API):
setup
ref与reactive
computed与watch
新的内置组件:
Fragment
Teleport
Suspense
其他改变
新的生命周期钩子
data选项应始终被声明为一个函数
移除keyCode支持作为v-on的修饰符
创建Vue3工程
基于vue-cli(不推荐)
创建方式:
vue –version
npm install -g @vue/cli
vue create vue_test
基于vite创建(推荐)
优势:
轻量加速热重载(HMR),能实现极速的服务启动
对TypeScript、JSX、CSS等支持开箱即用
真正的按需编译,不再等待整个应用编译完成
webpack构建与vite构建的对比
webpack:
启动先执行路由再执行模块,确认都执行完毕没问题再启动
vite:
启动之后就已经启动成功了,然后再去看你正在看的的哪个路由随后再加载对应的模块
创建命令:
npm create vue@latest
配置项目:
配置项目名称
Project name:(起英文名)
是否添加TypeScrupt支持
Add typeScript? Yes
是否添加JSX支持
Add JSX Support? No
是否添加路由环境
Add Vue Router For Single Page Application developent? No
是否添加pinia环境
Add Pinia for state management? No
是否添加单元测试
Add Vitest for Unit Testing? No
是否添加端到端测试方案
Add an End-to-End Testing Solution? No
是否添加ESLint语法检查
Add ESLint for code quality? Yes
是否添加Preettiert代码格式化
Add Prettier for code formatting? No
总结:
Vite项目中,index.html是项目的入口文件,在项目最外层。
加载index.html后,Vite解析type=”module”指向的JavaScript
Vue3中是通过createApp函数创建一个应用实例
Vue3也可以使用Vue2的语法
OptionsAPI与CompositionAPI
Vue2的API风格是Options(配置)风格的
Vue3的API风格是Composition(组合)风格的
OptionsAPI的弊端
Options类型的API,数据、方法、计算属性等,是分散在:data,methods,computed中的,若想新增或者修改一个需求,就需要分别修改:data,methods,computed,不便于维护和复用
CompositionAPI的优势
可以用函数的方法,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起
setup
setup概述
setup是Vue3中一个新的配置项,值是一个函数,它是Composition API“表演的舞台”,组件中所用到的:数据、方法、计算属性、监视……等等,均配置在setup中
特点如下:
setup函数返回的对象中的内容,可直接在模板中使用。
setup中访问this是undefined。
setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的
setup与OptionsAPI的关系
setup和OptionsAPI语法可以共存,OptionsAPI可以this到setup中的数据,但是setup里拿不到vue旧语法的值
setup导出
在vue文件中,export导出不能和setup共用一个script,但是可以使用
并且安装npm i vite-plugin-vue-setup-extend -D此命令库
在vite.config.ts配置:
import VueSetupExtend from ‘vite-plugin-vue-setup-extend’
plugins: [
VueSetupExtend()
],
这样舍弃了export繁琐的代码导出,直接在sript标签上添加name即可使用
响应式数据
ref创建:基本类型的响应式数据
作用:定义响应式变量
ref可以定义基本类型、对象类型的响应式数据
ref包裹对象类型的响应式数据,实际是value是用的reactive返回的proxy的实例对象
在setup中
import {ref} from ‘vue’
let age = ref(18);
function changeAge() {
age.value += 1;
}
原本在setup中的数据,即便修改也没有在页面中响应,但是将setup中需要修改的数据加上ref后,ref会将这个属性变成一个对象,调用的方法就可以属性.value对它作出修改就会在页面进行响应,在模板字符串中调用这个属性的时候只用填写这个的属性名,自然就会将对象里的这个属性值拿出来了。
age不是响应式,但是age.value是响应式
vue2直接写在选项式API里自然就进行响应了
reactive创建
作用:定义响应式变量
reactive只能定义对象类型的响应式数据,返回一个Proxy的实例对象,
reactive定义的响应式是深层次的
一辆, 价值
import { reactive } from “vue”;
let car = reactive({ brand: “奔驰”, price: 100 });
function changePrice() {
car.price += 10;
}
和ref一样不过reacive是针对负责复杂数据类型的响应式而创建的,而上述代码中的car是proxy代理对象
toRefs和toRef
toRefs
作用:将一个reactive对象里的属性,值都变成ref对象,并形成一个新的对象,这时候只用解构对象属性就可以拿到带有响应式的属性了
在setup中
let person = reactive({
name: ‘张三’,
age: 18
})
let {name, age} = person
解构后的name,age并不是响应式,只是拿到了person里的对象属性,但如果是person.name,person.age那么就是拿到了响应式的对象属性
toRef
作用:将一个reactive对象中的属性用toRef包裹起来,这个时候会形成一个新的对象,属性和值都变成ref对象,对于这个新对象直接调用就可以使用了
let person = reactive({
name: “张三”,
age: 18,
});
let nl = toRef(person, “name”);
console.log(nl.value);
computed计算属性
特点:当依赖的数据发生变化时会重新计算
如果当要一直调用一个方法来放到页面中,尽量使用计算属性,如果使用方法来放到页面中,那么调用几次方法就会显示被调用多少次,但是如果用计算属性,那么他的缓存会让他调用多少次都只会被调用一次
计算属性返回的值也是Ref是ComputedRefImpl,不过是只读的没有办法对value进行修改
对象解构:使用花括号
{}
来定义解构模式,变量名需要与对象的属性名相匹配(可以使用别名改变变量名)。数组解构:使用方括号
[]
来定义解构模式,变量的顺序与数组元素的顺序相对应。
let firstName = ref(“zhang”);
let lastName = ref(“san”);
let fullName = computed({
get() {
return (
firstName.value.slice(0, 1).toUpperCase() +
firstName.value.slice(1) +
“~” +
lastName.value
);
},
set(val) {
const [str1, str2] = val.split(“-“);
firstName.value = str1;
lastName.value = str2;
},
});
function changeFullName() {
fullName.value = “li-si”;
}
watch监听数据
作用:监视数据的变化
特点:Vue3中的watch只能监视一下四种数据
1.ref定义的数据。
2.reactive定义的数据。
3.函数返回一个值(getter函数)
4.一个包含上述内容的数组
第一个参数是侦听器的源, 这个来源可以是一下几种:
一个函数、一个值(getter函数)
一个ref
一个响应式对象
…或者是由以上类型的值组成的数组
情况一
监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变
import { ref, computed, watch } from “vue”;
let sum = ref(0);
function changeSum() {
sum.value += 1;
}
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log(“sum变化了”, newValue, oldValue);
if(newValue >= 10) {
stopWatch()
}
});
情况二
监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视
如果修改ref定义的【对象类型】数据的单个属性,那么watch深度监听到的新旧属性都是一样的,因为地址都一样,如果不开启watch深度监听那么单个修改对象类型的数据都不会监听到,但如果给对象类型的数据,换一个新的对象类型的数据,因为地址的改变,那么新旧值就是不一样的
import { ref, watch } from “vue”;
let person = ref({
name: “张三”,
age: 18,
});
function changeAge() {
person.value.name += ‘~’
}
function changeName() {
person.value.age += 1
}
function changePerson() {
person.value = {
name: ‘李四’,
age: 90
}
}
watch(person, (newValue, oldValue) => {
console.log(newValue,oldValue);
}, {deep: true,immediate: true})
情况三
监听reactive定义的【对象类型】数据,且默认开启了深度监视,且不可关闭(隐式创建深层监听)
reactive定义的对象不可整体修改
可以使用Object.assign(person,{name:’李四’, age: 80})浅拷贝
import { reactive, watch } from “vue”;
let person = reactive({
name: “张三”,
age: 18,
});
let obj = reactive({
a: {
b: {
c: 666,
},
},
});
function changeAge() {
person.name += “~”;
}
function changeName() {
person.age += 1;
}
function changePerson() {
return Object.assign(person, {
name: “李四”,
age: 90,
});
}
function test() {
obj.a.b.c = 888
}
watch(person, (newValue, oldValue) => {
console.log(“person变化了”, newValue, oldValue);
});
watch(obj, (newValue,oldValue) => {
console.log(‘obj变化了’, newValue,oldValue);
})
情况四
监视ref或reactive定义的【对象类型】数据中的某个属性,注意点如下:
1.若该属性值不是【对象类型】,需要写成函数形式
2.若该属性值依然是【对象类型】,可直接编,也可写成函数,不过建议写成函数。
import { reactive, watch } from “vue”;
let person = reactive({
name: “张三”,
age: 18,
car: {
car1: “奔驰”,
car2: “宝马”,
},
});
function changeName() {
person.name = “李四”;
}
function changeAge() {
person.age += 1;
}
function changeC1() {
person.car.car1 = “奥迪”;
}
function changeC2() {
person.car.car2 = “大众”;
}
function changeCar() {
person.car = { car1: “雅迪”, car2: “兰博” };
}
watch(
() => {
return person.name;
},
(newValue, oldValue) => {
console.log(“监视了”);
}
);
watch(
() => {
return person.car
},
(newValue, oldValue) => {
console.log(newValue, oldValue);
},{deep: true}
);
如果没加函数,那么监听不到person.car的整体对象,只能监听到person.car的属性,如果加了就只能监听到整体对象,监听不到单个属性,但是最后加深度监听便都可以监视了
情况五
监听上述的多个数据,只用将watch参数变成数组,以及要处理的对象属性即可
watch([
() => person.car
,() => person.name ]
,
(newValue, oldValue) => {
console.log(newValue, oldValue);
}, {
deep:true
}
);
watchEffct
立即运行一个函数,同时响应式的追踪其依赖,并在依赖更改时重新执行该函数。
watch对比watchEffct
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch:要明确指出监视的数据
watchEffectL不同明确指出监视的数据(函数中监视哪些属性,那就用到哪些属性)
import { ref , watch, watchEffect} from “vue”;
let temp = ref(10)
let height = ref(0)
function changeTemp() {
temp.value += 10
}
function changeHeight() {
height.value += 10
}
// watch([temp, height],(Value) => {
// let [tempValue, HeightValue] = Value;
// if(tempValue >= 60 || HeightValue >=80) {
// console.log(‘请求’);
// }
// })
watchEffect(() => {
if(temp.value >= 60 || height.value >=80) {
console.log(‘请求’);
}
})
标签里的Ref属性
创建一个title2,用于存储ref标记的内容
h2 ref=”title2” 北京 h2
let title2 = ref()
如果父页面和子页面某个元素都有相同的id选择器名字,那么会冲突,第二个id会被第一个id选择器覆盖,但用ref的时候就不会冲突
如果将ref放在组件标签里,那么就会得到组件实例proxy代理对象,组件实例里面的内容都被保护起来了,所以什么都看不到,只要将子组件里想暴露出来的数据就使用
defineExpose({a, b, c})
TS中的接口、泛型、自定义类型
接口演示
TS
// 定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
id: string,
name: string,
age: number
}
vue
import { type PersonInter, type PersonType } from “@/types”;
let person: PersonInter = { id: “123”, name: “张三”, age: 60 };
泛型
let personList: Array(尖括号)PersonInter(尖括号) = [
{ id: “1”, name: “张三”, age: 60 },
{ id: “2”, name: “张三”, age: 60 },
{ id: “3”, name: “张三”, age: 60 },
];
let personList = reactive(尖括号)PersonType(尖括号)([
{ id: “1”, name: “张三”, age: 60 },
{ id: “2”, name: “张三”, age: 60 },
{ id: “3”, name: “张三”, age: 60 },
]);
自定义类型
TS
// 一个自定义类型
export type PersonType = Array(尖括号)PersonInter(尖括号);
let personList: PersonType= [
{ id: “1”, name: “张三”, age: 60 },
{ id: “2”, name: “张三”, age: 60 },
{ id: “3”, name: “张三”, age: 60 },
];
Props
使用方法:
在父组件中的子组件标签中起一个属性名字,在里面写入想传入的值,如果是变量,那么就将前面加个冒号,后面将变量传进去。在子组件中用defineProps接收
父组件:
Person a=”哈哈” />
子组件:
import {defineProps} from ‘vue’
let x = defineProps([‘a’])
console.log(x.a);
也可以做限制类型
defineProps<{list: PersonType}>()
也可以做可传可不传的限制
defineProps<{list?: PersonType}>()
最终极的写法,限制必要性,写默认值(withDefaults)
withDefaults(defineProps<{ list?: PersonType }>(), {
list: () => [{ id: “1”, name: “李四”, age: 18 }],
});
对生命周期的理解
组件的生命周期
创建、挂载、更新、销毁
Vue2的生命周期
十一个生命周期,组件生命周期四个阶段,八个周期
// 创建前钩子函数
beforeCreate() {
console.log('beforeCreate')
},
触发时机:在实例初始化之后,数据观测 (
data
属性) 和event/watcher
事件配置之前被调用。// 创建后钩子函数
created() {
console.log(‘created’)
},
触发时机:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 (
data
属性)、property
和method
的计算、watch/event
事件回调的配置等。然而,挂载阶段还没有开始,$el
属性目前不可用。// 挂载前钩子函数
beforeMount() {
console.log(‘beforeMount’)
},
触发时机:在挂载开始之前被调用,此时模板编译成
render
函数完成,但还未将render
函数返回的虚拟 DOM 挂载到页面上。// 挂载后钩子函数
mounted() {
console.log(‘mounted’)
},
触发时机:在挂载完成后调用,此时
$el
已经被创建并替换掉el
选项所指向的 DOM 元素。// 更新前钩子函数
beforeUpdate() {
console.log(‘beforeUpdate’)
},
触发时机:在数据更新之前被调用,发生在虚拟 DOM 打补丁之前。
// 更新后钩子函数
updated() {
console.log(‘updated’)
},
触发时机:在数据更新之后被调用,发生在虚拟 DOM 打补丁之后。
// 销毁前钩子函数
beforeDestroy() {
console.log(‘beforeDestroy’)
},
触发时机:在实例销毁之前调用。此时实例仍然完全可用。
// 销毁后钩子函数
destroyed() {
console.log(‘destroyed’)
}
- 触发时机:在实例销毁之后调用。此时所有的事件监听器和子实例都已经被销毁。
Vue3的生命周期
创建阶段: setup
挂载阶段:onBeforeMount、onMounted
更新阶段:onBeforeUpdate、onUpdate
卸载阶段:onBeforeUnmount、onUnmounted
常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
父组件与子组件钩子关系
父创建,父挂载前,子创建,子挂载前,子挂载后,父挂载后
自定义Hooks
正因为有了Hooks,这个组合式Api才有意义
好处:
逻辑复用与代码组织
可复用逻辑封装
逻辑关注点分离
提高代码可读性和可维护性
代码模块化
减少模板代码
更好的状态管理和响应式处理
响应式逻辑封装
状态管理的灵活性
提升开发效率和团队协作
快速迭代和开发
团队协作
用法
将组件里的功能拆分到hooks文件夹下,一个功能对应一个,并用默认导出,将数据与方法返回。让想调用此功能的组件import导入功能,再将其数据方法解构,即可使用
路由
路由的理解
router是路由器,route是路由
路由就是一组key-value的对应关系
多个路由,需要经过路由器的管理
比如:点击菜单栏路径发生变化,就会被路由器监视到,路由器进行规则匹配
基本切换效果
1.导航栏、展示区
2.请来路由器
3.指定路由的具体规则(什么路径,对应着什么组件)
4.形成一个一个的【???.vue】Class.vue Subject.vue
在终端中使用 npm i vue-router
实现步骤:
1.在src下创建router文件夹,再创建index.ts文件
// 创建一个路由器,并暴露出去
// 引入createRouter
import { createRouter, createWebHistory } from “vue-router”;
// 引入组件
import Home from “@/components/Home.vue”;
import About from “@/components/About.vue”;
import News from “@/components/News.vue”;
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes: [
// 一个一个的路由规则
{
path: “/home”,
component: Home,
},
{
path: “/about”,
component: About,
},
{
path: “/news”,
component: News,
},
],
});
export default router;
createWebHistory 是路由器的工作模式
2.在components下创建对应的组件
3.在App.vue中将路由的展示引入RouterView,以及跳转路由位置的RouterLink标签
import { RouterView, RouterLink } from “vue-router”;
template中
RouterLink标签to路由 active-class=””点击哪个哪个高亮
// 展示区
RouterView标签展示
4.在主main文件中,将Router路由器配置
import {createApp} from ‘vue’
import App from ‘./App.vue’
import Router from ‘./router’
const app = createApp(App)
app.use(Router)
app.mount(‘#app’)
两个注意点
组件
一般组件:亲手写标签出来的
路由组件: 靠路由的规则渲染出来的
例如: routes:[{path:’/demo’,component:Demo}]
1.路由组件通常存放在pages或views文件夹,一般组件通常存放在components文件夹
2.通过点击导航,视觉效果上“消失”了的路由组件,默认是被卸载的,需要的时候再去挂载
路由工作模式
1.history模式
优点:URL更加美观,不带有#,更接近传统的网站URL
缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404报错
const router = createRouter({history: createWebHistory()})
2.hash模式
优点:兼容性更好,一位内不需要服务器端处理路径
缺点:URL带有#不太美观,且在SEO优化方面相对较差
const router = createRouter({history: createWebHashHistory()})
路由_to的两种写法
第一种:to字符串写法
router-link active-class=”active” to=”/home”
第二种:to的对象写法
router-link active-class=”active” :to=”{path: ‘/home’}”
路由的命名
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes: [
// 一个一个的路由规则
{
name:’zhuye’,
path: “/home”,
component: Home,
},
{
name: ‘guanyu’,
path: “/about”,
component: About,
},
{
name: ‘xinwen’,
path: “/news”,
component: News,
},
],
});
路由的命名使用
RouterLink :to=”{name:’xinwen’}” active-class=”active”
路由_嵌套路由
1.在路由器上创建children数组
{
name: “xinwen”,
path: “/news”,
component: News,
children: [
{
path: “deail”,
component: Detail,
},
],
},
然后将路由的子组件写上name,对应父组件的name,然后再父路由对应的组件下写
RouterLink to=”/news/deail”
点击即可跳转到子路由对应的组件上
再将下面想呈现子路由对应的组件的位置写RouterView标签
路由_传参
路由_query参数
在父路由组件中跳转子路由组件传参两种传参方式:
第一种
<RouterLink :to=”/news/deail?id=${item.id}&title=${item.title}&content=${item.content}`”
第二种
RouterLink :to=”{
path: ‘/news/deail’,
query: {
id: item.id,
title: item.title,
content: item.content
}
}”
在子路由对应的组件中用以下语法,即可拿到传参的proxy代理对象
import {useRoute} from ‘vue-router’
let route = useRoute()
从响应式中解构属性,这个属性是没有响应式的
import { toRefs } from “vue”;
import { useRoute } from “vue-router”;
let route = useRoute();
let {query} = toRefs(route)
在template的html结构中可以直接使用query拿到地址传过来的值
query.id
query.title
query.content
路由_params传参
在父路由组件中跳转子路由组件传参两种传参方式:
第一种:
父组件:
RouterLink :to=”`/news/deail/${item.id}/${item.title}/${item.content}”
RouterView
第二种:
第二种方式需要path换成name只能传name值
父组件:
RouterLink :to=”{
name: ‘deailName’,
params: {
id: item.id,
title: item.title,
// content: item.content
}
}”
RouterView
该如何使用params传参
路由中配置:
{
name: “xinwen”,
path: “/news”,
component: News,
children: [
{
name: ‘deailName’,
path: “deail/:id/:title/:content?”, // content可传可不传
component: Detail,
},
],
},
子组件接收
import { toRefs } from “vue”;
import { useRoute } from “vue-router”;
let route = useRoute();
let { params } = toRefs(route);
路由_props配置
第一种:在子路由中配置 props:true就会让地址传的params参数变成父传子的数据,将路由收到的所有params参数作为props传给路由组件
路由配置:
{
name: “xinwen”,
path: “/news”,
component: News,
children: [
{
name: ‘deailName’,
path: “deail/:id/:title/:content?”,
component: Detail,
props: true
},
],
},
子组件:
import { defineProps} from ‘vue’
defineProps([‘id’,’title’,’content’])
第二种:函数写法:可以自己决定将什么作为props给路由组件,只能对query传参
{
name: “xinwen”,
path: “/news”,
component: News,
children: [
{
name: ‘deailName’,
path: “deail”,
component: Detail,
props(route) {
return route.query;
}
},
],
},
第三种:对象写法,可以自己决定将什么作为props传参给路由组件
{
name: “xinwen”,
path: “/news”,
component: News,
children: [
{
name: ‘deailName’,
path: “deail”,
component: Detail,
props: {
a: 100,
b: 200,
c:300
}
},
],
},
路由的_replace属性
作用:控制路由跳转时操作浏览器历史记录的模式
浏览器的历史记录有两种写入方式:分别为push和replace:
push是追加历史记录(默认值)
replace是替换当前记录
开启replace模式
RouterLink replace …..> News<RouterLink
路由_编程式路由导航
脱离RouterLink实现路由跳转,使用脚本跳转页面,语法和to一样
button @click=”showNewDeail(item)”
import {useRouter} from ‘vue-router’
function showNewDeail(item: interNews) {
router.replace({
path: ‘/news/deail’,
query: {
id: item.id,
title: item.title,
content: item.content
}
})
路由_重定向
让指定的位置重新定位到另一个位置
{
path: “/“,
redirect: “/home”,
}