1、Vue3核心
1、setup
setup里弱化this,return可以返回函数,返回后页面也显示那个函数值
data里面是可以用this.来获取setup里的值,这个是单向的
vue3两个script标签不要觉得奇怪,一个是配置组合式api的,一个是配置组件名字的(需要下载插件vite-plugin-vue-setup-extend -D)
OptionsAPI(vue2) 与 CompositionAPI(vue3)
<script lang="ts" setup name="Person">
vite.config.ts里正常引入
2、ref和reactive
vue2里data中的数据已经自动数据代理和数据劫持,vue3中ref是函数,所以script标签里用.value
ref======>可以定义:基本类型、对象类型的响应式数据
reactive:=========>只能定义:对象类型的响应式数据
reactive处理函数是深层次的,不管哪一层都是响应式
为什么ref可以处理对象?假如ref处理对象的话,里面还是proxy对象,也就是ref中使用了reactive,reactive不需要
到底何时用ref和reactive?
ref
用来定义:基本类型数据、对象类型数据;reactive
用来定义:对象类型数据。
区别:
ref
创建的变量必须使用.value
(可以使用volar
插件自动添加.value
)。reactive
重新分配一个新对象,会失去响应式(可以使用Object.assign
去整体替换)。
使用原则:
- 若需要一个基本类型的响应式数据,必须使用
ref
。- 若需要一个响应式对象,层级不深,
ref
、reactive
都可以。- 若需要一个响应式对象,且层级较深,推荐使用
reactive
。
reactive失去响应式,用…也可以
(想到了以前开发的时候,改不动对象,改用ref去修改,醍醐灌顶)
3、toRef
解构赋值中name,age非响应式,需要使用到toRefs
name打印值
person打印值
toRefs后的值需要用.value
name.value===person.name
toRefs接收reactive响应式对象,并且把对象key、value拎出来形成新的对象,值来自于person
4、computed计算属性
{{fullName }}//是ComputedRefImpl
// fullName是一个计算属性,且是只读的
let fullName = computed(()=>{
console.log(1)
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
})
假如计算属性需要修改的话需要以下方式书写
// fullName是一个计算属性,可读可写
let fullName = computed({
// 当fullName被读取时,get调用
get(){
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
},
// 当fullName被修改时,set调用,且会收到修改的值
set(val){
const [str1,str2] = val.split('-')
firstName.value = str1
lastName.value = str2
}
})
5、watch监听
能监视四种属性
官网上,Vue3
中的watch
只能监视以下四种数据:
ref
定义的数据。reactive
定义的数据。- 函数返回一个值(
getter
函数)。- 一个包含上述内容的数组。
5.1、情况一
监视ref
定义的【基本类型】数据:直接写数据名即可,监视的是其value
值的改变,不需要.value
<template>
<div class="person">
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
// 监视,情况一:监视【ref】定义的【基本类型】数据
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>
stopWatch
5.2、情况二
监视ref
定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
若修改的是
ref
定义的对象中的属性,newValue
和oldValue
都是新值,因为它们是同一个对象。
若修改整个ref
定义的对象,newValue
是新值,oldValue
是旧值,因为不是同一个对象了。
/*
监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
watch的第一个参数是:被监视的数据
watch的第二个参数是:监视的回调
watch的第三个参数是:配置对象(deep、immediate等等.....)
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:true})
</script>
5.3、情况三
监视reactive
定义的【对象类型】数据,且默认开启了深度监视,{deep:false}关不掉。
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
let obj = reactive({
a:{
b:{
c:666
}
}
})
watch(obj,(newValue,oldValue)=>{
console.log('Obj变化了',newValue,oldValue)
})
function changeName(){
person.value.name += '~'
}
watch(person,(newValue,oldValue)=>{//没有创建新对象,所以值相同
console.log('person变化了',newValue,oldValue)
})
</script>
5.4、情况四
监视ref
或reactive
定义的【对象类型】数据中的某个属性,注意点如下:
- 若该属性值不是【对象类型】,需要写成函数形式。
- 若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数(但是这个对象被替换重新赋值了会有问题,无法监听person.car=xxx;person.car.c1属性是可以监听的)。
结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
/* watch(()=> person.name,(newValue,oldValue)=>{
console.log('person.name变化了',newValue,oldValue)
}) */
// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
</script>
5.5、情况五
监视上述的多个数据
//car是对象或者这么写[()=>person.name,()=>person.car]
watch([()=>person.name,person.car],(newValue,oldValue)=>{//newValue是['张三','奥迪']
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
6、watchEffect
官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
watch
对比watchEffect
- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch
:要明确指出监视的数据watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
假如数据很多用watchEffect
<script lang="ts" setup name="Person">
import {ref,watch,watchEffect} from 'vue'
// 数据
let temp = ref(0)
let height = ref(0)
// 用watch实现,需要明确的指出要监视:temp、height
watch([temp,height],(value)=>{
// 从value中获取最新的temp值、height值
const [newTemp,newHeight] = value
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(newTemp >= 50 || newHeight >= 20){
console.log('联系服务器')
}
})
// 用watchEffect实现,不用
const stopWtach = watchEffect(()=>{
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(temp.value >= 50 || height.value >= 20){
console.log(document.getElementById('demo')?.innerText)
console.log('联系服务器')
}
// 水温达到100,或水位达到50,取消监视
if(temp.value === 100 || height.value === 50){
console.log('清理了')
stopWtach()
}
})
</script>
7、标签的 ref 属性
作用:用于注册模板引用。
- 用在普通
DOM
标签上,获取的是DOM
节点。- 用在组件标签上,获取的是组件实例对象。
data-v是局部样式,scoped
<h1 ref="title1">尚硅谷</h1>//用在普通`DOM`标签上
<script lang="ts" setup name="Person">
let title1 = ref()
function showLog(){
// 通过ref获取元素
console.log(title1.value)
}
</script>
组件实例对象,看不见变量,打印是Proxy,这是保护措施
<!-- 父组件App.vue -->
<template>
<Person ref="ren"/>
<button @click="test">测试</button>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {ref} from 'vue'
let ren = ref()
function test(){
console.log(ren.value.name)
console.log(ren.value.age)
}
</script>
<!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script lang="ts" setup name="Person">
import {ref,defineExpose} from 'vue'
// 数据
let name = ref('张三')
let age = ref(18)
/****************************/
/****************************/
// 使用defineExpose将组件中的数据交给外部
defineExpose({name:name.value,age})
</script>
defineExpose
8、接口泛型自定义类型
person.vue
<template>
<div class="person"></div>
</template>
<script lang="ts" setup name="Person">
import {type PersonInter} from "@/types";
import {type Persons} from "@/types";
let person:PersonInter = {
name:'张三',
id:'18',
age:1
}
// let personList:Array<PersonInter> = [{
// name:'张三',
// id:'18',
// age:1
// }]
let personList:Persons= [{
name:'张三',
id:'18',
age:1
}]
console.log(person,personList)
</script>
types/index.ts
export interface PersonInter {
id:string,
name:string,
age:number
}
//export type Persons=Array<PersonInter>也可以这么写
export type Persons=PersonInter[]
9、props
// 定义一个接口,限制每个Person对象的格式 export interface PersonInter { id:string, name:string, age:number } // 定义一个自定义类型Persons export type Persons = Array<PersonInter>
App.vue
中代码:<template> <Person :list="persons"/> </template> <script lang="ts" setup name="App"> import Person from './components/Person.vue' import {reactive} from 'vue' import {type Persons} from './types' let persons = reactive<Persons>([//少用let persons:Persons = reactive([ {id:'e98219e12',name:'张三',age:18}, {id:'e98219e13',name:'李四',age:19}, {id:'e98219e14',name:'王五',age:20} ]) </script>
Person.vue
中代码:<template> <div class="person"> <ul> <li v-for="item in list" :key="item.id"> {{item.name}}--{{item.age}} </li> </ul> </div> </template> <script lang="ts" setup name="Person"> import {defineProps} from 'vue' import {type PersonInter} from '@/types' // 第一种写法:仅接收 // const props = defineProps(['list']) //不赋值变量,js无法使用props,打印的是Proxy(Object){a: '哈哈”} // 第二种写法:接收+限制类型(收的值是list,类型的Persons的一个对象) // defineProps<{list:Persons}>() // 第三种写法:接收+限制类型+指定默认值+限制必要性(?可以不传,不传需要默认值,需要withDefaults包裹第二步,()=>需要返回函数) let props = withDefaults(defineProps<{list?:Persons}>(),{ list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}] }) console.log(props) </script>
define开头的宏函数可以不用引入
10、生命周期
-
概念:
Vue
组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue
会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子 -
Vue2
的生命周期创建阶段:
beforeCreate
、created
挂载阶段:beforeMount
、mounted
更新阶段:beforeUpdate
、updated
销毁阶段:beforeDestroy
、destroyed
-
Vue3
的生命周期(执行顺序先子)创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
-
常用的钩子:
onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前)<script lang="ts" setup name="Person"> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' // 数据 let sum = ref(0) // 方法 function changeSum() { sum.value += 1 } console.log('setup') // 生命周期钩子 onBeforeMount(()=>{ console.log('挂载之前') }) onMounted(()=>{ console.log('挂载完毕') }) onBeforeUpdate(()=>{ console.log('更新之前') }) onUpdated(()=>{ console.log('更新完毕') }) onBeforeUnmount(()=>{ console.log('卸载之前') }) onUnmounted(()=>{ console.log('卸载完毕') }) </script>
11、自定义hook
什么是hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。
自定义hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂,相当于vue2的mixin,命名规则是usexxx。
hooks/useSum.ts
中内容如下:
import { ref ,onMounted,computed} from 'vue'
export default function () {
const sum=ref(1)
const bigSum=computed(()=>sum.value*10)
onMounted(()=>{
})
function add() {
sum.value+=1;
}
return {sum,add,bigSum}
}
hooks/useDog.ts
中内容如下:
import {reactive,onMounted} from 'vue'
import axios from 'axios'
export default function (){
// 数据
const dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'
])
// 方法
async function getDog(){
try {
const result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
} catch (error) {
alert(error)
}
}
// 钩子
onMounted(()=>{
getDog().then(() => {})
})
// 向外部提供东西
return {dogList,getDog}
}
组件中具体使用:
<template>
<div class="person">
<h2>当前求和为:{{ sum }},放大10倍后:{{ bigSum }}</h2>
<button @click="add">点我sum+1</button>
<hr>
<img v-for="(dog,index) in dogList" :src="dog" :key="index" alt="">
<br>
<button @click="getDog">再来一只小狗</button>
</div>
</template>
<script lang="ts" setup name="Person">
import useSum from '@/hooks/useSum'
import useDog from '@/hooks/useDog'
const {sum,add,bigSum} = useSum()
const {dogList,getDog} = useDog()
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
img {
height: 100px;
margin-right: 10px;
}
</style>
2、路由
1、基础路由
router/index.ts
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入一个一个可能要呈现组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
// 第二步:创建路由器
const router = createRouter({
history:createWebHistory(), //路由器的工作模式(稍后讲解)
routes:[ //一个一个的路由规则
{
path:'/home',
component:Home
},
{
path:'/news',
component:News
},
{
path:'/about',
component:About
},
]
})
// 暴露出去router
export default router
main.ts
import router from './router/index'
app.use(router)
app.mount('#app')
App.vue
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/home" active-class="active">首页</RouterLink>
//<router-link active-class="active" :to="{path:'/home'}">首页</router-link>
//第二种写法命名路由,也可以name跳转
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterLink to="/about" active-class="active">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup name="App">
import {RouterLink,RouterView} from 'vue-router'
</script>
- 路由组件通常存放在
pages
或views
文件夹,一般组件通常存放在components
文件夹。 - 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载onUnMounted掉的,需要的时候再去挂载onMounted。
2、工作模式
//history优点:`URL`更加美观,不带有`#`,更接近传统的网站`URL`。
//缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有`404`错误。
const router = createRouter({
history:createWebHistory(),
})
//hash优点:兼容性更好,因为不需要服务器端处理路径。
//缺点:`URL`带有`#`不太美观,且在`SEO`优化方面相对较差。
history:createWebHashHistory()
3、路由传参
query参数
- 传递参数
<!-- 跳转并携带query参数(to的字符串写法) --> <router-link to="/news/detail?a=1&b=2&content=欢迎你"> 跳转 </router-link> <!-- 跳转并携带query参数(to的对象写法) --> <RouterLink :to="{ //name:'xiang', //用name也可以跳转 path:'/news/detail', query:{ id:news.id, title:news.title, content:news.content } }" > {{news.title}} </RouterLink>
- 接收参数:
import {useRoute} from 'vue-router' const route = useRoute()//hooks // 打印query参数 const {query}= toRefs(route)
params参数
- 传递参数
<!-- 跳转并携带params参数(to的字符串写法) --> <RouterLink :to="`/news/detail/001/新闻001/内容001`">{{news.title}}</RouterLink> <!-- 跳转并携带params参数(to的对象写法) --> <RouterLink :to="{ name:'xiang', //用name跳转 params:{ id:news.id, title:news.title, content:news.title } }" > {{news.title}} </RouterLink>
- 接收参数:
import {useRoute} from 'vue-router' const route = useRoute() // 打印params参数 console.log(route.params)
备注1:传递
params
参数时,若使用to
的对象写法,必须使用name
配置项,不能用path
。query两个都可以备注2:传递
params
参数时,需要提前在规则中占位。
/:content?表示可传可不传
4、路由的props配置
让路由组件更方便的收到参数(可以将路由参数作为props
传给组件)
{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// props的对象写法,很少写,作用:把对象中的每一组key-value作为props传给Detail组件
// props:{a:1,b:2,c:3},
// props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
// props:true
// props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
props(route){
return route.query
}
}
defineProps(['id','title','content'])
相当于<detail :id="id" :title="title" :content="content">
5、replace属性
-
作用:控制路由跳转时操作浏览器历史记录的模式。
-
浏览器的历史记录有两种写入方式:分别为
push
和replace
:push
是追加历史记录(默认值)。replace
是替换当前记录。
-
开启
replace
模式:<RouterLink replace .......>News</RouterLink>
6、编程式导航
路由组件的两个重要的属性:$route
和$router
变成了两个hooks
import {useRoute,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)
7、重定向
- 作用:将特定的路径,重新定向到已有路由。
- 具体编码:
{ path:'/', redirect:'/about' }
3、pinia
3.1、读取
nanoid或者uuid库自动生成ID
<script setup lang="ts" name="LoveTalk">
import {reactive} from 'vue'
import axios from "axios";
import {nanoid} from 'nanoid'
// 数据
let talkList = reactive([
{id:'ftrfasdf01',title:'今天你有点怪,哪里怪?怪好看的!'},
{id:'ftrfasdf02',title:'草莓、蓝莓、蔓越莓,今天想我了没?'},
{id:'ftrfasdf03',title:'心里给你留了一块地,我的死心塌地'}
])
// 方法
async function getLoveTalk(){
// 发请求,下面这行的写法是:连续解构赋值+重命名
const {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
//{data:{content}}
// 把请求回来的字符串,包装成一个对象,title对象简写
const obj = {id:nanoid(),title}
// 放到数组中
talkList.unshift(obj)
}
</script>
src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
/* 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'
/* 创建pinia */
const pinia = createPinia()
const app = createApp(App)
/* 使用插件 */{}
app.use(pinia)
app.mount('#app')
它有三个概念:state
、getter
、action
,相当于组件中的: data
、 computed
和 methods
。
store//count.ts
// 引入defineStore用于创建store
import {defineStore} from 'pinia'
// 定义并暴露一个store,官方推荐用useXXX
export const useCountStore = defineStore('count',{
// 动作
actions:{},
// 状态
state(){//vue2是state:{}
return {
sum:6
}
},
// 计算
getters:{}
})
<template>
<h2>当前求和为:{{ sumStore.sum }}</h2>
</template>
<script setup lang="ts" name="Count">
// 引入对应的useXxxxxStore
import {useSumStore} from '@/store/sum'
// 调用useXxxxxStore得到对应的store,countStore.sum或者countStore.$state.sum可以读取
const sumStore = useSumStore()
</script>
countStore
不能通过countStore.sum.value的理由同下,object.c直接获取,为什么?
reactive里面的ref不需要.value
3.2、修改
- 第一种修改方式,直接修改
countStore.sum = 666//vue2里面需要this.$commit
- 第二种修改方式:批量修改
countStore.$patch({
sum:999,
school:'atguigu'
})
- 第三种修改方式:借助
action
修改(action
中可以编写一些业务逻辑)
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
/*************/
actions: {
//加
increment(value:number) {
if (this.sum < 10) {
//操作countStore中的sum
this.sum += value
}
},
//减
decrement(value:number){
if(this.sum > 1){
this.sum -= value
}
}
},
/*************/
})
组件中调用action
即可
// 使用countStore
const countStore = useCountStore()
// 调用对应action
countStore.incrementOdd(n.value)
3.3、storeToRefs
数量一多就会重复countStore.
- 借助
storeToRefs
将store
中的数据转为ref
对象,方便在模板中使用。 - 注意:
pinia
提供的storeToRefs
只会将数据做转换,而Vue
的toRefs
会转换store
中数据(toRefs会把store对象所有都改成ref,代价太大)。
<template>
<div class="count">
<h2>当前求和为:{{sum}}</h2>
</div>
</template>
<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count'
/* 引入storeToRefs */
import { storeToRefs } from 'pinia'
/* 得到countStore */
const countStore = useCountStore()
/* 使用storeToRefs转换countStore,随后解构 */
const {sum} = storeToRefs(countStore)
</script>
3.4、getters
- 概念:当
state
中的数据,需要经过处理后再使用时,可以使用getters
配置。 - 追加
getters
配置。
// 引入defineStore用于创建store
import {defineStore} from 'pinia'
// 定义并暴露一个store
export const useCountStore = defineStore('count',{
// 状态
state(){
return {
sum:1,
school:'atguigu'
}
},
// 计算
getters:{
bigSum(state){return state.sum*10}
bigSum:(state):number => state.sum*10,
upperSchool():string{
return this. school.toUpperCase()//this就是store,this.不能用箭头函数
}
}
})
- 组件中读取数据:
const {increment,decrement} = countStore
let {sum,school,bigSum,upperSchool} = storeToRefs(countStore)
3.5、$subscribe
通过 store 的 $subscribe()
方法侦听 state
及其变化
const talkStore= useTalkStore()
talkStore.$subscribe((mutate,state)=>{
console.log('LoveTalk',mutate,state)
localStorage.setItem('talk',JSON.stringify(talkList.value))
})
3.6、store组合式写法
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'
export const useTalkStore = defineStore('talk',()=>{
// talkList就是state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
// getATalk函数相当于action
async function getATalk(){
// 发请求,下面这行的写法是:连续解构赋值+重命名
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// 把请求回来的字符串,包装成一个对象
let obj = {id:nanoid(),title}
// 放到数组中
talkList.unshift(obj)
}
return {talkList,getATalk}
})