2024-06-06|閱讀時間 ‧ 約 5 分鐘

watch 前先小等一下

鱈魚的魚缸搬家了!新家文章皆有重新修訂,歡迎來新家看看喔。(´▽`ʃ♡ƪ)


raw-image


先說結論:

watch 不是不能用,而是在使用 watch 之前,先想想有沒有其他方案,真的沒有才用 watch。


千萬不要為了一時方便讓元件裡滿滿的 watch,因為容易產生難以追蹤的副作用,會讓資料流更加複雜。


口說無憑,讓我們舉個栗子 (。・∀・)ノ🌰。


考慮一個簡單的元件,可以提供數值資料:

<script setup lang="ts">
interface Props {
modelValue?: string | number;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: 0,
});

const emit = defineEmits<{
(e: 'update:modelValue', value: number): void;
}>();

const numberValue = ref(props.modelValue);

watch(() => props.modelValue, (value) => {
numberValue.value = parseFloat(value);
});
</script>

你可能會想問:「阿不是說能不用就不要用,怎麼開場就有 watch?」


這是因為除了 watch 以外,沒有其他方法可以在元件內得知 props.modelValue 發生變更,所以此部分可以使用 watch。

(或是請大家指教其他方法。(. ❛ ᴗ ❛.))。


以上程式已經將 props.modelValue 數值同步至 numberValue 中,現在只差將 numberValue 數值 emit 出去部分,直覺上可能會這樣寫:

<script setup lang="ts">
...

watch(numberValue, (value) => {
emit('update:modelValue', parseFloat(value));
});
</script>

在簡單的例子中,這樣寫不會有太大問題,但是若 numberValue 資料較為複雜(例如深層物件、矩陣等等)或者是資料耦合其他邏輯時,這樣寫可能會導致 watch 無限呼叫或其他難以追蹤的副作用。


一般情況下,我們應該可以直接知道 numberValue 的變化來源。

(如果沒辦法得知,應該重新仔細思考設計)


如果是 input 的話,我們可以從 @change 事件得知數值變化,所以此例子可以不使用 watch,結果可能會這樣:

<script setup lang="ts">
...

function handleUpdate() {
emit('update:modelValue', parseFloat(numberValue.value));
}

// 或是這樣
function handleUpdate(value: string) {
emit('update:modelValue', parseFloat(value));
}
</script>

也就是在明確的資料流中呼叫 emit。


最後讓我們總結一下。(。・∀・)ノ゙


在設計元件時,請讓資料流盡可能單向且單純,以免寫得時候很爽,除錯的時候火葬場。


例如 Vue v-model 的雙向綁定就是一個經典的例子。


大家應該都是知道 v-model 其實是個語法糖,程式碼很簡單時使用 v-model 容易且簡單。


但是一但資料開始複雜時,就會建議拆成 :model-value@update:modelValue 處理(也就是把原本的雙向資料流,拆成兩個單向資料流),彈性更高更可靠。( •̀ ω •́ )✧

(一時想不到有甚麼好例子,有想到再來分享,或是請大家幫忙補充補充。(´▽`ʃ♡ƪ))


但是如果發現元件內有很多「只能用 watch 才能處理的邏輯」,那就表示你可能耦合了太多邏輯和副作用在單一元件內了,請思考拆分元件、重構或者重新探討元件設計了。

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.