vue是渐进式框架
- 使用方式渐进:从CDN引入写简单交互,到CLI创建完整项目,再到Nuxt做SSR,每一步都是可选的。
- 功能模块渐进:核心库只负责视图层,需要路由加Vue Router,需要状态管理加Pinia,不强求一次性配齐。
- 学习曲线渐进:新手只需要会HTML/JS就能上手,随着项目复杂度提升,再逐步学习进阶特性。
Vue 采用自动追踪 的方式。它通过Proxy(Vue3)或Object.defineProperty(Vue2)拦截数据的读取和修改,在读取时收集依赖(当前正在运行的函数),在修改时通知所有依赖更新。这种方式的优点是精确------只有真正依赖这个数据的组件才会更新,而且开发者可以直接修改数据,不需要额外操作。
React 则采用显式触发 的方式。它没有自动追踪,而是通过setState手动触发更新。一旦setState调用,整个组件函数会重新执行,生成新的虚拟DOM,然后通过Diff算法找出变化的部分更新真实DOM。这种方式的优点是简单直观------数据变了就重新渲染,但缺点是需要开发者手动优化(memo/useMemo)避免不必要的渲染。
vue
<script>
export default {
name: 'PP',
// setup函数中的this是undefined,vue3中已经弱化this了,里边变量方法必须返回
// 执行时机 早于beforeCreated()
// setup返回对象,也可直接返回函数,页面直接渲染返回的内容
// setup 和 data和method关系
// setup()能和data\method同时存在
// data和methods可以读取setup()中数据this.name,setup先执行,setup里读不到data里数据
setup() {
let name = ref('lili');
let age = ref(18);
function changeName {
name.value = 'alice';
}
return {
name,
age,
changeName,
}
// return () => 'hahhahahah' // 这个组件直接渲染hahahahah
}
}
</script>
// setup函数语法糖
// 设置组件名,可与setup语法糖同时存在
<script>
export default {
name: 'PP',
}
</script>
// 上边不想再写个script单独设置组件名字,可以借助一个插件
// vite-plugin-vue-setup-extend 安装后在vite.config.ts中配置插件,即可name="person-123"
<script setup lang="ts" name="person-123">
let name = 'lili';
let age = 18;
function fn() {}
</script>
ref和reactive
vue2中,数据写在data(){return {}}中就是响应式的,原理defineProperty劫持。
vue3响应式 数据实现响应式使
基本类型 + 对象类型 使用ref(初始值) let name = ref('ddd') name.value 需要.value取值
对象类型 let obj = reactive(初始值) 直接访问 ;嵌套深层的对象,建议用reactive,也可用ref
reactive定义后,不能直接再赋值整个对象。
vue
let car = reactive({brand: 'bwp', price: 200});
// 错误
car = {brand: 'benci', price:300} // 错误,失去响应式,页面不更新
car = reactive({brand: 'aodi', price:300}) // 错误,原先的对象失去响应式,页面不更新
// 正确
Object.assign(car, {brand: 'aodi', price:300}) // 正确,页面更新,没有更新person的地址
// 如下可以,正确
const obj = ref({a: 123});
obj.value = {a: 567}; // 一个新对象赋值,obj的地址变了
toRefs和toRef
ini
let person = reactive({name: 'll', age:18}); //将响应式对象所有属性都变成响应式
let { name, age } = toRefs(person);
console.log(name, age);
let n = toRef(person, 'name');一个一个解构成响应式

computed vue3的
计算属性有缓存
javascript
// 这么定义的计算属性不能修改
let fullName = computed(() => {
return firstName.value + lastName.value;
})
// 这么定义的,可读可写
let fullName = computed({
get() {
return firstName.value + lastName.value;
},
// 赋值时调用
set(newVal) {
}
})

