vue3+ts项目心得
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 = str1lastName.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、heightwatch([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' }