Saturday, February 20, 2021

Vue.js - Component Communication

Passing data to Child Component - Props


In order to make components to be reusable, we need to be able to pass data from parent components to child components.

Vue provides 'Props' (Properties) to define custom HTML attributes.

Ex:

// Parent: html

<welcome-message
     header="My Header"
     message="Hello World!"
></welcome-message>


// Child: script
<script>
export default {
     props: ["header""message"],
};
</script>


NOTE: We need to use 'lower camel case' for props in JavaScript, and use 'kebab-case' in html. (Refer to this)


Can we change Props?


According to 'One-Way Data Flow', you cannot mutate props.

If you change it, you will see below errors.
    error Unexpected mutation of "xxx" prop vue/no-mutating-props

There are two ways to let you change the value binding to child components:

1. A child component emits a custom event to its parent component, and the parent component change the props value based on data data from the custom event.

2. Take props as init value of local data properties of the child component. Then, we can change the value of those local properties.


Props Type and Validators?


(Refer to this) This is just a basic version of props (string array):

Ex:

props: ["name", "phoneNumber", "email"],


Vue provides an object format to let you define the detail information of each props.

Ex:

props: {
     name: String,
     phoneNumber: {
         type: String,
         required: false,
         default: '11234567890'
     },
     email: {
         type: String,
         required: true,
         validator: function(value) {
             return value.includes('@');
         }
     }
}


Examples of validating failed:

    Invalid prop: custom validator check failed for prop "email".

    Invalid prop: type check failed for prop "email". Expected String with value "undefined", got Undefined
 
 

Set Props dynamically?


(Refer to this) Since props is a custom html attribute, it only can accept string format.
 
However, we can use what we have learned before such as v-bind to dynamically bind the value to those custom html attributes.
 
Ex:

<welcome-message
     v-bind:header="header"
     v-bind:message="message"
></welcome-message>



Passing data to Parent Component (Emitting a custom event)


In some cases, we need to let parent components to listen child component's events.

Then it can react in actions if something happened.

Ex:
// Child script

onUpdateButtonClicked() {
     this.$emit("message-update", this.message);
}


// Parent html

<welcome-message
     v-bind:header="header"
     v-bind:message="message"
     v-on:message-update="updateMessage"
></welcome-message>


// Parent script

updateMessage(message) {
     this.message = message;
},



Define custom events explicitly


Just like Props, we can define the detial information including validator for custom events.

Below is the basic version to help other developers can easily know what events belongs to this components instead of tracking the code.

Ex:

emits: ["message-update"],


Vue also provides a rich information version.
 
Ex:

emits: {
     "message-update": function (message) {
         if (message) {
             return true;
         }
         return false;
     },
},



Provide and Inject


You might encountered this issues before. It is so annoying to pass data through components.
There is an example below.
 
// Structure

├───App.vue

│           ├───ParentComponent.vue

│           │                   ├───ChildComponent.vue


// App.vue

<template>
    <div>
        <section>
            <h1>{{ message }}</h1>
        </section>
        <section>
            <parent-component
                v-bind:message="message"
                v-on:update-message="updateMessage"
            >
            </parent-component>
        </section>
    </div>
</template>

<script>
export default {
    data() {
        return {
            message: 'Hello World!',
        };
    },
    methods: {
        updateMessage(message) {
            this.message = message;
        },
    },
};
</script>


// ParentComponent.vue

<template>
    <child-component
        v-bind:message="message"
        v-on:update-message="$emit('update-message'$event)"
    >
    </child-component>
</template>
<script>
export default {
    props: ['message'],
    emits: ['update-message'],
};
</script>


// ChildComponent.vue

<template>
    <form>
        <input type="string" v-model="enteredValue" />
        <button v-on:click.prevent="$emit('update-message'enteredValue)">
            Update Message
        </button>
    </form>
</template>
<script>
export default {
    props: ['message'],
    emits: ['update-message'],
    data() {
        return {
            enteredValue: this.message,
        };
    },
};
</script>


You can notice that 'ParentComponent.vue' will just pass props to 'ChildCompoennt.vue', and pass custom event to 'App.vue'.
If data need to be passed through multiple components, you can use 'Provide and Inject' pattern.


Provide and Inject with Props


Below example, we will add 'provide' in App.vue to provide data and use inject in ChildComponent.vue to use data.

// App.vue

<template>
    <div>
        <section>
            <h1>{{ message }}</h1>
        </section>
        <section>
            <parent-component v-on:update-message="updateMessage">
            </parent-component>
        </section>
    </div>
</template>

<script>
export default {
    data() {
        return {
            message: 'Hello World!',
        };
    },
    provide() {
        return { message: this.message };
    },
    methods: {
        updateMessage(message) {
            this.message = message;
        },
    },
};
</script>


// ParentComponent.vue

<template>
    <child-component v-on:update-message="$emit('update-message'$event)">
    </child-component>
</template>
<script>
export default {
    emits: ['update-message'],
};
</script>


// ChildComponent.vue

<template>
    <form>
        <input type="string" v-model="enteredValue" />
        <button v-on:click.prevent="$emit('update-message'enteredValue)">
            Update Message
        </button>
    </form>
</template>
<script>
export default {
    inject: ['message'],
    emits: ['update-message'],
    data() {
        return {
            enteredValue: this.message,
        };
    },
};
</script>



Provide and Inject with Methods


According to the example above, we use Provide and Inject to serve props.
 
Can we use the same approach for methods?

// App.vue

<template>
    <form>
        <input type="string" v-model="enteredValue" />
        <button v-on:click.prevent="$emit('update-message'enteredValue)">
            Update Message
        </button>
    </form>
</template>
<script>
export default {
    inject: ['message'],
    emits: ['update-message'],
    data() {
        return {
            enteredValue: this.message,
        };
    },
};
</script>


// ParentComponent.vue

<template>
    <child-component></child-component>
</template>
<script>
export default {};
</script>


// ChildComponent.vue

<template>
    <form>
        <input type="string" v-model="enteredValue" />
        <button v-on:click.prevent="updateMessage(enteredValue)">
            Update Message
        </button>
    </form>
</template>
<script>
export default {
    inject: ['message''updateMessage'],
    data() {
        return {
            enteredValue: this.message,
        };
    },
};
</script>


Provide and Inject V.S. Props and Custom Events


We should not always use 'Provide and Inject' to replace 'Props and Custom Events'.
 
We will use 'Provide and Inject' only to deal with passing data through multiple components.
 
Otherwise, we need to stick with 'Props and Custom Events' because it will reduce readability when using 'Provide and Inject' approach.

No comments:

Post a Comment