Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building Modular UIs with Vue Component Architecture

Tech 1

Organizing user interfaces as a tree of isolated, reusable components is central to Vue development. This approach simplifies code management and improves scalability by breaking a page into small, self-contained units.

Creating and Registering Components

A component can be defined using an options object and registered globally or locally.

<div id="app">
  <custom-card></custom-card>
</div>

<script>
  // Global registration
  Vue.component("custom-card", {
    template: `
      <div class="card">
        <h3>Default Card</h3>
      </div>`
  });

  new Vue({
    el: "#app"
  });
</script>

Global vs. Local Registration

A globally registered component is available everywhere. Local registration confines the component to a specific parent instance:

new Vue({
  el: "#app",
  components: {
    "custom-card": {
      template: `<div class="card"><h3>Local Card</h3></div>`
    }
  }
});

Parent-Child Relatoinships

Components can nest, forming parent-child trees.

<div id="app">
  <parent-box></parent-box>
</div>

<script>
  const ChildBox = {
    template: `<p>Child content</p>`
  };

  const ParentBox = {
    template: `
      <div>
        <h2>Parent section</h2>
        <child-box></child-box>
      </div>`,
    components: {
      "child-box": ChildBox
    }
  };

  new Vue({
    el: "#app",
    components: {
      "parent-box": ParentBox
    }
  });
</script>

Isolating Template Content

Templates can be extracted using <template> or <script type="text/x-template"> with a matching id.

<template id="card-template">
  <div class="card">
    <h4>{{ title }}</h4>
  </div>
</template>

<script>
  Vue.component("info-card", {
    template: "#card-template",
    data() {
      return { title: "Information" };
    }
  });
</script>

Component Data Must Be a Function

Each component instance requires its own copy of data. A factory function returns a fresh object, preventing shared state across instances.

<div id="app">
  <counter-button></counter-button>
  <counter-button></counter-button>
</div>

<template id="counter-btn">
  <div>
    <span>Count: {{ count }}</span>
    <button @click="count++">+</button>
    <button @click="count--">-</button>
  </div>
</template>

<script>
  Vue.component("counter-button", {
    template: "#counter-btn",
    data() {
      return { count: 0 };
    }
  });

  new Vue({ el: "#app" });
</script>

Avoid sharing external objects directly to data. Each component needs its own count value, otherwise envolved state causes all instances to change together.

const sharedState = { count: 0 };
Vue.component("shared-counter", {
  template: "#counter-btn",
  data() {
    return sharedState; // wrong – every instance mutates the same object
  }
});

Parent-to-Child Communication via Props

Props flow from parent to child. They can be defined as an array or a detailed object with type checks, default values, and validators.

<div id="app">
  <movie-list :items="movies"></movie-list>
</div>

<template id="movie-template">
  <ul>
    <li v-for="item in items">{{ item }}</li>
  </ul>
</template>

<script>
  Vue.component("movie-list", {
    template: "#movie-template",
    props: {
      items: {
        type: Array,
        default() {
          return [];
        }
      }
    }
  });

  new Vue({
    el: "#app",
    data: {
      movies: ["Inception", "Interstellar", "Tenet"]
    }
  });
</script>

When using camelCase prop names, use their kebab-case equivalents in templates:

<user-profile :user-name="name"></user-profile>
props: {
  userName: String
}

Child-to-Parent Communication via Custom Events

Children emit events that parents listen to using $emit.

<div id="app">
  <selection-list @pick="onPick"></selection-list>
  <p>Selected: {{ selected }}</p>
</div>

<template id="list-template">
  <div>
    <button
      v-for="option in options"
      @click="choose(option)"
      :key="option"
    >
      {{ option }}
    </button>
  </div>
</template>

<script>
  Vue.component("selection-list", {
    template: "#list-template",
    data() {
      return {
        options: ["Alpha", "Beta", "Gamma"]
      };
    },
    methods: {
      choose(val) {
        this.$emit("pick", val);
      }
    }
  });

  new Vue({
    el: "#app",
    data: { selected: "" },
    methods: {
      onPick(value) {
        this.selected = value;
      }
    }
  });
