這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
vue3 于 2020 年 09 月 18 日正式發布,2022 年 2 月 7 日 vue3 成為新的默認版本
距離 vue3 正式發布已經過去兩年有余, 成為默認版本也過去大半年了,以前還能說是對新技術、新特性的觀望,而現在面試都直問 vue3 原始碼了,
我想,不管什么原因,是時候學習 vue3 了
所以這次我也順便把學習的程序記錄下來,算個總結,也便于日后的梳理,
前置介紹
在 vue3.2 中,我們只需在script標簽中添加setup,就可以做到,組件只需引入不用注冊,屬性和方法也不用 return 才能于 template 中使用,也不用寫setup函式,也不用寫export default ,甚至是自定義指令也可以在我們的template中自動獲得,
本次我們的學習也將在 setup 語法糖下進行,
環境搭建
npm init vue@latest
使用工具
<script setup lang="ts"> + VSCode + Volar
安裝 Volar 后,注意禁用 vetur
好的,準備作業已經完成,下面我們開始進入到 vue3 setup 的正式學習
ref 和 reactive
- ref: 用來給基本資料型別系結回應式資料,訪問時需要通過 .value 的形式, tamplate 會自動決議,不需要 .value
- reactive: 用來給 復雜資料型別 系結回應式資料,直接訪問即可
ref其實也是內部呼叫 reactive 來實作的
<template>
<div>
<p>{{title}}</p>
<h4>{{userInfo}}</h4>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
type Person = {
name: string;
age: number;
gender?: string;
};
const title = ref<string>("彼時彼刻,恰如此時此刻");
const userInfo = reactive<Person>({
name: '樹哥',
age: 18
})
</script>
toRef、toRefs、toRaw
toRef
toRef 如果原始物件是非回應式的,資料會變,但不會更新視圖
<template>
<div>
<button @click="change">按鈕</button>
{{state}}
</div>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const obj = {
name: '樹哥',
age: 18
}
const state = toRef(obj, 'age')
const change = () => {
state.value++
console.log('obj:',obj,'state:', state);
}
</script>

可以看到,點擊按鈕,當原始物件是非回應式時,使用toRef 的資料改變,但是試圖并沒有更新
<template>
<div>
<button @click="change">按鈕</button>
{{state}}
</div>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const obj = reactive({
name: '樹哥',
age: 18
})
const state = toRef(obj, 'age')
const change = () => {
state.value++
console.log('obj:', obj, 'state:', state);
}
</script>

當我們把 obj 用 reactive 包裹,再使用 toRef,點擊按鈕時,可以看到視圖和資料都變了
toRef回傳的值是否具有回應性取決于被解構的物件本身是否具有回應性,回應式資料經過toRef回傳的值仍具有回應性,非回應式資料經過toRef回傳的值仍沒有回應性,
toRefs
toRefs相當于對物件內每個屬性呼叫toRef,toRefs回傳的物件內的屬性使用時需要加.value,主要是方便我們解構使用
<template>
<div>
<button @click="change">按鈕</button>
name--{{name}}---age{{age}}
</div>
</template>
<script setup lang="ts">
import { reactive, toRefs } from 'vue'
const obj = reactive({
name: '樹哥',
age: 18
})
let { name, age } = toRefs(obj)
const change = () => {
age.value++
name.value = 'https://www.cnblogs.com/smileZAZ/archive/2023/03/10/張麻子'
console.log('obj:', obj);
console.log('name:', name);
console.log('age:', age);
}
</script>
簡單理解就是批量版的toRef,(其原始碼實作也正是通過物件回圈呼叫了toRef)
toRaw
將回應式物件修改為普通物件
<template>
<div>
<button @click="change">按鈕</button>
{{data}}
</div>
</template>
<script setup lang="ts">
import { reactive, toRaw } from 'vue'
const obj = reactive({
name: '樹哥',
age: 18
})
const data = https://www.cnblogs.com/smileZAZ/archive/2023/03/10/toRaw(obj)
const change = () => {
data.age = 19
console.log('obj:', obj, 'data:', data);
}
</script>

