想到了我和程世社季子的感情,取經路上有困難,但最終的結果是值得的。我們的每一次成長與等待,都讓我更加相信,無論過程多麼艱辛,最終會走到一起。程世社季子,取經的路上有你有我,我願意陪你一起經歷這段旅程,直到最終我們的心靈再度相通。
ref
: 建立一個響應式引用,對於物件類型會進行深層響應式轉換。shallowRef
: 建立一個淺層響應式引用,只有頂層屬性是響應式的。比較:
const obj = { nested: { count: 0 } };
const deepRef = ref(obj);
const shallowRef = shallowRef(obj);
// 深層響應
deepRef.value.nested.count++; // 觸發更新
// 淺層響應
shallowRef.value.nested.count++; // 不觸發更新
shallowRef.value = { nested: { count: 1 } }; // 觸發更新
使用 shallowRef
可以在處理大型物件時提高性能,但需要注意深層屬性變化不會觸發更新。
promiseTimeout
是一個工具函數,用於建立一個延遲指定時間後解決的 Promise:
function promiseTimeout(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
使用範例:
async function delayedGreeting() {
console.log("Wait for it...");
await promiseTimeout(2000);
console.log("Hello!");
}
until
是一個用於建立一個 Promise,該 Promise 會等待直到指定的響應式引用(Ref)達到預期的值才解決,主要用於在異步操作中等待某個條件成立。
使用範例:
const isReady = ref(false);
setTimeout(() => { isReady.value = true; }, 1000);
await until(isReady).toBe(true);
console.log("Ready!");
現在,讓我們更深入地分析 useAsyncState 的實現:
export interface UseAsyncStateReturnBase<Data, Params extends any[], Shallow extends boolean> {
state: Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>;
isReady: Ref<boolean>;
isLoading: Ref<boolean>;
error: Ref<unknown>;
execute: (delay?: number, ...args: Params) => Promise<Data>;
}
這個介面使用了條件類型 Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>
來決定 state
的類型。如果 Shallow
為 true,則直接使用 Ref<Data>
,否則使用 Ref<UnwrapRef<Data>>
。UnwrapRef
是一個工具類型,用於解包 ref 的值類型。
UnwrapRef
是一個用於『解包』嵌套的 ref 類型。它的主要作用是去除多餘的 ref 包裝,特別是在處理複雜的嵌套結構時。
Data
是簡單類型時(如 string, number, boolean):
Ref<Data>
和 Ref<UnwrapRef<Data>>
是相同的。Data
是物件或陣列,並且可能包含嵌套的 ref 時:
Ref<Data>
保留了原始的結構,包括可能存在的嵌套 ref。Ref<UnwrapRef<Data>>
會解開所有嵌套的 ref,給出一個"扁平化"的類型。讓我們看一些例子來說明這個區別:
type NestedData = {
count: Ref<number>,
items: Ref<string[]>
}
// 使用 Ref<Data>
type WithRef = Ref<NestedData>
// 結果:Ref<{ count: Ref<number>, items: Ref<string[]> }>
// 使用 Ref<UnwrapRef<Data>>
type WithUnwrapRef = Ref<UnwrapRef<NestedData>>
// 結果:Ref<{ count: number, items: string[] }>
在這個例子中:
WithRef
保留了 count
和 items
作為 Ref
。WithUnwrapRef
將 count
和 items
解包,去掉了內部的 Ref
包裝。UnwrapRef
確保在處理複雜的嵌套 ref 結構時能獲得正確的類型。shallowRef
時,解開內部 ref 結構可避免多次使用 .value
,簡化值的訪問。Shallow
為 true
時,使用 Ref<Data>
。這是因為 shallowRef
不處理嵌套的響應性。Shallow
為 false
時,使用 Ref<UnwrapRef<Data>>
。這反映了 ref
會遞迴地將嵌套值轉換為響應式的行為。這個設計很厲害的確保了類型定義與實際運行時行為的一致性,無論是使用淺層還是深層的響應式引用。真不愧是我大哥
const {
immediate = true,
delay = 0,
onError = noop,
onSuccess = noop,
resetOnExecute = true,
shallow = true,
throwError,
} = options ?? {}
這裡使用了解構賦值和默認值設定。noop
是一個空函數 () => {}
,用作默認的錯誤和成功回調。
// 可以回去看前置知識的第一點:什麼是 ref?什麼又是 shallowRef?
const state = shallow ? shallowRef(initialState) : ref(initialState)
const isReady = ref(false)
const isLoading = ref(false)
const error = shallowRef<unknown | undefined>(undefined)
這裡根據 shallow
選項決定使用 shallowRef
還是 ref
來建立 state
。使用 shallowRef
可以在處理大型物件時提高性能。
async function execute(delay = 0, ...args: any[]) {
if (resetOnExecute)
state.value = initialState
error.value = undefined
isReady.value = false
isLoading.value = true
if (delay > 0)
await promiseTimeout(delay)
const _promise = typeof promise === 'function'
? promise(...args as Params)
: promise
try {
const data = await _promise
state.value = data
isReady.value = true
onSuccess(data)
}
catch (e) {
error.value = e
onError(e)
if (throwError)
throw e
}
finally {
isLoading.value = false
}
return state.value as Data
}
這個函數包含了幾個關鍵點:
resetOnExecute
為 true,則重置狀態。promiseTimeout
實現延遲。isLoading
, isReady
, error
等狀態。這個寫法很有趣,不僅保留了async await 還保留了 then (可以看下面的實際使用範例)
function waitUntilIsLoaded() {
return new Promise<UseAsyncStateReturnBase<Data, Params, Shallow>>((resolve, reject) => {
until(isLoading).toBe(false).then(() => resolve(shell)).catch(reject)
})
}
return {
...shell,
then(onFulfilled, onRejected) {
return waitUntilIsLoaded()
.then(onFulfilled, onRejected)
},
}
這部分實現了 Promise-like 接口:
waitUntilIsLoaded
函數建立一個 Promise,該 Promise 在 isLoading
變為 false 時解決。then
方法,使其成為 thenable。const { state, isLoading, error, execute } = useAsyncState(
async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
},
{ data: null },
{ immediate: true, delay: 1000 }
);
// 使用 Promise-like 接口
useAsyncState(/* ... */).then(({ state }) => {
console.log(state.value);
});
// 手動執行
const refreshData = () => {
execute();
};
這個例子示範了如何使用 useAsyncState 來管理 API 請求的狀態,包括自動執行、延遲執行、錯誤處理,以及手動更新資料。
useAsyncState 結合了 Vue 的響應式系統、TypeScript 的特性和 JavaScript 的異步處理。它提供了一個強大且靈活的方式,來管理異步操作的狀態,同時保持了良好的類型安全性和錯誤處理能力。這個實現不僅功能豐富,而且考慮到了性能優化(通過 shallowRef)和使用便利性(Promise-like 接口)。