前端开发初上手
vue 3 学习
组合式API
setup,setup里的this是undifined,最后ret到模版
选项式的数据,方法,就是定义在setup里面的数据函数
setup比beforeCreate的声明周期更早,由于其生命周期很早,因此其他的组件也可以读取到setup里定义的变量/函数,通过this读取,也就是说旧写法能读取新写法的,但是新写法不能读取旧的,也就是setup读不了其他的
箭头函数:例子
return ()=>'hello'
,setup的返回值也可以是一个函数/箭头函数响应式:如果直接定义一个变量,比如
let name = "EPsilon"
,这个就不是响应式的,当我们通过一些事件修改这个变量的时候,虽然日志打印是没问题的,但是前端不会同步响应,要想达到响应式,就得用ref
来定义,比如let name = ref("EPsilon")
setup 语法糖:在
<script>
标签里面写<script setup>
,就相当于写了一个setup
函数vue3 组件可能会有两个
<script>
标签,具体如下:1
2
3
4
5
6
7
8<!-- 这个script标签是为了声明组件名称的,但是这么写稍微有点麻烦,专门为了声明name开一个script -->
<script lang="ts">
export default {
name: 'Person'
}
<script>
<script lang="ts" setup>
<script>可以通过插件变成如下写法:
1
npm install vite-plugin-vue-extend
1
2<script lang="ts" setup name="Person">
<script>此外还要修改
vite.config.ts
文件如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueSetupExtend from "vite-plugin-vue-extend"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
VueSetupExtend()
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})响应式数据:
ref
和reactive
ref
:可以基本类型/对象类型响应式数据,被ref
包裹的是对象,script 里面要加.value
,html 里面自动加不要写(vscode volar 插件可以自动加 value),ref
也不能直接整个替换。reactive
:只能对象类型响应式数据,可以不用加.value
,但是当其重新分配对象(就是直接一整个把对象给换了)时会失去响应式,可以用Object.assign()
接口改。区别就是
ref
是RefImpl
对象,同时里面的value
内嵌了一个Proxy
对象,reactive
就是一个Proxy
对象,也就是说ref
是依赖reactive
实现的。
- 使用原则:基本类型必须
ref
,对象类型层级不深都可以,层级较深最好reactive
。 toRefs
:解构响应式对象的时候使用,比如let {name, age} = person
,name, age
就不是响应式的,这时候let {name, age} = toRefs(person)
,name, age
就是响应式的,而且还是深拷贝toRef
:一次解构一个,let age = toRef(person, 'age')
computed
计算属性,可以用箭头函数,此外计算属性还有缓存,但是方法没有缓存1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 首字母大写,这样定义的计算属性是只读的,不可修改
let fullname = computed(() => {
return firstName.value.slice(0,1).toUppercase() + firstName.value.slice(1)+ '-' + lastName.value
})
// 下面这种是可修改的方案
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
}
})
// fullname = "li-si",执行这条语句的时候会调用 set() 方法,传入val并处理watch
:监视以下四种数据:ref
定义的数据reactive
定义的数据- 函数返回的值 (
getter
函数) 包含上述内容的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 监视ref基本类型数据,注意不是 value 属性,是 ref 本身
let sum = ref(0)
watch(sum, (newValue, oldValue) => {
console.log("sum changed")
})
// 监视ref对象类型数据,监视的是对象的地址(也就是说内部属性改了就不会触发),如果要监视内部属性就要加上 deep: true,如果要在刷新后立即触发就要加上 immediate: true
// 注意:实际开发中一般不管这个 oldValue,当ref对象地址被替换(一整个替换了)的时候 new/old Value才不一样(因为本质还是监视地址),如果只改变了一个属性,newValue和oldValue就是一样的,因为地址没变
let person = ref({name: "", age: 0})
watch(person, (newValue, oldValue) => {
console.log("person changed")
},{
deep: true,
immediate: true
})
// 监视 reative 对象类型数据,我们知道 reactive 对象替换用的是 Object.assign(),这个方法不会改地址,隐式创建深层监听
let person = reactive({name: "", age: 0})
watch(person, (newValue, oldValue) => {
console.log("person changed")
})
// 假如我只想监听一个reactive对象中的某个属性,就要监听一个 getter 函数,也就是有一个返回值的函数,可以直接写箭头函数
let person = reactive({name: "", age: 0})
watch(() => person.name, (newValue, oldValue) => {
console.log("person.name changed")
})
// 假如我向监听一个reactive对象中包含的一个对象,直接写 person.car,监听的就是car的内部属性,隐式创建深层监听,如果写箭头函数,监听的就是person.car的地址,如果既要监听地址又要监听内部属性,就在 getter 函数的基础上手动开启深度监听
let person = reactive({name: "", age: 0, car: {car1: "a", car2: "b"}})
watch(() => person.name, (newValue, oldValue) => {
console.log("person.name changed")
})
// 同时监视多个数据
let person = reactive({name: "", age: 0, car: {car1: "a", car2: "b"}})
watch([()=>person.name,person.car], (newValue, oldValue)=>{
console.log('person.car changed', newValue, oldValue)
},{deep: true})
watchEffect
不用指定监视谁,而是函数中用到哪些属性,那就监视哪些属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let temp = ref(0)
let height = ref(0)
// 用watch实现,需要明确的指出要监视:temp, height, 室温达到50℃,或水位达到20cm,立刻联系服务器
watch([temp,height],(value)=>{
const [newTemp,newHeight] = value
if(newTemp >= 50 || newHeight >= 20){
console.log('联系服务器')
}
})
// 用watchEffect实现,不用明确的指出要监视谁,
const stopWtach = watchEffect(()=>{
if(temp.value >= 50 || height.value >= 20){
console.log(document.getElementById('demo')?.innerText)
console.log('联系服务器')
}
if(temp.value === 100 || height.value === 50){
console.log('清理了')
stopWtach()
}
})标签的
ref
属性用在普通
DOM
标签上,获取的是DOM
节点。用在组件标签上,获取的是组件实例对象。
template 最后是要融合在一起的,如果 id 冲突了就会产生覆盖,这时候要用 ref 新建容器来避免
下面这个写法如果另一个 vue 里面也定义了
id="title2"
,就会出现覆盖1
<h2 id="title2">北京</h2>
因此我们应该用 ref 属性
1
<h2 ref="title2">上海</h2>
用在组件上,返回的就是组件实例,比如,这时候 person 就是一个组件实例,但是被保护了,需要用在子组件用 defineExpose 暴露给父组件
1
<Person ref="person"/>
props
属性1
2
3
4
5
6
7
8
9// 定义一个接口,限制每个Person对象的格式
export interface PersonInter {
id:string,
name:string,
age:number
}
// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>app.vue
提供参数list
1
2
3
4
5
6
7
8
9
10
11
12
13
14import {defineProps} from 'vue'
import {type PersonInter} from '@/types'
// 第一种写法:仅接收
const props = defineProps(['list'])
// 第二种写法:接收+限制类型
defineProps<{list:Persons}>()
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)生命周期/生命周期函数/生命周期钩子
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。
Vue2
的生命周期创建阶段:
beforeCreate
、created
挂载阶段:
beforeMount
、mounted
更新阶段:
beforeUpdate
、updated
销毁阶段:
beforeDestroy
、destroyed
Vue3
的生命周期创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
常用的钩子:
onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前)子组件先挂载,父组件后挂载
hook(模块化开发):
axios
1
2
3
4
5
6
7
8
9// 从指定 URL 获取 message
async function get() {
try{
let res = await axios.get('URL')
list.push(res.data.message)
} catch (error) {
alert(error)
}
}新建一个
hooks
文件夹,然后命名格式是use
开头的 ts 文件export default + 值(立即数,数组,对象,没名字的 function), export + 有名字的 function
一个 hooks 文件例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import {reactive,onMounted} from 'vue'
import axios,{AxiosError} from 'axios'
export default function(){
let dogList = reactive<string[]>([])
// 方法
async function getDog(){
try {
// 发请求
let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
// 维护数据
dogList.push(data.message)
} catch (error) {
// 处理错误
const err = <AxiosError>error
console.log(err.message)
}
}
// 挂载钩子
onMounted(()=>{
getDog()
})
//向外部暴露数据
return {dogList,getDog}
}路由:
route
- 路由时一组
key-value
的对应关系 - 多个路由,需要经过路由器管理
路由变化以后路径改变,然后
router
卸载原先的组件挂载新组件一个页面分为标题,导航区,展示区,有一个文件夹
router
里面一个index.ts
是路由器,还有一个pages
文件夹里面是组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import {createRouter, createWebHistory} from 'vue-router'
// 引入组件
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
// 创建路由器
const router = createRouter({
history:createWebHistory(), // 路由器的工作模式
routes:[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
{
path:'/news',
conponent: News
}
]
})
// 暴露 router
export default routermain.ts
1
2
3import router from './router/index'
app.use(router)
app.mount('#app')app.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/home" active-class="active">首页</RouterLink>
<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
文件夹。通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载。
history
模式优点:
URL
更加美观,不带有#
,更接近传统的网站URL
。缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有
404
错误。1
2
3
4const router = createRouter({
history:createWebHistory(), //history模式
/******/
})hash
模式优点:兼容性更好,因为不需要服务器端处理路径。
缺点:
URL
带有#
不太美观,且在SEO
优化方面相对较差。1
2
3
4const router = createRouter({
history:createWebHashHistory(), //hash模式
/******/
})to
两种写法1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link>
<!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
// 也可以给路由规则命名
{
name:'xinwen',
path:'/news',
component:News,
}
<router-link active-class="active" :to="{name:'news'}">Home</router-link嵌套路由,使用
children
配置项:1
2
3
4
5
6
7
8
9
10
11
12{
name:'xinwen',
path:'/news',
component:News,
children:[
{
name:'xiang',
path:'detail',
component:Detail
}
]
}跳转路由(记得要加完整路径):
1
2
3<router-link to="/news/detail">xxxx</router-link>
<!-- 或 -->
<router-link :to="{path:'/news/detail'}">xxxx</router-link>记得去
Home
组件中预留一个<router-view>
1
2
3
4
5
6
7
8
9
10
11
12<template>
<div class="news">
<nav class="news-list">
<RouterLink v-for="news in newsList" :key="news.id" :to="{path:'/news/detail'}">
{{news.name}}
</RouterLink>
</nav>
<div class="news-detail">
<RouterView/>
</div>
</div>
</template>query 传参:
传递参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- 跳转并携带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>接收参数
1
2
3
4import {useRoute} from 'vue-router'
const route = useRoute()
// 打印query参数
console.log(route.query)
- 路由时一组
- params 参数
- 传递参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 跳转并携带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>
- 接收参数:
1
2
3
4
import {useRoute} from 'vue-router'
const route = useRoute()
// 打印params参数
console.log(route.params)
> 备注1:传递`params`参数时,若使用`to`的对象写法,必须使用`name`配置项,不能用`path`。
>
> 备注2:传递`params`参数时,需要提前在规则中占位。
- props 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
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
}
}
- replace 属性
作用:控制路由跳转时操作浏览器历史记录的模式。
浏览器的历史记录有两种写入方式:分别为```push```和```replace```:
- ```push```是追加历史记录(默认值)。
- `replace`是替换当前记录。
- 编程式导航:路由组件的两个重要的属性:`$route`和`$router`变成了两个`hooks`
1
2
3
4
5
6
7
8
9
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)
- 重定向:将特定的路径,重新定向到已有路由。
1
2
3
4
{
path:'/',
redirect:'/about'
}
pinia
:集中式状态(数据)管理[Pinia | The intuitive store for Vue.js (vuejs.org)](https://pinia.vuejs.org/zh/)
Store
是一个保存:状态、业务逻辑 的实体,每个组件都可以读取、写入它。- 它有三个概念:
state
、getter
、action
,相当于组件中的:data
、computed
和methods
。
存储、读取、修改数据:
需要暴露一个
store
,首先关注state
,里面存的是数据,然后外部组件直接引入useCountStore
即可得到这个 store1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 引入defineStore用于创建store
import {defineStore} from 'pinia'
// 定义并暴露一个store
export const useCountStore = defineStore('count',{
// 动作
actions:{},
// 状态
state(){
return {
sum:6
}
},
// 计算
getters:{}
})修改数据可以单个数据修改,也可以用
store.$patch()
批量修改,还可以借助action
修改(定义成动作可以复用,项目常用)在引入
store
的时候,如果要解构成响应式的数据,不要用toRefs
,这样会把整个store
对象的每个成员都变成ref
,应该用pinia
提供的storeToRefs
来进行解构- 它有三个概念:
getters
:当state
中的数据,需要经过处理后再使用时,可以使用getters
配置,也就是说getters
可以直接读取state
不通过this
,当然也可以通过this
1
2
3
4
5getters:{
bigSum:(state):number => state.sum *10,
upperSchool():string{
return this. school.toUpperCase()
}store.$subscribe
:通过store
的$subscribe()
方法侦听state
及其变化,有点类似watch
1
2
3
4talkStore.$subscribe((mutate,state)=>{
console.log('LoveTalk',mutate,state)
localStorage.setItem('talk',JSON.stringify(talkList.value))
})组合式写法:不写
state, getters, action
,而是直接定义数据、函数然后返回1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import {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}
})
组件通信:分四类来说
props
:使用频率最高的一种通信方式,常用与 :父 ↔ 子。若 父传子:属性值是非函数;若 子传父:属性值是函数,用defineProps
接自定义事件:常用于 子 => 父
原生事件:
- 事件名是特定的(
click
、mosueenter
等等) - 事件对象
$event
: 是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)
- 事件名是特定的(
自定义事件:
- 事件名是任意名称
- 事件对象
$event
: 是调用emit
时所提供的数据,可以是任意类型
示例:
1
2
3
4
5<!--在父组件中,给子组件绑定自定义事件:-->
<Child @send-toy="toy = $event"/>
<!--注意区分原生事件与自定义事件中的$event-->
<button @click="toy = $event">测试</button>1
2//子组件中,触发事件:
this.$emit('send-toy', 具体数据)
mitt
:与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信,其实也就是一个全局变量任意两个组件只要找到emitter
即可通信1
2
3
4
5import mitt from "mitt"
const emitter = mitt()
emitter.on('your-event', (value)=> {console.log(value)}) // 绑定事件
emitter.off() // 清理事件
emitter.emit('your-event', value) // 触发事件v-model
:实现 父↔子 之间相互通信v-model
本质:1
2
3
4
5
6
7
8
9<!-- 使用v-model指令 -->
<input type="text" v-model="userName">
<!-- v-model的本质是下面这行代码 -->
<input
type="text"
:value="userName"
@input="userName =(<HTMLInputElement>$event.target).value"
>组件标签上的
v-modle
本质:1
2
3
4
5
6
7<!-- 组件标签上使用v-model指令,这里v-model后面不跟,默认就是 modelValue -->
<epInput v-model="userName"/>
<epInput v-model: epName="userName"/>
<!-- 组件标签上v-model的本质 -->
<epInput :modelValue="userName" @update:modelValue="userName = $event"/>
<epInput :epName="userName" @update:ep-name="userName = $event"/>epInput
组件内部写法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div class="box">
<!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
<!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
<input
type="text"
:value="epName"
@input="emit('update:ep-value',$event.target.value)"
>
</div>
</template>
<script setup lang="ts" name="epInput">
// 接收props
defineProps(['epName'])
// 声明事件
const emit = defineEmits(['update:ep-name'])
</script>$attrs
:用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。$attrs
是一个对象,包含所有父组件传入的标签属性。父组件传给子组件一个
v-bind
,子组件将其分为两类:props
和attrs
,如果用defineProps
接收,就是props
,孙组件就用不了,如果不接收,就是attrs
,子组件没用的数据就可以通过v-bind: $attrs
传给孙组件注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)refs, parent
:refs
用于 父→子,$parent
用于:子→父。| 属性 | 说明 |
| ————- | ———————————————————————————— |
|$refs
| 值为对象,包含所有被ref
属性标识的DOM
元素或组件实例。 |
|$parent
| 值为对象,当前组件的父组件实例对象。 |provide, inject
:实现祖孙组件直接通信,- 具体使用:
- 在祖先组件中通过
provide
配置向后代组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
- 在祖先组件中通过
- 具体使用:
slot
插槽:一个组件被多次复用,里面显示的内容结构大部分都一致,但是有部分内容有时显示列表,有时是图片,有时又是视频等,可以使用插槽来解决默认插槽:
1
2
3
4
5
6
7
8
9
10
11
12
13
14父组件中:
<Category title="今日热门游戏">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<!-- 默认插槽 -->
<slot></slot>
</div>
</template>具名插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19父组件中:
<Category title="今日热门游戏">
<template v-slot:s1>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
<template #s2>
<a href="">更多</a>
</template>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>作用域插槽:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
- 组件通过
v-slot
把数据传给调用者,但是组件本身没有权利决定这些数据如何展示,而是由调用者决定 - 也可以具名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26父组件中:
<Game v-slot="params">
<!-- <Game v-slot:default="params"> -->
<!-- <Game #default="params"> -->
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</Game>
子组件中:
<template>
<div class="category">
<h2>今日游戏榜单</h2>
<slot :games="games" a="哈哈"></slot>
</div>
</template>
<script setup lang="ts" name="Category">
import {reactive} from 'vue'
let games = reactive([
{id:'asgdytsa01',name:'英雄联盟'},
{id:'asgdytsa02',name:'王者荣耀'},
{id:'asgdytsa03',name:'红色警戒'},
{id:'asgdytsa04',name:'斗罗大陆'}
])
</script>- 组件通过
shallowRef
和shallowReactive
:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的也就是说如果只关注是不是新的整体修改,就用这俩,不考虑内部的成员是不是响应式
通过使用
shallowRef()
和shallowReactive()
来绕开深度响应。浅层式API
创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。readonly
作用:用于创建一个对象的深只读副本。
用法:
1
2const original = reactive({ ... });
const readOnlyCopy = readonly(original);特点:
- 对象的所有嵌套属性都将变为只读。
- 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
应用场景:
- 创建不可变的状态快照。
- 保护全局状态或配置不被修改。
shallowReadonly
作用:与
readonly
类似,但只作用于对象的顶层属性。用法:
1
2const original = reactive({ ... });
const shallowReadOnlyCopy = shallowReadonly(original);特点:
只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。
适用于只需保护对象顶层属性的场景。
toRaw
:用于获取一个响应式对象的原始对象,toRaw
返回的对象不再是响应式的,不会触发视图更新。官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
何时使用? —— 在需要将响应式对象传递给非
Vue
的库或外部系统时,使用toRaw
可以确保它们收到的是普通对象markRaw
:作用:标记一个对象,使其永远不会变成响应式的。例如使用
mockjs
时,为了防止误把mockjs
变为响应式对象,可以使用markRaw
去标记mockjs
customRef
:创建一个自定义的ref
,并对其依赖项跟踪和更新触发进行逻辑控制,一般是写入hooks
方便复用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import {customRef } from "vue";
export default function(initValue:string,delay:number){
let msg = customRef((track,trigger)=>{
let timer:number
return {
get(){
track() // 告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
return initValue
},
set(value){
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() //通知Vue数据msg变化了
}, delay);
}
}
})
return {msg}
}Teleport
:一种能够将我们的组件html结构移动到指定位置的技术。1
2
3
4
5
6
7<teleport to='body' >
<div class="modal" v-show="isShow">
<h2>我是一个弹窗</h2>
<p>我是弹窗中的一些内容</p>
<button @click="isShow = false">关闭弹窗</button>
</div>
</teleport>suspense
:等待异步组件时渲染一些额外内容,让应用有更好的用户体验使用步骤:
异步引入组件
使用
Suspense
包裹组件,并配置好default
与fallback
其他 API:
app.component
app.config
app.directive
app.mount
app.unmount
app.use
- 过渡类名
v-enter
修改为v-enter-from
、过渡类名v-leave
修改为v-leave-from
。 keyCode
作为v-on
修饰符的支持。v-model
指令在组件上的使用已经被重新设计,替换掉了v-bind.sync。
v-if
和v-for
在同一个元素身上使用时的优先级发生了变化。- 移除了
$on
、$off
和$once
实例方法。 - 移除了过滤器
filter
。 - 移除了
$children
实例propert
。
Typescript 学习
一些JS要注意的点,比如
==
符号,还有类似1<x<3
的表达式这种坑推断类型,声明一个对象以后可以用
interface
声明类型,然后在变量声明后使用类似: TypeName
的语法来声明 JavaScript 对象符合新interface
的形状,此外也支持面向对象构建类型有两种语法,一种是interface,一种是type,常用这两种类型可以相互转化和扩展,但是interface支持多次定义,当多次定义的时候interface会自动合并这些成员类型,但是type不会
组合类型有联合和泛型,联合的意思如下:
type MyBool = true | false;
,就是用|
符号连接,泛型有点类似cpp里面的template
结构类型系统:TypeScript 的核心原则之一是类型检查侧重于值的形状。这有时称为 “duck typing” 或 “结构类型”。
在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。例如一个对象定义的时候没有声明类型,但是如果其被调用的时候符合
interface
声明的格式,或者声明格式是该对象的一个子集,也能通过类型检查typescript支持静态类型检查以及检查非异常错误,比如拼写错误,undefine错误等
类型:
string, boolean, number, array<type> or type[], any
类型注释:
const, let, var
匿名函数:
1
2
3
4
5
6
7
8
9
10const names = ["Alice", "Bob", "Eve"];
// 即使参数 s 没有类型注释,TypeScript 还是使用 forEach 函数的类型以及推断的数组类型来确定 s 将具有的类型。这个过程称为上下文类型,因为函数发生的上下文告知它应该具有什么类型。
names.forEach(function (s) {
console.log(s.toUpperCase());
});
names.forEach((s) => {
console.log(s.toUpperCase());
});