資料能變化,視圖不變化(失去回應式)
computed
<template>
<div>
<p>{{title}}</p>
<h4>{{userInfo}}</h4>
<h1>{{add}}</h1>
</div>
</template>
<script setup lang="ts">
import { ref, reactive,computed } from "vue";
const count = ref(0)
// 推導得到的型別:ComputedRef<number>
const add = computed(() => count.value +1)
</script>
watch
vue3 watch 的作用和 Vue2 中的 watch 作用是一樣的,他們都是用來監聽回應式狀態發生變化的,當回應式狀態發生變化時,就會觸發一個回呼函式,
watch(data,()=>{},{})
-
參數一,監聽的資料
-
引數二,資料改變時觸發的回呼函式(newVal,oldVal)
-
引數三,options配置項,為一個物件
-
1、監聽ref定義的一個回應式資料
<script setup lang="ts">
import { ref, watch } from "vue";
const str = ref('彼時彼刻')
//3s后改變str的值
setTimeout(() => { str.value = 'https://www.cnblogs.com/smileZAZ/archive/2023/03/10/恰如此時此刻' }, 3000)
watch(str, (newV, oldV) => {
console.log(newV, oldV) //恰如此時此刻 彼時彼刻
})
</script>
- 2、監聽多個ref
這時候寫法變為陣列的形式
<script setup lang="ts">
import { ref, watch } from "vue";
let name = ref('樹哥')
let age = ref(18)
//3s后改變值
setTimeout(() => {
name.value = 'https://www.cnblogs.com/smileZAZ/archive/2023/03/10/我叫樹哥'
age.value = https://www.cnblogs.com/smileZAZ/archive/2023/03/10/19
}, 3000)
watch([name, age], (newV, oldV) => {
console.log(newV, oldV) // ['我叫樹哥', 19] ['樹哥', 18]
})
</script>
- 3、監聽Reactive定義的回應式物件
<script setup lang="ts">
import { reactive, watch } from "vue";
let info = reactive({
name: '樹哥',
age: 18
})
//3s后改變值
setTimeout(() => {
info.age = 19
}, 3000)
watch(info, (newV, oldV) => {
console.log(newV, oldV)
})
</script>
當 watch 監聽的是一個回應式物件時,會隱式地創建一個深層偵聽器,即該回應式物件里面的任何屬性發生變化,都會觸發監聽函式中的回呼函式,即當 watch 監聽的是一個回應式物件時,默認開啟 deep:true
- 4、監聽reactive 定義回應式物件的單一屬性
錯誤寫法:
<script setup lang="ts">
import { reactive, watch } from "vue";
let info = reactive({
name: '樹哥',
age: 18
})
//3s后改變值
setTimeout(() => {
info.age = 19
}, 3000)
watch(info.age, (newV, oldV) => {
console.log(newV, oldV)
})
</script>
可以看到控制臺出現警告
[Vue warn]: Invalid watch source: 18 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types. at <Index> at <App>
如果我們非要監聽回應式物件中的某個屬性,我們可以使用 getter 函式的形式,即將watch第一個引數修改成一個回呼函式的形式
正確寫法:
// 其他不變
watch(()=>info.age, (newV, oldV) => {
console.log(newV, oldV) // 19 18
})
- 5、監聽reactive定義的 參考資料
<script setup lang="ts">
import { reactive, watch } from "vue";
let info = reactive({
name: '張麻子',
age: 18,
obj: {
str: '彼時彼刻,恰如此時此刻'
}
})
//3s后改變s值
setTimeout(() => {
info.obj.str = 'to be or not to be'
}, 3000)
// 需要自己開啟 deep:true深度監聽,不然不發觸發 watch 的回呼函式
watch(() => info.obj, (newV, oldV) => {
console.log(newV, oldV)
}, {
deep: true
})
</script>
WatchEffect
會立即執行傳入的一個函式,同時回應式追蹤其依賴,并在其依賴變更時重新運行該函式,(有點像計算屬性)
如果用到 a 就只會監聽 a, 就是用到幾個監聽幾個 而且是非惰性,會默認呼叫一次
<script setup lang="ts">
import { ref, watchEffect } from "vue";
let num = ref(0)
//3s后改變值
setTimeout(() => {
num.value++
}, 3000)
watchEffect(() => {
console.log('num 值改變:', num.value)
})
</script>
可以在控制臺上看到,第一次進入頁面時,列印出num 值改變:0,三秒后,再次列印num 值改變:1
- 停止監聽
當 watchEffect 在組件的 setup() 函式或生命周期鉤子被呼叫時,偵聽器會被鏈接到該組件的生命周期,并在組件卸載時自動停止,
但是我們采用異步的方式創建了一個監聽器,這個時候監聽器沒有與當前組件系結,所以即使組件銷毀了,監聽器依然存在,
這個時候我們可以顯式呼叫停止監聽
<script setup lang="ts">
import { watchEffect } from 'vue'
// 它會自動停止
watchEffect(() => {})
// ...這個則不會!
setTimeout(() => {
watchEffect(() => {})
}, 100)
const stop = watchEffect(() => {
/* ... */
})
// 顯式呼叫
stop()
</script>
- 清除副作用(onInvalidate)
watchEffect 的第一個引數——effect函式——可以接收一個引數:叫onInvalidate,也是一個函式,用于清除 effect 產生的副作用
就是在觸發監聽之前會呼叫一個函式可以處理你的邏輯,例如防抖
import { ref, watchEffect } from "vue";
let num = ref(0)
//3s后改變值
setTimeout(() => {
num.value++
}, 3000)
watchEffect((onInvalidate) => {
console.log(num.value)
onInvalidate(() => {
console.log('執行');
});
})
控制臺依次輸出:0 => 執行 => 1
- 配置選項
watchEffect的第二個引數,用來定義副作用重繪時機,可以作為一個除錯器來使用
flush (更新時機):
- 1、pre:組件更新前執行
- 2、sync:強制效果始終同步觸發
- 3、post:組件更新后執行
<script setup lang="ts">
import { ref, watchEffect } from "vue";
let num = ref(0)
//3s后改變值
setTimeout(() => {
num.value++
}, 3000)
watchEffect((onInvalidate) => {
console.log(num.value)
onInvalidate(() => {
console.log('執行');
});
}, {
flush: "post", //此時這個函式會在組件更新之后去執行
onTrigger(e) { //作為一個除錯工具,可在開發中方便除錯
console.log('觸發', e);
},
})
</script>
生命周期
和 vue2 相比的話,基本上就是將 Vue2 中的beforeDestroy名稱變更成beforeUnmount; destroyed 表更為 unmounted;然后用setup代替了兩個鉤子函式 beforeCreate 和 created;新增了兩個開發環境用于除錯的鉤子

父子組件傳參
defineProps
父組件傳參
<template>
<Children :msg="msg" :list="list"></Children>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import Children from './Children.vue'
const msg = ref('hello 啊,樹哥')
const list = reactive<number[]>([1, 2, 3])
</script>
在 script setup 中,引入的組件會自動注冊,所以可以直接使用,無需再通過components進行注冊
子組件接受值
defineProps 來接收父組件傳遞的值, defineProps是無須引入的直接使用即可
<template>
<div>
<p>msg:{{msg}}</p>
<p>list:{{list}}</p>
</div>
</template>
<script setup lang="ts">
defineProps<{
msg: string,
list: number[]
}>()
</script>
使用 withDefaults 定義默認值
<template>
<div>
<p>msg:{{msg}}</p>
<p>list:{{list}}</p>
</div>
</template>
<script setup lang="ts">
type Props = {
msg?: string,
list?: number[]
}
// withDefaults 的第二個引數便是默認引數設定,會被編譯為運行時 props 的 default 選項
withDefaults(defineProps<Props>(), {
msg: '張麻子',
list: () => [4, 5, 6]
})
</script>
子組件向父組件拋出事件
defineEmits
子組件派發事件
<template>
<div>
<p>msg:{{msg}}</p>
<p>list:{{list}}</p>
<button @click="onChangeMsg">改變msg</button>
</div>
</template>
<script setup lang="ts">
type Props = {
msg?: string,
list?: number[]
}
withDefaults(defineProps<Props>(), {
msg: '張麻子',
list: () => [4, 5, 6]
})
const emits = defineEmits(['changeMsg'])
const onChangeMsg = () => {
emits('changeMsg','黃四郎')
}
</script>
子組件系結了一個click 事件 然后通過defineEmits 注冊了一個自定義事件,點擊按鈕的時候,觸發 emit 呼叫我們注冊的事件,傳遞引數
父組件接收
<template>
<Children :msg="msg" :list="list" @changeMsg="changeMsg"></Children>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import Children from './Children.vue'
const msg = ref('hello 啊,樹哥')
const list = reactive<number[]>([1, 2, 3])
const changeMsg = (v: string) => {
msg.value = https://www.cnblogs.com/smileZAZ/archive/2023/03/10/v
}
</script>
defineExpose 獲取子組件的實體和內部屬性
在 script-setup 模式下,所有資料只是默認 return 給 template 使用,不會暴露到組件外,所以父組件是無法直接通過掛載 ref 變數獲取子組件的資料,
如果要呼叫子組件的資料,需要先在子組件顯示的暴露出來,才能夠正確的拿到,這個操作,就是由 defineExpose 來完成,
子組件
<template>
<p>{{name}}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const name = ref('張麻子')
const changeName = () => {
name.value = 'https://www.cnblogs.com/smileZAZ/archive/2023/03/10/縣長'
}
// 將方法、變數暴露給父組件使用,父組件才可通過 ref API拿到子組件暴露的資料
defineExpose({
name,
changeName
})
</script>
父組件
<template>
<div>
<child ref='childRef' />
<button @click="getName">獲取子組件中的資料</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import child from './Child.vue'
// 子組件ref(TypeScript語法)
const childRef = ref<InstanceType<typeof child>>()
const getName = () => {
// 獲取子組件name
console.log(childRef.value!.name)
// 執行子組件方法
childRef.value?.changeName()
// 獲取修改后的name
console.log(childRef.value!.name)
}
</script>
注意:defineProps 、defineEmits 、 defineExpose 和 withDefaults 這四個宏函式只能在
<script setup>中使用,他們不需要匯入,會隨著<script setup>的處理程序中一起被編譯,
插槽
在 Vue2 的中一般中具名插槽和作用域插槽分別使用slot和slot-scope來實作,如:
父組件
<template>
<div>
<p style="color:red">父組件</p>
<Child ref='childRef'>
<template slot="content" slot-scope="{ msg }">
<div>{{ msg }}</div>
</template>
</Child>
</div>
</template>
<script lang="ts" setup>
import Child from './Child.vue'
</script>
子組件
<template> <div>child</div> <slot name="content" msg="hello 啊,樹哥!"></slot> </template>
在 Vue3 中將slot和slot-scope進行了合并統一使用,使用 v-slot, v-slot:slotName 簡寫 #slotName
父組件
<template>
<div>
<p style="color:red">父組件</p>
<Child>
<template v-slot:content="{ msg }">
<div>{{ msg }}</div>
</template>
</Child>
</div>
</template>
<script lang="ts" setup>
import Child from './Child.vue'
</script>
<!-- 簡寫 -->
<Child>
<template #content="{ msg }">
<div>{{ msg }}</div>
</template>
</Child>
實際上,v-slot 在 Vue2.6+ 的版本就可以使用,
異步組件
通過 defineAsyncComponent 異步加載
<template>
<Children :msg="msg" :list="list" @changeMsg="changeMsg"></Children>
</template>
<script setup lang="ts">
import { ref, reactive,defineAsyncComponent } from 'vue'
// import Children from './Children.vue'
const Children = defineAsyncComponent(() => import('./Children.vue'))
</script>
Suspense
Suspense 允許應用程式在等待異步組件時渲染一些其它內容,在 Vue2 中,必須使用條件判斷(例如 v-if、 v-else等)來檢查資料是否已加載并顯示一些其它內容;但是,在 Vue3 新增了 Suspense 了,就不必跟蹤何時加載資料并呈現相應的內容,
他是一個帶插槽的組件,只是它的插槽指定了default 和 fallback 兩種狀態,
Suspense 使用:
- 1、使用
<Suspense></Suspense>包裹所有異步組件相關代碼 - 2、
<template v-slot:default></template>插槽包裹異步組件 - 3、
<template v-slot:fallback></template>插槽包裹渲染異步組件渲染之前的內容
<template>
<Suspense>
<template #default>
<!-- 異步組件-默認渲染的頁面 -->
<Children :msg="msg" :list="list" @changeMsg="changeMsg"></Children>
</template>
<template #fallback>
<!-- 頁面還沒加載出來展示的頁面 -->
<div>loading...</div>
</template>
</Suspense>
</template>
<script setup lang="ts">
import { ref, reactive, defineAsyncComponent } from 'vue'
const Children = defineAsyncComponent(() => import('./Children.vue'))
</script>
Teleport傳送組件
Teleport 是一種能夠將我們的模板渲染至指定DOM節點,不受父級style、v-show等屬性影響,但data、prop資料依舊能夠共用的技術
主要解決的問題:因為Teleport節點掛載在其他指定的DOM節點下,完全不受父級style樣式影響
使用: 通過to 屬性插入到指定元素位置,如 body,html,自定義className等等,
<template>
<!-- 插入至 body -->
<Teleport to="body">
<Children></Children>
</Teleport>
<!-- 默認 #app 下 -->
<Children></Children>
</template>
<script lang="ts" setup>
import Children from './Children.vue'
</script>

