Contents14

2026 update: a post ported over from the old VuePress blog. The shape of the explanation still holds, but the tooling and Vue version are frozen at the time of writing and differ from the current release.

For anyone picking up Vue.js — or a JavaScript framework — for the first time, the part to understand first is components.

This post starts from what a component is, so it should read cleanly even without prior framework experience.

The short version

  • A component is a reusable UI part — it raises maintainability and lets you reuse work
  • Data flows between a parent and a child through binding
  • Use one-way binding (v-bind / v-on) and two-way binding (v-bind + v-on, or .sync) depending on whether the data needs transforming

What is a component?

Vue.js and Angular both build on the idea of a component.

Vue uses components to consolidate code. Consolidation cuts down the amount of code you maintain and makes it easier to change later.

Every Vue project uses components in some form, so understanding them is non-optional.

A reusable part

A component, in one line, is a reusable part.

A .vue file can import and use another .vue file inside its own template.

Build a button as a reusable component, for example, and any caller can render the same look and the same behaviour just by referencing it.

Example: a button

Goal: reuse a 20 px square button labelled “click here”.

How:

  1. In file A, define a 20 px square button labelled “click here”.
  2. In file B, render the button component.

Result: the button shows up in file B.

The side being called (file A) and the side calling it (file B) are usually referred to as the child and the parent.

From here on:

  • The caller = parent
  • The called = child

What is binding?

In the button example above, two limitations stand out:

  • The button text is hard-coded
  • The click handler has to live in the child

Binding is what removes those limits.

Component-to-component data flow

Binding, in one line, is the data flow between components.

With binding in place you can:

  • Let the parent decide the child’s text
  • Let the child propagate an event up to the parent

Both of the issues above go away once binding is added.


The relationship between the parent and the child matters here:

  • A one-way trip — parent to child, or child to parent — is one-way binding
  • A round trip between the two is two-way binding

Components do not click into place until binding does, so this is the section worth re-reading.

Working examples

From here, the explanation is anchored to real Vue.js code.

If .vue file basics are unfamiliar, the earlier post covers them:

Using a component

Start with the plainest case: rendering a component with no binding at all.

The child shows a string. The parent renders the child.

Child: ChildComponent.vue

<template>
  string
</template>

The child has no logic — it just prints text.

Parent: ParentComponent.vue

<template>
  <ChildComponent />
</template>

<script>
import ChildComponent from "xxxx/ChildComponent.vue";

export default {
  components: { ChildComponent },
};
</script>

The parent imports ChildComponent.vue and registers it under components. On the template side, dropping <ChildComponent /> where you want it to render is enough.

v-bind — one-way binding, parent to child

With the call mechanics out of the way, the next step is parent-to-child binding.

The idea is: let the parent decide the child’s text.

The snippet below passes a string set in the parent down to the child.

Child: ChildComponent.vue

<template>
  {{bindName}}
</template>

<script>
export default {
  props: ["bindName"],
};
</script>

To receive data from the parent, the child declares props.

The prop name has to match on both sides. Here it is bindName.

props is an array, so a child can declare multiple inbound props at once.

Parent: ParentComponent.vue

<template>
  <ChildComponent :bindName="dataName" />
</template>

<script>
import ChildComponent from "xxxx/ChildComponent.vue";

export default {
  components: { ChildComponent },

  data() {
    return {
      dataName: 'string'
    };
  },
};
</script>

The parent renders the child with :bindName="dataName". That attribute is the bind itself.

  • bindName is the prop name on the child
  • dataName is the data field on the parent

That is the full parent-to-child flow.

v-on — one-way binding, child to parent

Next, child-to-parent binding.

The idea is: let the parent observe an event that happened inside the child.

In the snippet below, clicking the child’s text fires an event in the parent and flips the parent’s text from string → モジ.

Child: ChildComponent.vue

<template>
  <div>
    <div @click="clickText()">
      クリックして変更
    </div>
  </div>
</template>

<script>
export default {

  methods: {
    clickText() {
      this.$emit('onName', 'モジ');
    }
  }
};
</script>

The child wires up a click handler — @click is the directive.

