Appearance
学习VUE3
setup与data等能否共存
可以共存,旧的能读新的,但新的读不到旧的
ref、reactive
ref定义基本类型数据、对象类型数据。数据会变成refimpl。定义对象类型数据时内部会使用reactive。
reactive定义对象类型数据。会把数据包装成Proxy。当对象层级较深的时候,更推荐使用reactive。
在书写代码区域,必须使用.value来获取值。这个在vscode中可以通过设置volar插件的配置项来实现。在vscode设置中,搜索dot value,勾选上。
另外,reactive定义的变量不能改变对象本身,会失去响应式。如果想修改,使用Object.assign(原对象,新对象)
let originObj = reactive({name:'tom', age:18});
originObj = {name: 'Joy', age: 28} // 失去响应式
toRefs、toRef
假设有一个响应式对象或基本类型数据,想直接对内部或值修改,同时保留响应式,需要用到这两个。toRefs自然是用于多值时。
toRefs是将对象中的值全部取出,toRef则需要指定所需的值。
let a = reactive({name: 'Tom', age: 18});
let num = 18;
let {name,age} = toRefs(a); // 这样,新赋值的name和age就变成了响应式
let newNum = toRef(a, num);
计算属性
可以看做是变量,跟其他变量用法相同。
<template>
<div class="index">
原始:{{ age }}
结果: {{ result }}
结果2: {{ result2 }}
</div>
<button @click="changeAge">修改年龄</button>
</template>
<script lang="ts" setup>
import {ref, reactive, computed} from 'vue'
let age = ref(18);
const result = computed(()=>{
return age.value + 1;
})
const result2 = computed({
set(val){
age.value = 24;
},
get(){
return age.value + 2
}
});
const changeAge = () => {
result2.value = 23;
};
</script>
watch
监视数据变化。只能监视四种数据:
- ref定义的数据
- reactive定义的数据
- 函数返回一个值
- 一个包含上述内容的数组
watchEffect
props
interface aType{
name: string;
}
// 普通接收
defineProps(['a'])
// 限制类型
defineProps<{a:aType}>()
// 限制必要性并指定默认值
withDefaults(defineProps<{a: aType}>(), {
a: ()=>({name: 'Tom'})
})
生命周期
vue2里有八个,对应四阶段:创建、挂载、更新、销毁
beforeCreate/created
beforeMounte/mounted
beforeUpdate/updated
beforeDestroyed/destroyed
vue3里有六个,仍旧对应四阶段
在setup中直接默认创建
挂载 ( 挂载前 onBeforeMount, 挂载完毕 onMounted )
更新 ( 更新前 onBeforeUpdate, 更新完毕 onUpdated )
销毁 ( 卸载前 onBeforeUnmount, 卸载完毕 onUnmounted )
hooks
命名以use开头。单独作为文件时是.js或.ts
实际上,只要做成一个use开头的函数,基本上都能被认为是一个钩子。
const useAdd = ()=>{
let baseNum = 0;
const addNum = () => {
baseNum = baseNum + 1;
}
return {baseNum, addNum}
}
const {baseNum, addNum} = useAdd(); // 解构名要与钩子内部变量、函数的命名一致
钩子里也能使用vue里的ref等内容,也可以使用生命周期函数。
路由
如果没安装过,执行代码安装
npm
npm i vue-router
yarn
yarn add vue-router
安装好后配置一下路由文件和加载环境
src下新建router文件夹,新建index.ts文件
import { createRouter, createWebHistory } from "vue-router";
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/news', component: News },
]
});
export default router;
main.ts中使用router
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
createApp(App).use(router).mount("#app");
找个vue文件,写入跳转和展示
// 用active-class,会自动识别哪个按钮被激活了
<template>
<div class="index">
<RouterLink to="/" active-class="active">首页</RouterLink>
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterView />
</div>
</template>
<script lang="ts" setup>
import {RouterView, RouterLink} from 'vue-router'
import Person from '@/components/Person.vue'
</script>
<style>
a.active {
color: green;
}
</style>
路由另一种写法
import { createRouter, createWebHistory } from "vue-router";
import News from '@/components/News.vue'
import News2 from '@/components/News2.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ name: 'contact', path: '/contact', component: News1 },
{ name: 'name-news', path: '/news', component: News2 },
]
});
export default router;
链接里可以这样写
<RouterLink :to="{name:'name-news'}" active-class="active">新闻</RouterLink>
路由传参--query
// 写法一
<RouterLink to="/user?uid=1516">
// 写法二
<RouterLink :to="{
path: '/user',
query: {
uid: 1516
}
}">
接收参数的vue文件里要使用useRoute
import {useRoute} from 'vue-router'
const route = useRoute()
console.log(route.query)
路由传参--params
使用params传递参数,to的对象写法里,必须使用name而不是path
const router = createRouter({
history: createWebHistory(),
routes: [
// 路径中添加冒号变量名作为占位
// 问号代表可选,即可有可无
{ name: 'name-news', path: '/news/:id?', component: News2 },
]
});
接收参数的vue文件里要使用useRoute
import {useRoute} from 'vue-router'
const route = useRoute()
console.log(route.params)
路由工作模式
路由有两种工作模式:history模式,hash模式
history模式的URL更美观,但上线后需要服务端配合处理路径,否则刷新会404
hash模式兼容性好,但带有#不美观,在SEO优化方面相对较差
Pinia
Vue3推荐的集中式状态管理。没有安装过要先安装
npm i pinia
在main.ts中配置
ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');
所有的状态管理,最好都存放在src下的store文件夹中。这是通用标准。
下面是一个案例:
js
// store中的文件count.ts
import { defineStore } from "pinia";
export const useCountStore = defineStore('count', {
actions:{
add(value: number){
this.sum += value
}
},
state() {
return {
sum: 6
}
}
})
// .vue文件中使用
<template>
<div>{{ storeCount.sum }}</div>
<button @click="addCount">加</button>
</template>
<script setup lang="ts" name="Home">
import { useCountStore } from "@/store/count";
const storeCount = useCountStore();
const addCount = () => {
// 修改方法一
// storeCount.sum += 1;
// 修改方法二
//storeCount.$patch({
// sum: storeCount.sum + 1
//})
// 修改方法三
storeCount.add(3)
}
</script>
pinia--storeToRefs
如果想直接使用store中的变量,而不再用store.值的形式,可以用storeToRefs解构出变量。
vue
<template>
<div>{{ sum }}</div>
<button @click="addCount">加</button>
</template>
<script setup lang="ts" name="Home">
// 引入
import { storeToRefs } from "pinia";
import { useCountStore } from "@/store/count";
const storeCount = useCountStore();
const {sum} = storeToRefs(storeCount); // 响应
const addCount = () => {
sum.value += 1;
}
</script>
pinia--getters
在pinia中,也可以不借助action,对数据直接进行处理
ts
import { defineStore } from "pinia";
export const useCountStore = defineStore('count', {
state() {
return {
sum: 6
}
},
getters:{
bigSum: state => state.sum * 3
}
})
.vue文件中的使用
js
<template>
<div>{{ bigSum }}</div>
</template>
<script setup lang="ts" name="Home">
import { storeToRefs } from "pinia";
import { useCountStore } from "@/store/count";
const storeCount = useCountStore();
const {bigSum} = storeToRefs(storeCount);
</script>
pinia--$subscribe
监听。切记,监听的是store变量,不是其中的值。
js
import { useCountStore } from "@/store/count";
const storeCount = useCountStore();
storeCount.$subscribe((mutate, state)=>{
console.log(mutate)
console.log(state)
})
pinia--组合式写法
以上都是选项式写法。组合式写法如下
js
import { defineStore } from "pinia";
import {ref} from 'vue';
export const useCountStore = defineStore('count', ()=>{
let sum = ref(0);
const addCount = () => {
sum.value = sum.value + 1;
}
return {sum, addCount}
})
.vue文件里使用
js
<template>
<div>{{ sum }}</div>
<button @click="storeCount.addCount">加</button>
</template>
<script setup lang="ts" name="Home">
import { storeToRefs } from "pinia";
import { useCountStore } from "@/store/count";
const storeCount = useCountStore();
// 注意这里, 由于storeToRefs, 所以下面解构方法会报错
const {sum} = storeToRefs(storeCount);
</script>
组件通信---props
之前写过,这里再简略写一次。
js
// father.vue
<template>
<div class="index">
<Child :name="name" />
</div>
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue'
let name = 'Tom'
</script>
// Child.vue
<template>
<div>{{ name }}</div>
</template>
<script setup lang="ts" name="Child">
const {name} = defineProps(['name']);
</script>
组件通信---自定义事件
官方推荐:自定义事件名如果由多个单词组成,则单词以短横杠连接。例如:check-ticket
关键词:emit
js
// father.vue
<template>
<div class="index">
{/* 这里用@自定义了一个changeName事件 */}
<Child :name="name" @changeName="changeName" />
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import Child from '@/components/Child.vue'
let name = ref('Tom')
const changeName = () => {
name.value = 'John'
}
// Child.vue
<template>
<div>{{ name }}</div>
{/* $emit用来调用事件 */}
<button @click="emit('changeName')">改名</button>
</template>
<script setup lang="ts" name="Child">
import { onMounted } from 'vue';
const {name} = defineProps(['name']);
const emit = defineEmits(['changeName'])
{/* 代码块中不需要$符号也能用 */}
{/* onMounted(()=>{
emit('changeName');
}) */}
</script>
带参写法:
javascript
// father.vue
const changeName = (value: string) => {
name.value = value
}
// Child.vue
<button @click="emit('changeName', 'John')">改名</button>
组件通信---mitt
mitt相当于第三方。就是组件把事情都交给第三方处理,达成通信。
npm i --save mitt
mitt要全局唯一,不要生成多个实例。
在src下创建tool或utils来存放工具文件。
ts
// utils/emitter
import mitt from "mitt";
const emitter = mitt();
export default emitter;
在vue文件中
js
// 组件1
import emitter from '@/utils/emitter';
const toAdd = () => {
emitter.emit('numAdd');
}
// 组件2
import emitter from '@/utils/emitter';
import { onUnmounted } from 'vue';
emitter.on('numAdd', () => {
console.log('add');
});
// 一定要记得卸载,否则占用内存
onUnmounted(()=>{
emitter.off('numAdd');
});
组件通信---v-model
一般用法。v-model是语法糖,本质上是value和input的组合。
js
<template>
<div>
<input v-model="name" />
<input :value="name" @input="name=(<HTMLInputElement>$event.target).value" />
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
let name = ref('');
watch(name, ()=>{
console.log(name.value)
})
</script>
多组件中也可以直接用v-model,还可以给v-model命名。
js
// 组件1
<template>
<div class="index">
<Child v-model:name="name" v-model:psw="psw"></Child>
</div>
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref, watch } from 'vue';
let name = ref('');
let psw = ref('');
watch(name, () =>{
console.log(name.value)
})
watch(psw, () =>{
console.log(psw.value)
})
</script>
// 组件2
<template>
<input :value="name" @input="emit('update:name', (<HTMLInputElement>$event.target).value)" />
<input :value="psw" @input="emit('update:psw', (<HTMLInputElement>$event.target).value)" />
</template>
<script setup lang="ts" name="Child">
defineProps(['name', 'psw']);
const emit = defineEmits(['update:name','update:psw']);
</script>
组件通信---
Slot---默认插槽
我们之前用组件标签都是自闭合标签。实际上可以在标签内放置其他内容。
javascript
<template>
<div class="index">
<Child>
<div>
插槽内容
</div>
</Child>
</div>
</template>
如果想让子组件中知道这部分包裹内容要放在哪里,需要一个标记,这个标记就是slot。
javascript
<template>
<!-- <slot></slot> -->
<slot>默认内容</slot>
</template>
slot是承接内容的区域。如果没有内容传过来,且slot中不写内容,那么就是空。
没有内容传过来但是写了默认展示的内容,则展示默认内容。
多个插槽标签,意味着有多个展示位。
Slot---具名插槽
父组件中有很多内容,想指定放置到的插槽位置,此时需要具名插槽。
未命名的插槽也是有名字的,叫undefined
javascript
<template>
<div class="index">
// 注意,用的是冒号
<Child v-slot:slot1>
<div>
插槽内容
</div>
</Child>
</div>
</template>
<template>
<slot name="slot1">默认内容</slot>
</template>
Slot---作用域插槽
子类插槽中的数据可以被父类获取
js
// father.vue
<template>
<div class="index">
<Child v-slot="{text}">{{ text }}</Child>
</div>
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
</script>
// Child.vue
<template>
<slot :text="name"></slot>
</template>
<script setup lang="ts" name="Child">
import { ref } from 'vue';
let name = ref('child');
</script>
shallowRef和shallowReactive
只处理第一层的数据,对深层的不做处理。在需要只处理第一层的时候,效率会比ref和reactive要好。
readonly和shallowReadonly
readonly和shallowReadonly都是函数,内部可以接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
原值改变时,只读代理的值也会改变。
只有shallowReadonly返回的只读代理的深层内容才能够改变。
javascript
import { reactive, readonly, shallowReadonly } from 'vue';
let human = reactive({
name: 'tom',
age: 18,
hobby: {
hobby1: 'ball',
hobby2: 'money',
hideHobby: {
h1: 'dance',
}
}
});
let humanConst = readonly(human);
let humanConst2 = shallowReadonly(human);
// humanConst.age = 20; // age无法修改
// humanConst2.hobby['hoby3'] = 'piano'; // 无法找到hoby3
humanConst2.hobby.hideHobby['h1'] = 'piano';
toRaw和markRaw
toRaw函数能把一个响应式的值转为非响应式的值。
markRaw函数能把一个本身非响应式的值,永远无法成为响应式。
customRef
可以更细化地操作响应式。
javascript