Skip to content



NodeJS »

Dataflow between Vue components

VueJS is a JavaScript framework for building user interfaces. It builds on top of standard HTML, CSS and JavaScript, and provides a declarative and component-based programming model that helps you efficiently develop user interfaces, be it simple or complex.

Last update: 2022-08-18


Premitive and Reference types#

JavaScript has 5 premitive types:String, Number, Boolean, Null, Undefined, and all others will be Reference type like Object.

  • When assigning value to primitive type, it will copy the value and give the new memory address.

    var a = 1
    var b = a // copy the value and have new address
    b = 2 // change b's value
    console.log(a) // 1
    console.log(b) = // 2
    
  • When assigning value to reference type, it will copy the pointer to the address. They point to the same memory address.

    var obj1 = {a: 1}
    var obj2 = obj1 // copy the pointer to the address 
    obj2 = {a: 2} // change obj2's value
    console.log(a) // {a: 2}
    console.log(b) = // {a: 2}
    

VueJS Props and Events#


VueJS Props and Events

VueJS Props#

VueJS uses One-Way Data Flow:

All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to understand.

In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component.

Child components can use the props in 2 ways:

  • As an Initial Value, so when parent’s value change, it is not updated in children

    const props = defineProps(['initialCounter'])
    // counter only uses props.initialCounter as the initial value;
    // it is disconnected from future prop updates.
    const counter = ref(props.initialCounter)
    
  • As a Monitored Value, so when parent’s value change, it is also updated in children

    using computed:

    const props = defineProps(['counter'])
    // computed is triggered whenever props.counter is changed
    const counter = computed(() => {return props.counter; })
    

    or using watch to do something:

    const props = defineProps(['counter'])
    // callback function is triggered whenever props.counter is changed
    watch(props.counter, (newValue, oldValue) => {
    })
    

Mutating Object / Array Props

When objects and arrays are passed as props, while the child component cannot mutate the prop binding, it will be able to mutate the object or array’s nested properties. This is because in JavaScript objects and arrays are passed by reference.

As a best practice, you should avoid such mutations unless the parent and child are tightly coupled by design. In most cases, the child should emit an event to let the parent perform the mutation.

VueJS Events#

A component can emit custom events and its parent can listen to it. The event can also have its arguments to carry data.

<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>
function increaseCount(n) {
  count.value += n
}
<MyButton @increase-by="(n) => count += n" />
<MyButton @increase-by="increaseCount" />

VueJS Event Bus#


VueJS Event Bus

NOT a real Event Bus

Event Bus in VueJS actually is a Global Vue instance! Because Vue has implementations for emit, on and off of itself, we can take advantages of them.

EventBus.ts
import Vue from 'vue'
const EventBus = new Vue()
export default EventBus
Component
import EventBus from './EventBus'

// to emit the event
EventBus.$emit("event-name", data);

// to listen the event
EventBus.$on("event-name", callback);

// to destroy the event
EventBus.$off("event-name");


Make Event Bus Global

Create a Class and export it as a plug-in:

EventBus.js
import Vue from 'vue'

class EventBus {
    constructor() {
        this.bus = new Vue()
    }

    on(event, handler) {
        this.bus.$on(event, handler)
    }

    once(event, handler) {
        this.bus.$once(event, handler)
    }

    off(event, handler) {
        this.bus.$off(event, handler)
    }

    emit(event, ...args) {
        this.bus.$emit(event, ...args)
    }
}

export default {
    install(Vue) {
        const bus = new EventBus()
        Vue.prototype.$eventBus = bus
    },
}

Install the plug-in in the global Vue class:

App.ts
import EventBus from './EventBus'
Vue.use(EventBus)

In any compoment in App, use $eventBus.emit(), $eventBus.once(), $eventBus.on() and $eventBus.off().

Call Method in children#

When ever a child compoment emits an event, its parent can execute a function to handle new data.

How about the other direction, from parent to child?

Props triggering#

The easiest way is to use Props. If child compoment creates a watch for its prop, a function will be called when the prop is changed. Make sure to change the prop’s value to trigger the callback function.

Child
const props = defineProps(['counter'])
// callback function is triggered whenever props.counter is changed
watch(props.counter, (newValue, oldValue) => {
})
Parent
counter++;
<Child :counter="counter"/>

Child Reference#

Using the ref attribute to direct access to the underlying DOM elements.

Parent
<template>
    <button @click="$refs.myChild.sayHello()">Click me</button>
    <child-component ref="myChild" />
</template>
Parent
<script setup>
import { ref, onMounted } from 'vue'

// declare a ref to hold the element reference
// the name must match template ref value
const myChild = ref(null)

onMounted(() => {
  myChild.value.sayHello()
})
</script>

<template>
  <Child ref="myChild" />
</template>
  • If the child component is using Options API, the referenced instance will be identical to the child component’s this, which means the parent component will have full access to every property and method of the child component.

  • If the child component is using Composition API, the <script setup> are private by default, the parent won’t be able to access anything unless the child component chooses to expose a public interface using the defineExpose macro:

    Child
    <script setup>
    import { ref } from 'vue'
    
    const a = 1;
    const b = ref(2);
    function fx(){};
    
    defineExpose({
        a,
        b,
        fx
    })
    </script>
    

Child Object interface#

This method still uses the props-event as adviced by VueJS. This takes advantages of reference object in JavaScript to expose an interface to parent compoment via event arguments.

Child
<script setup lang="ts">
import { onMounted, ref } from 'vue'

const counter = ref(0)
const emits = defineEmits(['interface'])

function addCounter() { counter.value++; }

onMounted(() => {
    emits('interface', {
        add: () => { addCounter(); },
    })
})
</script>

<template>
    Counter: {{ counter }}
</template>

<style scoped>
</style>
Parent
<script setup lang="ts">
import Child from './Child.vue'

var childInterface = { add: () => { /* default */} }
function addCount() { childInterface.add(); }

function getChildInterface(i) { childInterface = i; }
</script>

<template>
    <div id="parent">
        <Child @interface="getChildInterface" />
        <button @click="addCount">Add</button>
    </div>
</template>

<style scoped>
</style>

Comments