Inside the method, the line that lifts the event up to the parent is emit.

  • The first argument, onName, is the event name
  • The second argument, モジ, is the payload (an object works too)

Clicking “クリックして変更” propagates up to the parent’s onName listener.

Parent: ParentComponent.vue

<template>
  {{dataName}}
  <ChildComponent @onName="changeName"/>
</template>

<script>
import ChildComponent from "xxxx/ChildComponent.vue";

export default {
  components: { ChildComponent },

  data() {
    return {
      dataName: 'string'
    };
  },

  methods: {
    changeName(item) {
      this.dataName = item
    }
  }
};
</script>

The parent looks much like the v-bind case.

The new piece is changeName — its argument is the payload the child emitted.

That is the full child-to-parent flow.

Going further

The one-way patterns above cannot move data in both directions on their own.

Two-way binding is what closes that loop. It is the trickiest part of components, so this section is worth slowing down on.

v-bind + v-on — two-way binding

Vue.js has two common ways to do two-way binding. The most fundamental one comes first.

It is the combination of:

  • v-bind — one-way, parent to child
  • v-on — one-way, child to parent

The flow:

  1. The parent sets the string the child will show (string)
  2. The child sends a click event up to the parent (payload: モジ)
  3. The parent’s string flips from stringモジ
  4. The child re-renders with モジ

Previous sections led with the child. From here, the parent comes first — it reads more naturally.

Parent: ParentComponent.vue

<template>
  <ChildComponent :bindName="dataName" @onName="changeName"/>
</template>

<script>
import ChildComponent from "xxxx/ChildComponent.vue";

export default {
  components: { ChildComponent },

  data() {
    return {
      dataName: 'string'
    };
  },

  methods: {
    changeName(item) {
      this.dataName = item
    }
  }
};
</script>

It is just v-bind and v-on running side by side. The parent stays straightforward.

Child: ChildComponent.vue

<template>
  <div>
    {{bindName}}
    <div @click="clickText()">
      クリックして変更
    </div>
  </div>
</template>

<script>
export default {
  props: ["bindName"],

  methods: {
    clickText() {
      this.$emit('onName', 'モジ');
    }
  }
};
</script>

The child is the same shape — v-bind plus v-on.

If this stops making sense, walking through the four-step flow above in order tends to put it back together.

.sync — two-way binding

v-bind + v-on works, but it gets verbose.

.sync is the shortcut for the cases that do not need the wiring.

In one line, .sync turns v-bind into a synced binding.

The code below produces the same end behaviour as the previous snippet:

  1. The parent sets the string the child will show (string)
  2. The child sends a click event up to the parent (payload: モジ)
  3. The parent’s string flips from stringモジ
  4. The child re-renders with モジ

Parent: ParentComponent.vue

<template>
  <ChildComponent :bindName.sync="dataName" />
</template>

<script>
import ChildComponent from "xxxx/ChildComponent.vue";

export default {
  components: { ChildComponent },

  data() {
    return {
      dataName: 'string'
    };
  },
};
</script>

The parent gets noticeably lighter.

The key is bindName.sync — adding .sync removes the explicit assignment that v-on was doing.

Child: ChildComponent.vue

<template>
  <div>
    {{bindName}}
    <div @click="clickText()">
      クリックして変更
    </div>
  </div>
</template>

<script>
export default {
  props: ["bindName"],

  methods: {
    clickText() {
      this.$emit('update:bindName', 'モジ');
    }
  }
};
</script>

The child changes slightly — the first argument to emit becomes update:bindName.

The update: prefix is what wires the emit back into the parent’s .sync.

v-on vs. .sync — when to use which

.sync is shorter, but it has a caveat.

Because it is a synced bind, there is no place to hook in transformation logic. If the parent needs to filter or reshape the child’s payload before assigning it, .sync will not fit.

A rough rule for two-way binding:

  • If the data is transformed between files → v-bind + v-on
  • If the data is passed through unchanged → .sync

Wrap-up

Components and binding are two of the load-bearing pieces of Vue.js, and the rest of the framework leans on them. Worth re-reading and writing out by hand until they feel automatic.

Related: