全局API
createApp
返回一個提供應用上下文的應用實例。應用實例掛載的整個組件樹共享同一個上下文。
const app = createApp(Component);
app.mount('#root');
defineComponent
創建組件
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
let counter = ref(0);
const increment = () => {
counter.value += 1;
}
return {
counter,
increment
}
},
render() {
const {counter, increment} = this;
return (
<div>
<p>Counter: {counter}</p>
<button onClick={increment}>+</button>
</div>
);
},
});
或者是一個 setup 函數,函數名稱將作為組件名稱來使用
import { defineComponent, ref } from 'vue';
const Parent = defineComponent(function Parent() {
let age = ref(10);
const incrementAge = () => {
age.value += 1;
}
return {
age,
incrementAge
}
})
console.log('Parent', Parent)
// {
// name: "Parent2"
// setup: ? Parent2()
// }
defineAsyncComponent
創建一個異步加載組件
// 全局注冊
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
app.component('async-component', AsyncComp)
// 局部注冊
import {createApp, defineAsyncComponent } from 'vue'
createApp({
components: {
AsyncComponent: defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
}
})
// 高階組件
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
// 工廠函數
loader: () => import('./Foo.vue')
// 加載異步組件時要使用的組件
loadingComponent: LoadingComponent,
// 加載失敗時要使用的組件
errorComponent: ErrorComponent,
// 在顯示 loadingComponent 之前的延遲 | 默認值:200(單位 ms)
delay: 200,
// 如果提供了 timeout ,并且加載組件的時間超過了設定值,將顯示錯誤組件
// 默認值:Infinity(即永不超時,單位 ms)
timeout: 3000,
// 定義組件是否可掛起 | 默認值:true
suspensible: false,
/**
*
* @param {*} error 錯誤信息對象
* @param {*} retry 一個函數,用于指示當 promise 加載器 reject 時,加載器是否應該重試
* @param {*} fail 一個函數,指示加載程序結束退出
* @param {*} attempts 允許的最大重試次數
*/
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// 請求發生錯誤時重試,最多可嘗試 3 次
retry()
} else {
// 注意,retry/fail 就像 promise 的 resolve/reject 一樣:
// 必須調用其中一個才能繼續錯誤處理。
fail()
}
}
})
resolveComponent
在當前應用中查找組件, 只能在 render 或 setup 函數中使用。
import { createApp, defineComponent, resolveComponent} from 'vue'
const app = createApp({});
app.component('ResolveComponent', {
render(){
return (<h1>This is a ResolveComponent</h1>)
}
})
app.mount('#root');
defineComponent({
render() {
// return component or componentName;
const ResolveComponent = resolveComponent('ResolveComponent');
return (
<div>
<ResolveComponent />
</div>
)
}
})
resolveDynamicComponent
查找組件
import {defineComponent, resolveDynamicComponent} from 'vue'
defineComponent({
setup() {
const isShow = ref(false);
return {
isShow
}
},
render() {
// return component or componentName;
const ResDynamicComponent = resolveDynamicComponent({
render() {
return (
<div>
<h1>This is a ResolveDynamicComponent</h1>
</div>
)
}
})
return (
<div>
{
isShow ? <ResDynamicComponent /> : ''
}
</div>
)
}
})
resolveDirective
在當前應用中查找指令, 只能在 render 或 setup 函數中使用。
import { createApp, defineComponent, resolveDirective} from 'vue'
const app = createApp({})
app.directive('highlight', {})
defineComponent({
setup() {
// return Directive or undefined;
const directive = resolveDirective('highlight');
}
})
withDirectives
defineComponent({
render() {
const focus = resolveDirective('focus')
return withDirectives(<input type="text" v-focus />, [[focus]])
}
})
createRenderer
nextTick
將回調推遲到下一個 DOM 更新周期之后執行。在更改了一些數據以等待 DOM 更新后立即使用它。
mergeProps
const props = mergeProps({
class: 'active'
}, this.$attrs)
4) 比較Vue2與Vue3的響應式(重要)
vue2的響應式
- 核心:
- 對象: 通過defineProperty對對象的已有屬性值的讀取和修改進行劫持(監視/攔截)
- 數組: 通過重寫數組更新數組一系列更新元素的方法來實現元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
- 問題
- 對象直接新添加的屬性或刪除已有屬性, 界面不會自動更新
- 直接通過下標替換元素或更新length, 界面不會自動更新 arr[1] = {}
Vue3的響應式
- 核心:
- 通過Proxy(代理): 攔截對data任意屬性的任意(13種)操作, 包括屬性值的讀寫, 屬性的添加, 屬性的刪除等...
- 通過 Reflect(反射): 動態對被代理對象的相應屬性進行特定的操作
- 文檔:
new Proxy(data, {
// 攔截讀取屬性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 攔截設置屬性值或添加新屬性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 攔截刪除屬性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy 與 Reflect</title>
</head>
<body>
<script>
const user = {
name: "John",
age: 12
};
/*
proxyUser是代理對象, user是被代理對象
后面所有的操作都是通過代理對象來操作被代理對象內部屬性
*/
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log('劫持get()', prop)
return Reflect.get(target, prop)
},
set(target, prop, val) {
console.log('劫持set()', prop, val)
return Reflect.set(target, prop, val); // (2)
},
deleteProperty (target, prop) {
console.log('劫持delete屬性', prop)
return Reflect.deleteProperty(target, prop)
}
});
// 讀取屬性值
console.log(proxyUser===user)
console.log(proxyUser.name, proxyUser.age)
// 設置屬性值
proxyUser.name = 'bob'
proxyUser.age = 13
console.log(user)
// 添加屬性
proxyUser.sex = '男'
console.log(user)
// 刪除屬性
delete proxyUser.sex
console.log(user)
</script>
</body>
</html>
5) setup細節
-
setup執行的時機
- 在beforeCreate之前執行(一次), 此時組件對象還沒有創建
- this是undefined, 不能通過this來訪問data/computed/methods / props
- 其實所有的composition API相關回調函數中也都不可以
-
setup的返回值
- 一般都返回一個對象: 為模板提供數據, 也就是模板中可以直接使用此對象中的所有屬性/方法
- 返回對象中的屬性會與data函數返回對象的屬性合并成為組件對象的屬性
- 返回對象中的方法會與methods中的方法合并成功組件對象的方法
- 如果有重名, setup優先
- 注意:
- 一般不要混合使用: methods中可以訪問setup提供的屬性和方法, 但在setup方法中不能訪問data和methods
- setup不能是一個async函數: 因為返回值不再是return的對象, 而是promise, 模板看不到return對象中的屬性數據
-
setup的參數
- setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含props配置聲明且傳入了的所有屬性的對象
- attrs: 包含沒有在props配置中聲明的屬性的對象, 相當于 this.$attrs
- slots: 包含所有傳入的插槽內容的對象, 相當于 this.$slots
- emit: 用來分發自定義事件的函數, 相當于 this.$emit
<template>
<h2>App</h2>
<p>msg: {{msg}}</p>
<button @click="fn('--')">更新</button>
<child :msg="msg" msg2="cba" @fn="fn"/>
</template>
<script lang="ts">
import {
reactive,
ref,
} from 'vue'
import child from './child.vue'
export default {
components: {
child
},
setup () {
const msg = ref('abc')
function fn (content: string) {
msg.value += content
}
return {
msg,
fn
}
}
}
</script>
<template>
<div>
<h3>{{n}}</h3>
<h3>{{m}}</h3>
<h3>msg: {{msg}}</h3>
<h3>msg2: {{$attrs.msg2}}</h3>
<slot name="xxx"></slot>
<button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import {
ref,
defineComponent
} from 'vue'
export default defineComponent({
name: 'child',
props: ['msg'],
emits: ['fn'], // 可選的, 聲明了更利于程序員閱讀, 且可以對分發的事件數據進行校驗
data () {
console.log('data', this)
return {
// n: 1
}
},
beforeCreate () {
console.log('beforeCreate', this)
},
methods: {
// update () {
// this.n++
// this.m++
// }
},
// setup (props, context) {
setup (props, {attrs, emit, slots}) {
console.log('setup', this)
console.log(props.msg, attrs.msg2, slots, emit)
const m = ref(2)
const n = ref(3)
function update () {
// console.log('--', this)
// this.n += 2
// this.m += 2
m.value += 2
n.value += 2
// 分發自定義事件
emit('fn', '++')
}
return {
m,
n,
update,
}
},
})
</script>
6) reactive與ref-細節
- 是Vue3的 composition API中2個最重要的響應式API
- ref用來處理基本類型數據, reactive用來處理對象(遞歸深度響應式)
- 如果用ref對象/數組, 內部會自動將對象/數組轉換為reactive的代理對象
- ref內部: 通過給value屬性添加getter/setter來實現對數據的劫持
- reactive內部: 通過使用Proxy來實現對對象內部所有數據的劫持, 并通過Reflect操作對象內部數據
- ref的數據操作: 在js中要.value, 在模板中不需要(內部解析模板時會自動添加.value)
<template>
<h2>App</h2>
<p>m1: {{m1}}</p>
<p>m2: {{m2}}</p>
<p>m3: {{m3}}</p>
<button @click="update">更新</button>
</template>
<script lang="ts">
import {
reactive,
ref
} from 'vue'
export default {
setup () {
const m1 = ref('abc')
const m2 = reactive({x: 1, y: {z: 'abc'}})
// 使用ref處理對象 ==> 對象會被自動reactive為proxy對象
const m3 = ref({a1: 2, a2: {a3: 'abc'}})
console.log(m1, m2, m3)
console.log(m3.value.a2) // 也是一個proxy對象
function update() {
m1.value += '--'
m2.x += 1
m2.y.z += '++'
m3.value = {a1: 3, a2: {a3: 'abc---'}}
m3.value.a2.a3 += '==' // reactive對對象進行了深度數據劫持
console.log(m3.value.a2)
}
return {
m1,
m2,
m3,
update
}
}
}
</script>
7) 計算屬性與監視
-
computed函數:
- 與computed配置功能一致
- 只有getter
- 有getter和setter
-
watch函數
- 與watch配置功能一致
- 監視指定的一個或多個響應式數據, 一旦數據變化, 就自動執行監視回調
- 默認初始時不執行回調, 但可以通過配置immediate為true, 來指定初始時立即執行第一次
- 通過配置deep為true, 來指定深度監視
-
watchEffect函數
- 不用直接指定要監視的數據, 回調函數中使用的哪些響應式數據就監視哪些響應式數據
- 默認初始時就會執行第一次, 從而可以收集需要監視的數據
- 監視數據發生變化時回調
<template>
<h2>App</h2>
fistName: <input v-model="user.firstName"/><br>
lastName: <input v-model="user.lastName"/><br>
fullName1: <input v-model="fullName1"/><br>
fullName2: <input v-model="fullName2"><br>
fullName3: <input v-model="fullName3"><br>
</template>
<script lang="ts">
/*
計算屬性與監視
1\. computed函數:
與computed配置功能一致
只有getter
有getter和setter
2\. watch函數
與watch配置功能一致
監視指定的一個或多個響應式數據, 一旦數據變化, 就自動執行監視回調
默認初始時不執行回調, 但可以通過配置immediate為true, 來指定初始時立即執行第一次
通過配置deep為true, 來指定深度監視
3\. watchEffect函數
不用直接指定要監視的數據, 回調函數中使用的哪些響應式數據就監視哪些響應式數據
默認初始時就會執行第一次, 從而可以收集需要監視的數據
監視數據發生變化時回調
*/
import {
reactive,
ref,
computed,
watch,
watchEffect
} from 'vue'
export default {
setup () {
const user = reactive({
firstName: 'A',
lastName: 'B'
})
// 只有getter的計算屬性
const fullName1 = computed(() => {
console.log('fullName1')
return user.firstName + '-' + user.lastName
})
// 有getter與setter的計算屬性
const fullName2 = computed({
get () {
console.log('fullName2 get')
return user.firstName + '-' + user.lastName
},
set (value: string) {
console.log('fullName2 set')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
})
const fullName3 = ref('')
/*
watchEffect: 監視所有回調中使用的數據
*/
/*
watchEffect(() => {
console.log('watchEffect')
fullName3.value = user.firstName + '-' + user.lastName
})
*/
/*
使用watch的2個特性:
深度監視
初始化立即執行
*/
watch(user, () => {
fullName3.value = user.firstName + '-' + user.lastName
}, {
immediate: true, // 是否初始化立即執行一次, 默認是false
deep: true, // 是否是深度監視, 默認是false
})
/*
watch一個數據
默認在數據發生改變時執行回調
*/
watch(fullName3, (value) => {
console.log('watch')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
})
/*
watch多個數據:
使用數組來指定
如果是ref對象, 直接指定
如果是reactive對象中的屬性, 必須通過函數來指定
*/
watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
console.log('監視多個數據', values)
})
return {
user,
fullName1,
fullName2,
fullName3
}
}
}
</script>
8) 生命周期
vue2.x的生命周期
lifecycle.png
vue3的生命周期
lifecycle (1).png
與 2.x 版本生命周期相對應的組合式 API
-
beforeCreate
-> 使用setup()
-
created
-> 使用setup()
-
beforeMount
->onBeforeMount
-
mounted
->onMounted
-
beforeUpdate
->onBeforeUpdate
-
updated
->onUpdated
-
beforeDestroy
->onBeforeUnmount
-
destroyed
->onUnmounted
-
errorCaptured
->onErrorCaptured
新增的鉤子函數
組合式 API 還提供了以下調試鉤子函數:
- onRenderTracked
- onRenderTriggered
<template>
<div class="about">
<h2>msg: {{msg}}</h2>
<hr>
<button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import {
ref,
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from "vue"
export default {
beforeCreate () {
console.log('beforeCreate()')
},
created () {
console.log('created')
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
},
beforeUnmount () {
console.log('beforeUnmount')
},
unmounted () {
console.log('unmounted')
},
setup() {
const msg = ref('abc')
const update = () => {
msg.value += '--'
}
onBeforeMount(() => {
console.log('--onBeforeMount')
})
onMounted(() => {
console.log('--onMounted')
})
onBeforeUpdate(() => {
console.log('--onBeforeUpdate')
})
onUpdated(() => {
console.log('--onUpdated')
})
onBeforeUnmount(() => {
console.log('--onBeforeUnmount')
})
onUnmounted(() => {
console.log('--onUnmounted')
})
return {
msg,
update
}
}
}
</script>
<template>
<h2>App</h2>
<button @click="isShow=!isShow">切換</button>
<hr>
<Child v-if="isShow"/>
</template>
<script lang="ts">
import Child from './Child.vue'
export default {
data () {
return {
isShow: true
}
},
components: {
Child
}
}
</script>
09) 自定義hook函數
使用Vue3的組合API封裝的可復用的功能函數
自定義hook的作用類似于vue2中的mixin技術
自定義Hook的優勢: 很清楚復用功能代碼的來源, 更清楚易懂
-
需求1: 收集用戶鼠標點擊的頁面坐標
hooks/useMousePosition.ts
import { ref, onMounted, onUnmounted } from 'vue'
/*
收集用戶鼠標點擊的頁面坐標
*/
export default function useMousePosition () {
// 初始化坐標數據
const x = ref(-1)
const y = ref(-1)
// 用于收集點擊事件坐標的函數
const updatePosition = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
// 掛載后綁定點擊監聽
onMounted(() => {
document.addEventListener('click', updatePosition)
})
// 卸載前解綁點擊監聽
onUnmounted(() => {
document.removeEventListener('click', updatePosition)
})
return {x, y}
}
<template>
<div>
<h2>x: {{x}}, y: {{y}}</h2>
</div>
</template>
<script>
import {
ref
} from "vue"
/*
在組件中引入并使用自定義hook
自定義hook的作用類似于vue2中的mixin技術
自定義Hook的優勢: 很清楚復用功能代碼的來源, 更清楚易懂
*/
import useMousePosition from './hooks/useMousePosition'
export default {
setup() {
const {x, y} = useMousePosition()
return {
x,
y,
}
}
}
</script>
利用TS泛型強化類型檢查
-
需求2: 封裝發ajax請求的hook函數
hooks/useRequest.ts
import { ref } from 'vue'
import axios from 'axios'
/*
使用axios發送異步ajax請求
*/
export default function useUrlLoader<T>(url: string) {
const result = ref<T | null>(null)
const loading = ref(true)
const errorMsg = ref(null)
axios.get(url)
.then(response => {
loading.value = false
result.value = response.data
})
.catch(e => {
loading.value = false
errorMsg.value = e.message || '未知錯誤'
})
return {
loading,
result,
errorMsg,
}
}
<template>
<div class="about">
<h2 v-if="loading">LOADING...</h2>
<h2 v-else-if="errorMsg">{{errorMsg}}</h2>
<!-- <ul v-else>
<li>id: {{result.id}}</li>
<li>name: {{result.name}}</li>
<li>distance: {{result.distance}}</li>
</ul> -->
<ul v-for="p in result" :key="p.id">
<li>id: {{p.id}}</li>
<li>title: {{p.title}}</li>
<li>price: {{p.price}}</li>
</ul>
<!-- <img v-if="result" :src="result[0].url" alt=""> -->
</div>
</template>
<script lang="ts">
import {
watch
} from "vue"
import useRequest from './hooks/useRequest'
// 地址數據接口
interface AddressResult {
id: number;
name: string;
distance: string;
}
// 產品數據接口
interface ProductResult {
id: string;
title: string;
price: number;
}
export default {
setup() {
// const {loading, result, errorMsg} = useRequest<AddressResult>('/data/address.json')
const {loading, result, errorMsg} = useRequest<ProductResult[]>('/data/products.json')
watch(result, () => {
if (result.value) {
console.log(result.value.length) // 有提示
}
})
return {
loading,
result,
errorMsg
}
}
}
</script>
10) toRefs
把一個響應式對象轉換成普通對象,該普通對象的每個 property 都是一個 ref
應用: 當從合成函數返回響應式對象時,toRefs 非常有用,這樣消費組件就可以在不丟失響應式的情況下對返回的對象進行分解使用
問題: reactive 對象取出的所有屬性值都是非響應式的
解決: 利用 toRefs 可以將一個響應式 reactive 對象的所有原始屬性轉換為響應式的 ref 屬性
<template>
<h2>App</h2>
<h3>foo: {{foo}}</h3>
<h3>bar: {{bar}}</h3>
<h3>foo2: {{foo2}}</h3>
<h3>bar2: {{bar2}}</h3>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
將響應式對象中所有屬性包裝為ref對象, 并返回包含這些ref對象的普通對象
應用: 當從合成函數返回響應式對象時,toRefs 非常有用,
這樣消費組件就可以在不丟失響應式的情況下對返回的對象進行分解使用
*/
export default {
setup () {
const state = reactive({
foo: 'a',
bar: 'b',
})
const stateAsRefs = toRefs(state)
setTimeout(() => {
state.foo += '++'
state.bar += '++'
}, 2000);
const {foo2, bar2} = useReatureX()
return {
// ...state,
...stateAsRefs,
foo2,
bar2
}
},
}
function useReatureX() {
const state = reactive({
foo2: 'a',
bar2: 'b',
})
setTimeout(() => {
state.foo2 += '++'
state.bar2 += '++'
}, 2000);
return toRefs(state)
}
</script>
11) ref獲取元素
利用ref函數獲取組件中的標簽元素
功能需求: 讓輸入框自動獲取焦點
<template>
<h2>App</h2>
<input type="text">---
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/*
ref獲取元素: 利用ref函數獲取組件中的標簽元素
功能需求: 讓輸入框自動獲取焦點
*/
export default {
setup() {
const inputRef = ref<HTMLElement|null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus()
})
return {
inputRef
}
},
}
</script>