📕
TIC
  • Tehnologii ale InformaÅ£iei ÅŸi ComunicaÅ£iilor (TIC)
  • Basic web principles
    • How web pages work
    • The pillars of a web page
    • Extra: Getting Started with GitHub
  • Basic HTML
    • Description and Basic Syntax
    • Extra resources
  • Basic CSS
    • Description and Basic Syntax
    • Advanced Positioning
    • Extra Resources
  • Basic Javascript
    • Description and basic syntax
    • The Document Object Model
    • Extra Resources
    • Basic assignment
    • The Color Game
  • Advanced Javascript
    • Runtime Engine, Callstack, Scope
    • ES6
  • Advanced Javascript 2
  • Programming paradigms
  • OOP Javascript
  • Functional Programming
  • OOP vs. Functional Programming
  • Asynchronous Javascript
  • Backend Javascript
    • NodeJS
    • ExpressJS
    • REST APIs
    • Authentication and Authorization
  • Firebase
    • NoSQL Databases
    • Database as a Service
    • Google Cloud Firestore
    • CRUD operations
    • Securing your database
  • Basic VueJS
  • Agenda: VueJS and Frontend Frameworks
  • Single Page Applications
  • VueJS basic syntax
  • Vue Components
  • Advanced VueJS
  • Advanced apps with Vue CLI
  • Vue Router
  • SPA State Management - Vuex
  • Composition API
  • Evaluation
    • Final Individual assignment
Powered by GitBook
On this page
  • Application & The Root Component
  • Registering a component
  • Lifecycle Hooks
  • Components hierarchy
  • Props
  • Custom Events with emit

Was this helpful?

Vue Components

Components are collections of elements that are encapsulated into a group that can be accessed through one single element.

Application & The Root Component

Every Vue application starts by creating a new application instance with the createApp function.

The options passed to createApp are used to configure the root component. That component is used as the starting point for rendering when we mount the application.

An application needs to be mounted into a DOM element. For example, if we want to mount a Vue application into <div id="app"><div>, we should pass #app:

const RootComponent = { /* options */ }
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app') //vm name comes from a MVVM inspired pattern Vue has

Vue.js uses HTML-based templates syntax to bind the Vue instance to the DOM, very useful for components.

Each component instance has its own isolated scope. Data must be a function.

Every Vue component you create is also essentially a Vue Instance under the hood. Components are extensions of the base Vue constructor. When you define a component, you are essentially creating a blueprint for Vue Instances.

Registering a component

// Create a Vue application
const app = Vue.createApp({
    data() {
      return {
        message: 'I am The Root Component'
      }
    }
  })

// Define a new global component called button-counter
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
})

const vm = app.mount('#app')
//...
<div id="app" class="demo">
        {{ message }}
        <button-counter><button-counter /> //the component's template is inserted
</div>
//...

For increased readability and maintainability, this can be rewritten as follows:

// Create a Vue application
const app = Vue.createApp({
    data() {
      return {
        message: 'I am The Root Component'
      }
    }
  })

// Define a new global component called button-counter
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: '#button-counter-template'
})

const vm = app.mount('#app')
//..
<div id="app" class="demo">
    {{ message }}
    <button-counter><button-counter />
</div>

//the template of the button-counter component
<script type="text/x-template" id="button-counter-template">
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>
</script>
//..

Lifecycle Hooks

Each component instance goes through a series of initialization steps when it's created - for example, it needs to set up data observation, compile the template, mount the instance to the DOM, and update the DOM when data changes. Along the way, it also runs functions called lifecycle hooks, giving users the opportunity to add their own code at specific stages.

Created and Mounted are great places to perform API calls.

Mounted should handle DOM operations.

//inserting our logic in the "created" lifecycle hook
Vue.createApp({
  data() {
    return { count: 1 }
  },
  created() {
    // `this` points to the vm instance
    console.log('count is: ' + this.count) // => "count is: 1"
  }
})

//TIP: Don't use arrow functions on an options property or callback
//as they don't have an implicit parameter "this"

Components hierarchy

Vue Components can be easily nested. In fact, the example from above showcases this. The button-counter component is actually nested in the root component.

For example, a Todo application's component tree might look like this:

Root Component
└─ TodoList
   ├─ TodoItem
   │  ├─ DeleteTodoButton
   │  └─ EditTodoButton
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics

Given the fact each component has a local, isolated scope, it is crucial to be able to pass data between components.

In Vue3 this can be achieved through the following methods:

  1. Props drilling, to communicate from a parent to an exact child component

  2. Event emitting, to communicate from a child component to its parent component

  3. A state management service such as Vuex, Pinia or Redux, for having global access to data from any component. This is useful for sibling components or passing data to components to which we don't have a straight hierarchy path (e.g., siblings) or passing data through multiple levels of components without props drilling.

  4. Composition API. Managing data in a Functional Programming philosophy. While not strictly for component communication, the Composition API provides a more functional way to organize component logic. It can make it easier to share reactive data and functions between components, especially in complex scenarios. You can use provide and inject to make data accessible down the component tree.

Props

The types of data that can be passed as props are: String, Number, Boolean, Date, Array, Object but also Functions, Promises or Constructors.

Static and dynamic props can be passed. In case of static props, the v-bind directive is missing.

app.component('shopping-cart', {
    props: {
        numberOfProducts: { //camelCased in JS
            type: Number, //optional
            required: true, //optional
            default: 0 //optional
            //Objects, arrays need their defaults to be returned from a function
        }
    },
    data() {
        return {
        //..
        }
    },
    computed: {
        rounded: function() {
        return this.numberOfProducts > 9 ? '9+' : this.numberOfProducts
        //using props in methods, computed properties, watchers
        }
    }
    template: `<div>{{ numberOfProducts }}</div>`
})
//..
<shopping-cart :number-of-products="products.length"><shopping-cart />
//assuming the parent component has access to a "products" array
//..

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. If you do, Vue will warn you in the console.

HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you're using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents.

Custom Events with emit

Also one-way flow of data, but this time from child to the parent component.

Emitted events can be defined on the component via the emits option.

When a native event (e.g., click) is defined in the emits option, the component event will be used instead of a native event listener.

app.component('custom-form', {
  emits: { //optional, but recommended for better documenting the component
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})
//...
<parent-component>
    <custom-form @submit="submitData"><custom-form />
    //submitData method should consider the payload that is sent with the event
<parent-component>
//...

Choosing the Right Method

The best method for passing data depends on the specific situation:

  • Props drilling: Simple parent-child communication.

  • Event emitting: Child-to-parent communication or when a child needs to trigger an action in the parent.

  • State management: Complex state, sibling communication, or data sharing across multiple components.

  • Composition API: Provides more flexibility and can be used in combination with other methods for more organized data management.

PreviousVueJS basic syntaxNextAdvanced apps with Vue CLI

Last updated 4 months ago

Was this helpful?