watch
监听数据变化,Vue3只能监听4种数据。
- ref定义的数据。
ini
let sum = ref(0);
const addSum = () => {
sum.value += 1;
};
// 解除监听
// 监听【ref】定义的【基本类型】
const stopWatch = watch(sum, (newVal, oldVal) => {
console.log(newVal, oldVal);
if (oldVal > 10) {
stopWatch(); // 调用该函数解除监听
}
});
javascript
// 监视【ref】定义的【对象类型】数据,监视的是对象的地址值,
// 若想监听对象内部属性发生的变化,需要【手动开启深度监听】
/** 监视ref定影的对象类型数据,监视的是对象的地址值,
若想监听对象内部属性发生的变化,需要手动开启深度监听
watch第一个参数:被监视的数据;
第二个参数:监视的回调
第三个:配置的对象deep、immediate等
*/
let person = ref({ name: 'lisi', age: 18 });
watch(
person,
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{ deep: true, immediate: true }
// deep开启,监听内部属性,
// immediate值表示立即执行一次,数据未变化时就执行一次
);
- reactive定义的数据
javascript
// 监视【reactive】定义的对象,默认开启深度监听,不用手动开启,不能关闭
let person = reactive({ name: 'lisi', age: 18 });
watch(
person,
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{ immediate: true }
// 此时deep默认开启,可监听内部属性
// immediate值表示立即执行一次,数据未变化时就执行一次
);
- 函数返回的一个值 -》getter函数(能返回一个值的函数)。 监视ref或reactive定义的对象类型中的某个属性(属性为基本类型的或者对象类型,属性为对象的也可以直接监视这个属性,建议写成函数式)
scss
let person = reactive({
name: 'lisi',
car: {c1: 'yadi', c2: 'baoma'}
})
watch(() => person.name, () => {}, {})
// 下面情况能监听到car中单个属性的变化,但是car整体赋值监听不到,car = {c1; 'rr', c2: 'ee'}
watch(person.car, () => {}, {})
// 下面情况能监听到car整体赋值,不加deep参数,car的单个属性变化监听不到,所以要加deep参数
// 函数的写法,要深度监听,写deep参数。即地址上想监听内部属性变化,需加deep参数
watch(() => person.car /** 该函数返回car的地址 */, () => {}, {deep: true})
- 上述组成的数组
php
let person = reactive({
name: 'lisi',
age: 18,
car: {c1: 'yadi', c2: 'baoma'}
})
watch([() => person.name, () => person.car], () => {}, {deep: true})
watchEffect 副作用
watch必须明确指出监视谁。 watchEffect不用写监视谁,直接回调,回调中用哪些属性到就监视哪些
scss
let height = ref(0);
let width = ref(0);
// 会立即调用回调函数,响应式追踪变化
watchEffect(() => {
if (heigth.value > 10 || width.value > 5) {
console.log('超过标准了');
}
})
ref容器
ini
<h2 ref='title'>nihao</h2>
let title = ref(); // title.value就是拿到h2这个Dom元素【普通标签】
<Person ref='personRef'></Person>
let personRef = ref(null);
personNull.value 就是person组件实例,可以拿到该组件defineExpose的东西【组件】
ts规范
typescript
// 接口,用于限制person对象的具体属性
// src/types/index.ts
export interface PersonInterface {
name: string;
age: number;
}
// 一个自定义类型
export type Persons = Array<PersonInterface>
// export type Persons = PersonInterface[] // 或者这种写法
// src/components/Person.vue
import {type PersonInterface, type Persons} from '@/types'
let person:PersonInterface = {age: 19, name: 'lisi'};
let personList2 = reactive<Persons>([]);
let personList: Persons = [];
let personList1: Array<PersonInterface> = [];
组件生命周期
v-if 创建销毁组件 v-show 隐藏使用display:none 元素还在
生命周期函数,生命周期钩子
vue2的生命周期 创建:created(创建前beforeCreate,创建完毕created)
挂载:mounted(挂载前beforeMount,挂载完毕-组件显示在页面上mounted)
更新:updated(更新前beforeUpdate,更新完毕 updated)
销毁:destroyed(销毁前beforeDestory,销毁完毕destroyed)
vue3的生命周期
创建:setup()替代了,模拟创建前和创建完
挂载:onBeforeMount(() => {}) onMounted(() => {})
更新:onBeforeUpdate(() => {}) onUpdated(() => {})
卸载:onBeforeUnmount(() => {}) onUnmounted(() => {})
父子生命周期顺序:
子挂载完--》父挂载完 父组件是最后挂载完的
hooks
本质是一个返回值的函数。 使用时引入,可解构获取hook中暴露的数据
csharp
// 将逻辑抽离出来,放到一个ts或js文件中
// 里边可以使用生命周期函数、或者computed、watch等vue中的东西
// src/hooks/sumHook.ts
import { ref } from 'vue'
export default function() {
let sum = ref('')
let add = () => {
sum.value += 1;
}
return {
sum,
add
}
}
// 引用处
import useSum from '@/hooks/sumHook.ts'
let { sum, add } = useSum();
路由router
javascript
import { RouterView, RouterLink} from 'vue-router'
<RouterView></RouterView> // 加载的路由组件显示区域占位
// 路由跳转组件
<RouterLink to='/home' active-class='actived-class'></RouterLink>
<RouterLink :to={path: '/home'} active-class='actived-class'></RouterLink>
<RouterLink :to={name: '/zhuye'} active-class='actived-class'></RouterLink>
路由组件:靠路由规则渲染出来的。一般写在pages或view文件夹下
routes: [{ path: '/home', component: Home, name='zhuye' }]
路由切换时,视觉消失的路由组件,是被卸载了
一般组件:手动写标签,一般写在components下 <person></person>
路由工作模式
history模式
优点:URL更美观,不带#,更接近传统网站的URL。 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误,可在nginx等服务器上配置
css
vue2: mode: 'history'
vue3: history: createWebHistory()
const router = createRouter({
history: createWebHistory(),
routes: [],
})
hash模式
优点:兼容性更好,因为不需要服务器处理路径
缺点:url上带#不美观,且在SEO优化方面相对较差
css
vue2: mode: 'hash'
vue3: history: createWebHashHistory()
const router = createRouter({
history: createWebHashHistory(),
routes: [],
})
路由参数
ini
import { useRoute, useRouter } from 'vue-router';
let route = useRoute();
// route.query
<RouterLink :to={path: '/zhuye', query: {id: xxx, title: xxx}} active-class='actived-class'></RouterLink>
<RouterLink :to=`/news/detail?id=${id}&title=${title}` active-class='actived-class'></RouterLink>
// /news/detail?id=119&title=万万没想到 // id=119&title=万万没想到 query参数
// parmas传参 to中路由必须写name,不能是path;且params中不能传对象和数组
<RouterLink :to={name: '/zhuye', params: {id: xx, title: xx}} active-class='actived-class'></RouterLink>
<RouterLink :to=`/new/detail/${id}/${title}` active-class='actived-class'></RouterLink>
// route.params 路由处占位: /news/detail/:id/:title
路由的props
ruby
routes: [{
path: 'news',
component: News,
name='zhuye',
children: [
{
name: 'xiang',
path: 'detail/:id/:title',
component: Detail,
// 第一种写法:将路由收到的所有【params参数】作为props传给路由组件
// <Detail id=xx title=xx />
// props: true,
// 第二种写法:函数写法,可以自己决定将什么作为props传给路由组件
//props(route){ // 参数为route路由信息
// return route.query
//}
// 第三种写法:对象写法,可以自己决定将什么作为props传给路由组件
//props: { // 这种写法传固定值
// a: 100
// b: 200
//}
}
]
}]
路由的replace属性
javascript
// replace替换,不能回退到上一个访问的路由 ;不加默认是push,可以回到上一个访问的路由
<RouterLink replace :to=`/new/detail/${id}/${title}` active-class='actived-class'></RouterLink>
编程式路由导航
ini
import { useRouter } from 'vue-router';
const router = useRouter();
router.push('/news');
router.replace('/news');
vuex与pinia 集中式状态(数据)管理
多个组件共享数据
javascript
import { defineStore } from 'pinia';
// 选项式
export const useCountStore = defineStore('count', {
state() {
return {
sum: 6,
school: 'cc',
address: 'ww'
}
},
// actions中放置的一个一个的方法,用于响应组件中的动作
actions: {
increment(value) {
console.log('ii调用了', value);
}
}
});
// setup写法 组合式
export const useCountStore = defineStore('count', () => {
// state
let sum = ref(6),
let school = ref('cc'),
let address = ref('ww')
// actions
const increment = (value) => {
console.log('ii调用了', value);
}
return {
sum,
school,
address,
increment,
}
});
store/count.ts
import { useCountStore } from '@/store/count';
const countStore = useCountStore();
// 拿到store中数据
// countStore 是Proxy包裹的对象,里面的ref会自动解包,不用再.value
console.log(countStore.sum)
// 第一种修改方法
countStore.sum = 9;
// // 第一种修改方法, 批量变更 store
countStore.$patch({
sum: 8,
school: 'dd'
});
// 第三种修改方法,调用store的actions中定义的修改方法
countStore.increment('+++');
// import { storeToRefs } from 'pinia';
// storeToRefs 只会关注store中的数据,不会对方法进行ref包裹
const { sum, scheool } = storeToRefs(useCountStore());
组件间通信
- props,emit 父子组件
- mitt 引入mitt,订阅取消订阅;事件总线
- v-model 此通信方式在UI组件库大量使用双向绑定
ini
<input type='text' v-model="username"> 等价于下边
<input type='text' :value="username" @input="username = (<HTMLInputElement>$event.target).value">
<my-input v-model="username">
<my-input :modelValue="username" @update:modelValue="username = $event">
MyInput.vue
<input type='text' :value="username" @input="username = (<HTMLInputElement>$event.target).value">
defineProps(['modelValue])
$attrs用在模版中,子组件用这个获取副组件传过来的未使用props接收的其他所有属性 然后子组件可以使用v-bind= <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 将其未显示接收的参数传给他的子组件,及父传孙子组件 ' v − b i n d = k e y : v a l u e , . . . . ' = = = > ' v − b i n d = attrs将其未显示接收的参数传给他的子组件,及父传孙子组件 `v-bind={key: value, ....}` ===> `v-bind= </math>attrs将其未显示接收的参数传给他的子组件,及父传孙子组件'v−bind=key:value,....'===>'v−bind=attrs`
用在js上时
javascript
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
// 或
export default {
setup(props, ctx) { // 透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs)
}
}
$ref $parents$ref父组件获取所有的子组件;父-》子 子组件使用ref<child ref='child1Ref'/>$parents子组件中获取到父组件 子-》父
注意点: 一个响应式对象中的属性是ref()定义,读取时不用再.value,底层会自动获取数据- provide/reject 嵌套较深的组件间 祖先-子孙 project('moneyContext', {money, updateMoney}); 父 let {money, updateMoney} = reject('moneyContext', {}) // 可以给个默认值,孙子组件可以使用updateMoney通信给父组件
插槽
默认插槽
<slot>默认内容</slot> ==> <slot name='default'>默认内容</slot> 插槽没用到就显示默认内容
具名插槽
xml
<slot name='header'></slot>
<template v-slot:header><div>menu</div></template>
<Category v-slot:header><div>menu</div></Category>
作用域插槽 v-slot="params"
数据在子那边,但根据数据生成的结构,却由父决定,即需要用到zi的数据
javascript
// 子组件的数据可以绑定到slot上,传给父组件使用
<slot name='header' :youxi=games :a='123'></slot>
// 使用
<template v-slot:header><div>menu</div></template>
<Category v-slot="params"><div>{{params.youxi}}</div></Category> // 默认插槽
<Category v-slot:header="{youxi}"><div>{{params.youxi}}</div></Category> // 解构 header插槽
v-slot:header="{youxi}" ===》 #header={youxi}
shallowRef与shallowReactive 用法和ref和reactive一样,只是监听的顶层属性
两者用来绕开深度响应,避免每个内部属性都做响应式带来的性能成本,使得属性访问更快,可提升性能。
- shallowRef:浅层ref 只关注引用层的变化,不关心内部属性的变化; 只监听.value这层的改变,如果是对象,car.value.a,这个监听不到
- shallowReactive:对象的顶层属性是响应式的,但嵌套属性不是。
readonly及shallowReadonly
readonly所有层都只读
ini
let sum1 = ref(0);
let sum2 = readonly(sum1); // sum2关联了sum1为只读,但sum1变化时,sum2也会变化,sum1自己维护,sum2给别人使用,防止改坏了
shallowReadonly只限制第一层为只读,可以修改第二层数据
toRaw与markRaw
ini
let person = ref({name: 'ii', age: 18});
let p2 = toRaw(person); // 变成了普通对象,无响应式了,用在作为参数传给非vue库去做处理,如lodash库的函数处理数据
let c = {a: 99, b:0};
let c1 = reactive(c); // 响应式
// markRaw 标记一个对象,使其永远不能成为响应式
let car = markRaw({b: ''qq', c: 22});
customRef
自定义ref
scss
let initValue = '你好';
// track跟踪, trigger触发
let msg = customRef((track, trigger) => {
// 读取
get() {
track(); // 告诉vue数据msg很重要,你要对msg进行持续关注,一旦msg变化就去更新
reutrn initValue;
},
// 修改
set(value) {
initValue = value;
trigger(); // 通知vue一下数据msg变化了
}
})
Teleport 传送
xml
将结构传送到body下,里面的元素就能插入到body元素标签下
<Teleport to='body'>
<div>你好</div>
</Teleport>
<Teleport to='.m-box'>
<div>你好</div>
</Teleport>