</script>

Achieving Two-Way Binding Between Parent and Child

For a parent-child model synchronization, a child can accept a prop and emit an update event. A watcher ensures05子どもux stays sync when the prop changes.

<div id="app">
  <p>Parent quantity: {{ qty }}</p>
  <input type="number" v-model.number="qty" />

  <quantity-editor
    :value="qty"
    @update="qty = $event"
  ></quantity-editor>
</div>

<template id="editor-template">
  <div>
    <p>Child editor: {{ localValue }}</p>
    <input
      type="number"
      :value="localValue"
      @input="updateLocal"
    />
  </div>
</template>

<script>
  Vue.component("quantity-editor", {
    template: "#editor-template",
    props: {
      value: Number
    },
    data() {
      return {
        localValue: this.value
      };
    },
    watch: {
      value(newVal) {
        this.localValue = newVal;
      }
    },
    methods: {
      updateLocal(event) {
        const num = Number(event.target.value);
        this.localValue = num;
        this.$emit("update", num);
      }
    }
  });

  new Vue({
    el: "#app",
    data: {
      qty: 1
    }
  });
</script>

Direct Component Access

) para collectedos parent can reach children using $refs, while a child can access its parent through $parent`.

<div id="app">
  <child-widget ref="widgetA"></child-widget>
  <button @click="showInfo">Log child info</button>
</div>

<script>
  Vue.component("child-widget", {
    template: `<p>Widget component</p>`,
    data() {
      return { secret: 42 };
    }
  });

  new Vue({
    el: "#app",
    methods: {
      showInfo() {
        console.log(this.$refs.widgetA.secret);
      }
    }
  });
</script>

Slots for Content Distribution

Slots allow parent components to inject content into predefined places inside a child.

Default Slot

<div id="app">
  <panel-box>
    <strong>Dynamic content</strong>
  </panel-box>
</div>

<template id="panel">
  <div class="panel">
    <h3>Panel Header</h3>
    <slot>Fallback text</slot>
  </div>
</template>

<script>
  Vue.component("panel-box", {
    template: "#panel"
  });
  new Vue({ el: "#app" });
</script>

Named Slots

指定name属性占据不同出口

<div id="app">
  <layout-widget>
    <template v-slot:title>
      <h2>Custom Title</h2>
    </template>
    <template v-slot:body>
      <p>body area</p>
    </template>
  </layout-widget>
</div>

<template id="layout">
  <div>
    <slot name="title">Default Title</slot>
    <slot name="body">Default Body</slot>
  </div>
</template>

<script>
  Vue.component("layout-widget", {
    template: "#layout"
  });
  new Vue({ el: "#app" });
</script>

Scoped Slots

Scoped slots allow0 a parent to receive data from the child while overriding the child’s slot content. Use v-slot (or shorthand #) to access the exposed props.

<div id="app">
  <todo-list>
    <template #default="{ tasks }">
      <ul>
        <li v-for="task in tasks" :key="task.id">
          {{ task.label }}
        </li>
      </ul>
    </template>
  </todo-list>
</div>

<template id="todo-template">
  <div>
    <h4>Tasks</h4>
    <slot :tasks="todoList">
      <p v-for="task in todoList">{{ task.label }}</p>
    </slot>
  </div>
</template>

<script>
  Vue.component("todo-list", {
    template: "#todo-template",
    data() {
      return {
        todoList: [
          { id: 1, label: "Design UI" },
          { id: 2, label: "Implement logic" }
        ]
      };
    }
  });
  new Vue({ el: "#app" });
</script>

v-slot unification

The v-slot directive replaces slot and slot-scope (Vue 2.6+). Note that v-slot is placed on <template>. For default slots you can use v-slot or #default. When using destructuring, you can also rename properties:

<template v-slot="{ tasks: projectTasks }">
  <span>{{ projectTasks.length }} tasks</span>
</template>
Tags: vue

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.