[重构倒数第03天] - One-Way Data Flow 单向资料流

前言

该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系列文章,在这边整理了许多我自己使用Vue重构很多网站的经验分享给读者们。

Component 之间在做资料沟通的时候,很常会使用 props 以及 emit 来传递你的资料,但是当你 propsemit 变多的时候,其实对於资料流的传递上面就会变复杂,我们先来看一下使用 propsemit 的例子。

Vue mike)

首先大家会看到我有一个 header 里面有一颗 menu 的按钮,当我点击了会把我的侧边选单给开启滑进来,然後点击了关闭按钮又被关起来了,这其实在我们前面很多的范例都有许多类似的内容,但是关於 Component 之间在做资料沟通的细节我们并没有特别的去探讨,今天就让我们来仔细探讨这件事情。

这是我们 App.vue

<script>
import { ref } from "vue";
import Header from "./components/Header.vue";
import Nav from "./components/Nav.vue";
export default {
  components: {
    Header,
    Nav,
  },
  setup() {
    const isOpen = ref(false);
    const handToggle = () => isOpen.value = !isOpen.value;
    return { isOpen, handToggle };
  },
};
</script>

<template>
  <Header @toggle="handToggle" />
  <Nav :isOpen="isOpen" @toggle="handToggle" />
</template>

我的 Header.vue

<script>
export default {
  setup(props, { emit }) {
    const handleMenu = () => {
      emit("toggle");
    };
    return { handleMenu };
  },
};
</script>

<template>
  <header>
    <nav>
      <button @click="handleMenu">menu</button>
    </nav>
  </header>
</template>

侧边选单 Nav.vue

<script>
export default {
  props: {
    isOpen: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, { emit }) {
    const handleMenu = () => {
      emit("toggle");
    };
    return { props, handleMenu };
  },
};
</script>

<template>
  <div :class="['nav', { open: props.isOpen }]">
    <a @click="handleMenu">close</a>
  </div>
</template>

整体介绍一下这个资料的 flow

https://ithelp.ithome.com.tw/upload/images/20210928/20125854obPJlLNSFI.jpg

我们会在点击了 Header.vue里面的 Menu 後发送一个 emit 给上层的 component,透过上层接收到的 toggle事件去修改我们 isOpenvalue,然後 isOpen再透过 props 传入 Nav.vue 里面去控制侧边选单的开起,然後透过侧边选单的 close 按钮,在发送一个 toggle事件往上去更改 isOpen,然後 props 的 isOpen 也会同步知道被更改,这样就完成了整个的流程!

codesandbox 范例 :https://codesandbox.io/s/one-way-data-flow-1-i4hcx

But...

这个资料流的流程很乱,上上下下的,虽然制作具有一定规模的专案我们会选用 vuex 或是 composition API 来管理我们的资料,但是难免在一些小案子或是小组件上面我们选择用比较直观的方式来处理,但是越直观的方式就越要特别注意,老实说我不是很推荐滥用 emit,我认爲 emit 的使用应该是要搭配第三方套件或是自己在开发套件上面的使用会比较恰当,所以在这种开发的情境下面,我会推荐另外一种做法 One-Way Data Flow

什麽是One-Way Data Flow (单向资料流) ?

简单来说就是我们的资料是透过 props 往下传递,但是不要往上发送,然後不可以透过子组件去直接修改 props传递下来的 value 。

官方文件 :https://v3.vuejs.org/guide/component-props.html#one-way-data-flow

你可能会问,那要怎麽修改上层的资料?

我们来看一下实际的作法

App.vue

<script>
// 其他省略...
export default {
  setup() {
    const isOpen = ref(false);
    const handleOpenMenu = () => isOpen.value = !isOpen.value;
    return { isOpen, handleOpenMenu };
  },
};
</script>

<template>
  <Header :handleOpenMenu="handleOpenMenu" />
  <Nav :isOpen="isOpen" :handleOpenMenu="handleOpenMenu" />
</template>

我在父层先定义好了一个修改 isOpen 的 function 叫做 handleOpenMenu,然後把这个 function 也当成 props 给传下去我的子组件。

header.vue

<script>
export default {
  props: {
    handleOpenMenu: {
      type: Function,
      default: () => {},
    },
  },
  setup(props) {
    return { props };
  },
};
</script>
<template>
  <header>
    <nav>
      <button @click="props.handleOpenMenu">menu</button>
    </nav>
  </header>
</template>

nav.vue

<script>
export default {
  props: {
    isOpen: {
      type: Boolean,
      default: false,
    },
    handleOpenMenu: {
      type: Function,
      default: () => {},
    },
  },
  setup(props, { emit }) {
    return { props };
  },
};
</script>
<template>
  <div :class="['nav', { open: props.isOpen }]">
    <a @click="props.handleOpenMenu">close</svg>
    </a>
  </div>
</template>

你会看到我直接把 props 传递下来的 handleOpenMenu 拿来使用,去触发父层的 function 来修改父层的资料,这样一来一样可以达到相同的效果。

https://ithelp.ithome.com.tw/upload/images/20210928/20125854qG373HAb9N.jpg

你会看到这张图的表示,你就会发现现在的资料流都只有从上面往下,flow也变简单,这样子在进行开发的时候可以统一就我们对於资料及操作放在上层去控管,子组件只要负责触发即可。

codesandbox 范例 :https://codesandbox.io/s/one-way-data-flow-2-h0du4?file=/src/main.js

先告一个段落

其实蛮多刚入门的新手在开发上面蛮常会把 propsemit 大量的使用,导致後续专案难以维护,不过我们只要我们可以掌握 One-Way Data Flow 的原则,在组件上面的资料流程就会相对单纯一点,也会变得比较好维护,但是如果专案的规模慢慢变大,建议还是使用 vuex 或是 composition API 来管理比较好。

QRcode

那如果对於Vue3不够熟的话呢?

Ps. 购买的时候请登入或注册该平台的会员,然後再使用下面连结进入网站点击「立即购课」,这样才可以让我获得更多的课程分润,还可以帮助我完成更多丰富的内容给各位。

我有开设了一堂专门针对Vue3从零开始教学的课程,如果你觉得不错的话,可以购买我课程来学习
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果对於JS基础不熟的朋友,我也有开设JS的入门课程,可以参考这个课程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

订阅Mike的频道享受精彩的教学与分享

Mike 的 Youtube 频道
Mike的medium
MIke 的官方 line 帐号,好友搜寻 @mike_cheng


<<:  [从0到1] C#小乳牛 练成基础程序逻辑 Day 13 - if 条件判断

>>:  [Day28]Hashmat the brave warrior

Day8 Vue Computed vs Method

我们在模板中要进行计算或转换资料时computed及method通常能做到一样的效果,这麽说的话那不...

30天学习笔记 -day 26-Motion Editor(上篇)

Motion Editor是自 Android Studio 4.0 版本开始为MotionLayo...

予焦啦!RISC-V 的计时器中断机制

本节是以 Golang 上游 8854368cb076ea9a2b71c8b3c8f675a8e1...

「行动」是难下的决定,剩下的只是坚持。

「行动」是难下的决定,剩下的只是坚持。 The most difficult thing is th...

【Day3】声音的特徵提取

梅尔倒频谱 昨天我们介绍了频谱跟梅尔频率,那有没有机会我可以把这两个结合在一起,获得更有用的资讯呢?...