keep-alive 快取組件
- 作用和vue2一致,只是生命周期名稱有所更改
- 初次進入時: onMounted> onActivated
- 退出后觸發 deactivated
- 再次進入:只會觸發 onActivated
事件掛載的方法等,只執行一次的放在 onMounted中;組件每次進去執行的方法放在 onActivated中
provide/inject
provide 可以在祖先組件中指定我們想要提供給后代組件的資料或方法,而在任何后代組件中,我們都可以使用 inject 來接收 provide 提供的資料或方法,
父組件
<template>
<Children></Children>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import Children from "./Children.vue"
const msg = ref('hello 啊,樹哥')
provide('msg', msg)
</script>
子組件
<template>
<div>
<p>msg:{{msg}}</p>
<button @click="onChangeMsg">改變msg</button>
</div>
</template>
<script setup lang="ts">
import { inject, Ref, ref } from 'vue'
const msg = inject<Ref<string>>('msg',ref('hello啊!'))
const onChangeMsg = () => {
msg.value = 'https://www.cnblogs.com/smileZAZ/archive/2023/03/10/shuge'
}
</script>
如果你想要傳入的值能回應式的改變,需要通過ref 或 reactive 添加回應式
v-model 升級
v-model 在vue3可以說是破壞式更新,改動還是不少的
我們都知道,v-model 是props 和 emit 組合而成的語法糖,vue3中 v-model 有以下改動
- 變更:value =https://www.cnblogs.com/smileZAZ/archive/2023/03/10/> modelValue
- 變更:update:input => update:modelValue
- 新增:一個組件可以設定多個 v-model
- 新增:開發者可以自定義 v-model修飾符
- v-bind 的 .sync 修飾符和組件的 model 選項已移除
子組件
<template>
<div>
<p>{{msg}},{{modelValue}}</p>
<button @click="onChangeMsg">改變msg</button>
</div>
</template>
<script setup lang="ts">
type Props = {
modelValue: string,
msg: string
}
defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'update:msg'])
const onChangeMsg = () => {
// 觸發父組件的值更新
emit('update:modelValue', '恰如此時此刻')
emit('update:msg', '彼時彼刻')
}
</script>
父組件
<template>
// v-model:modelValue簡寫為v-model
// 系結多個v-model
<Children v-model="name" v-model:msg="msg"></Children>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Children from "./Children.vue"
const msg = ref('hello啊')
const name = ref('樹哥')
</script>
自定義指令
自定義指令的生命周期
- created 元素初始化的時候
- beforeMount 指令系結到元素后呼叫 只呼叫一次
- mounted 元素插入父級dom呼叫
- beforeUpdate 元素被更新之前呼叫
- update 這個周期方法被移除 改用updated
- beforeUnmount 在元素被移除前呼叫
- unmounted 指令被移除后呼叫 只呼叫一次
實作一個自定義拖拽指令
<template>
<div v-move >
<div ></div>
<div>
內容
</div>
</div>
</template>
<script setup lang='ts'>
import { Directive } from "vue";
const vMove: Directive = {
mounted(el: HTMLElement) {
let moveEl = el.firstElementChild as HTMLElement;
const mouseDown = (e: MouseEvent) => {
//滑鼠點擊物體那一刻相對于物體左側邊框的距離=點擊時的位置相對于瀏覽器最左邊的距離-物體左邊框相對于瀏覽器最左邊的距離
console.log(e.clientX, e.clientY, "起始位置", el.offsetLeft);
let X = e.clientX - el.offsetLeft;
let Y = e.clientY - el.offsetTop;
const move = (e: MouseEvent) => {
el.style.left = e.clientX - X + "px";
el.style.top = e.clientY - Y + "px";
console.log(e.clientX, e.clientY, "位置改變");
};
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", () => {
document.removeEventListener("mousemove", move);
});
};
moveEl.addEventListener("mousedown", mouseDown);
},
};
</script>
<style >
.box {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 1px solid #ccc;
}
.header {
height: 20px;
background: black;
cursor: move;
}
</style>
自定義 hooks
我們都知道在 vue 中有個東西叫 mixins,他可以將多個組件中相同的邏輯抽離出來,實作一次寫代碼,多組件受益的效果,
但是 mixins 的副作用就是參考的多了變數的來源就不清晰了,而且還會有變數來源不明確,不利于閱讀,容易使代碼變得難以維護,
- Vue3 的 hook函式 相當于 vue2 的 mixin, 不同在與 hooks 是函式
- Vue3 的 hook函式 可以幫助我們提高代碼的復用性, 讓我們能在不同的組件中都利用 hooks 函式
useWindowResize
我們來實作一個視窗改變時獲取寬高的 hook
import { onMounted, onUnmounted, ref } from "vue";
function useWindowResize() {
const width = ref(0);
const height = ref(0);
function onResize() {
width.value = https://www.cnblogs.com/smileZAZ/archive/2023/03/10/window.innerWidth;
height.value = window.innerHeight;
}
onMounted(() => {
window.addEventListener("resize", onResize);
onResize();
});
onUnmounted(() => {
window.removeEventListener("resize", onResize);
});
return {
width,
height
};
}
export default useWindowResize;
使用:
<template>
<h3>螢屏尺寸</h3>
<div>寬度:{{ width }}</div>
<div>高度:{{ height }}</div>
</template>
<script setup lang="ts">
import useWindowResize from "../hooks/useWindowResize.ts";
const { width, height } = useWindowResize();
</script>
style v-bind CSS變數注入
<template>
<span> style v-bind CSS變數注入</span>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const color = ref('red')
</script>
<style scoped>
span {
/* 使用v-bind系結組件中定義的變數 */
color: v-bind('color');
}
</style>
本文轉載于:
https://juejin.cn/post/7158331832512020511
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/546488.html
標籤:其他
上一篇:Web客戶端開發
下一篇:HTML表格

