Skip to content

單向資料流

https://vuejs.org/guide/components/props.html#one-way-data-flow

js
const props = defineProps(['foo'])

// ❌ 不可直接變更 props!
props.foo = 'bar'
const props = defineProps(['foo'])

// ❌ 不可直接變更 props!
props.foo = 'bar'

Flux

Flux 是一種設計模式。這個概念由 Facebook 提出,最核心的思想就是確保資料在應用程序中的流動方向是單向的。以避免雙向資料流所可能帶來的維護問題。

=> 資料在哪裡定義,就由哪裡修改!

Details

Flux 明確定義不同角色的職責和互動方式,幫助開發者更清晰地管理應用程序的狀態和資料流:

  • Action(動作)
    表示某個事件或操作,通常由使用者或系統事件觸發,用來更新應用程序的狀態,所有改變資料的動作必須在這裡被定義和觸發
  • Dispatcher(分發器)
    將目前發生的 Action 通知給所有已註冊的 Store。它充當一個中央管理器,確保 Actions 被適當地傳遞給相關的 Stores。
  • Store(資料儲存)
    每個 Store 存儲特定部分的應用程序狀態,重要的是,Store 是唯讀的,提供 getter 方法供 View 存取,只能通過 Actions 來修改它的狀態。
  • View(視圖)
    根據資料渲染使用者界面,同時它也監聽事件並將事件映射到適當的 Actions。當使用者與 View 互動時,View 會生成對應的 Action 來觸發相應的變更。

唯獨 Props 的利用

  • 單純使用 props 唯獨值

  • props 作為初始化的值

    在 setup 裡面直接取用一次,賦予 reactivity。
    js
    const props = defineProps(['initialCounter'])
    const counter = ref(props.initialCounter)
    const props = defineProps(['initialCounter'])
    const counter = ref(props.initialCounter)
  • 跟蹤 props 變動 得到 computed

    使用 computed
    js
    const props = defineProps(['size'])
    const normalizedSize = computed(() => props.size.trim().toLowerCase())
    const props = defineProps(['size'])
    const normalizedSize = computed(() => props.size.trim().toLowerCase())

修改 Props

為了不違反單向資料流,子元件不可直接修改父元件屬性,資料在哪裡定義,就由哪裡修改!因此父元件必須要監聽子元件事件以修改屬性。

html
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>
html
<!--input 在 vue 當中的地位比較特殊-->
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>
<!--input 在 vue 當中的地位比較特殊-->
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

v-model 語法糖

一樣使用事件觸發上游資料變更,不違反單向資料流實作 two-way binding 語法糖

html
<input v-model="searchText" />
<input v-model="searchText" />

這時候子元件長這樣,使用 getter, setter 讓 value 看起來可以像是直接修改(實際上遵循單向資料流):

html
<!-- CustomInput.vue -->
<template>
  <input v-model="model" />
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const model = computed({
  () => props.modelValue,
  (v) => emit('update:modelValue', v)
});
</script>
<!-- CustomInput.vue -->
<template>
  <input v-model="model" />
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const model = computed({
  () => props.modelValue,
  (v) => emit('update:modelValue', v)
});
</script>

試試看

綁定多個 v-model

父元件:

html
<script setup>
import UserName from './UserName.vue';
import { ref } from 'vue'

const first = ref('Peter')
const last = ref('Chen')
</script>

<template>
{{first}}, {{last}}
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
</template>
<script setup>
import UserName from './UserName.vue';
import { ref } from 'vue'

const first = ref('Peter')
const last = ref('Chen')
</script>

<template>
{{first}}, {{last}}
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
</template>

子元件:

html
<template>
    <input type="text" v-model="firstName" />
    <input type="text" v-model="lastName" />
</template>

<script setup>
import { computed } from 'vue'
const props = defineProps({
    firstName: String,
    lastName: String
});

const emit = defineEmits(['update:firstName', 'update:lastName']);

const firstName = computed({
    get() { return props.firstName },
    set(e) { emit('update:firstName', e) }
});
const lastName = computed({
    get() { return props.lastName },
    set(e) { emit('update:lastName', e) }
});
<template>
    <input type="text" v-model="firstName" />
    <input type="text" v-model="lastName" />
</template>

<script setup>
import { computed } from 'vue'
const props = defineProps({
    firstName: String,
    lastName: String
});

const emit = defineEmits(['update:firstName', 'update:lastName']);

const firstName = computed({
    get() { return props.firstName },
    set(e) { emit('update:firstName', e) }
});
const lastName = computed({
    get() { return props.lastName },
    set(e) { emit('update:lastName', e) }
});

試試看

Summary

  1. 單向資料流:資料哪裡定義哪裡修改。
  2. v-model 雙向綁定語法
    • 實際上是單向資料流。
    • v-model:someValue 可以綁定多個 props。
    • 沒有指定值就綁定 modelValue

Reference