Vue Notes - robbiehume/CS-Notes GitHub Wiki

Links

Topic pages

API Styles:

  • API styles guide
  • Most notes in this GitHub wiki are formatted for the composition API

Look into:

General Notes

  • Vue examples (in this repo markdown)
  • To create a Vue app, run npm create vue@latest. Choose 'yes' for Vue router, Pinia, and DevTools; no for the rest
  • Vue doesn't require semicolons, but you can still use them if you want
  • The purpose of using Vue is to make websites, or widgets within a website, more interactive and dynamic
  • Using Single-File Components(SFCs) allows us to avoid having to declare the setup() function to expose state and methods
    • Can just put the code inside a <script setup> ... </script> tag
  • Progressive Web App (PWA): a web site/app that can be run anywhere and can even run in the background and/or without internet access; all the code is local, not individually fetched from the server

Vuejs.org Tutorial Notes (Click me)

Vuejs.org tutorial (each section is a link to the tutorial page)

  • ref() can take any value type and create an object that exposes the inner value under a .value property
    • In composition API, ref() is the recommended way to declare reactive state
  • reactive(): unlike a ref which wraps the inner value in a special object, reactive() makes an object itself reactive
  • Reactive state declared in the component's <script setup> block can be used directly in the template
  • You can render dynamic text based on the value of a ref or reactive object using the mustache {{ }} syntax:
    • const message = ref('Hello World!')
      <h1>{{ message }}</h1>
  • The content inside the mustaches isn't limited to just identifies or paths - you can use and valid JS expression:
    • <h1>{{ message.split('').reverse().join('') }}</h1>
  • To bind an attribute to a dynamic value, we use the v-bind directive: <div v-bind:id="dynamicId"></div>
  • A directive is a special attribute that starts with the v- prefix
  • The part after the colon (:id) is the "argument" of the directive. Here, the element's id attribute will be synced with the dynamicId property from the component's state
  • Because v-bind is used so frequently, it has a dedicated shorthand syntax of just :; <div :id="dynamicId"></div>
  • v-bind can work with other attributes such as class: <h1 :class="dynamicClass">header</h1>
  • We can listen to DOM events using the v-on directive: <button v-on:click="increment">{{ count }}</button>
  • Due to its frequent use, v-on also has a shorthand syntax (@): <button @click="increment">{{ count }}</button>
    • In this example, increment is referencing an increment function declared in <script setup>
      • function increment(){count.value++}
  • @keydown.enter="handleEnter"

Form Bindings (v-model)

  • v-model overview
  • Using v-bind and v-on together, we can create two-way bindings on form input elements:
    • <input :value="text" @input="onInput">
      function onInput(e) {
        // a v-on handler receives the native DOM event as the argument
        text.value = e.target.value
      }
  • To simplify this, you can replace the above code with one line:
    • <input v-model="text">
  • v-model automatically syncs the <input>'s value with the bound state, so we no longer need to use an event handler for that
  • v-model works not only on text inputs, but also on other input types such as checkboxes, radio buttons, and select dropdowns
  • You can use the v-if directive to conditionally render an element: <h1 v-if="awesome">Vue is awesome!</h1>
    • This <h1> will be rendered only if the value of awesome is truthy. If awesome changes to a falsy value, it will be removed from the DOM
  • Can also use v-else and v-else-if to denote other branches of the condition:
    • <h1 v-if="awesome">Vue is awesome!</h1>
      <h1 v-else>Oh no 😢</h1>
  • For more details on v-if go to this link
  • Can use v-for to cycle through an array of data and output each item in a template
  • Can use the v-for directive to render a list of elements based on a source array:
    • let id = 0
      const todos = ref([{id: id++, text: 'Learn HTML', done: true },{ id: id++, text: 'Learn JavaScript', done: true },{ id: id++, text: 'Learn Vue', done: false }])
      ...
      <ul>
        <li v-for="todo in todos" :key="todo.id">
          {{ todo.text }}
        </li>
      </ul>
  • Notice that each todo object has a unique id and it's binded as the special key attribute for each <li>. The key allows Vue to accurately move each <li> to match the position of its corresponding object in the array
  • There are two ways to update the list
    • Call mutating methods on the source array: todos.value.push(newTodo)
    • Replace the array with a new one: todos.value = todos.value.filter(/* ... */)

Computed Property (computed())

  • A computed property is a way to define a data property inside our components that depends on other data we have inside that component
  • To build off the previous example, we can add a button to hide the completed items of the list
  • With computed() we can create a computed ref that computes its .value based on other reactive data sources
  • import { ref, computed } from 'vue'
    const hideCompleted = ref(false)
    const todos = ref([
      /* ... */
    ])
    const filteredTodos = computed(() => {
      // return filtered todos based on `todos.value` & `hideCompleted.value`
    })
  • A computed property tracks other reactive state used in its computation dependencies. It caches the result and automatically updates it when its dependencies change
  • Computed properties are only for transforming data for the presentation layer, it should never alter/change the existing data
  • So far, Vue has been handling all the DOM updates for us, thanks to reactivity and declarative rendering. However, inevitably there will be cases where we need to manually work with the DOM
  • We can request a template ref - i.e. a reference to an element in the template - using the special ref attribute:
    • <p ref="pElementRef">hello</p>
  • To access the ref, we need to declare a ref with matching name: const pElementRef = ref(null)
  • It's initialized to null because the element doesn't exist yet when <script setup> is executed. The template ref is only accessible after the component is mounted
  • To run code after mount, can add put it inside onMounted()
    • onMounted(() => {
          pElementRef.value.textContent = 'hi'  // changes the innerHTML from hello to hi
      })

Watchers (watch())

  • watch() can directly watch a ref and take action when it changes
  • const count = ref(0)
    watch(count, (newCount) => {
      console.log(`new count is: ${newCount}`)     // yes, console.log() is a side effect
    })
  • watch() can also watch other types of data sources - more details are covered in Guide - Watchers.
  • If you just want to fire a function when a ref changes, can do a one-liner: watch(id, fetchData)
  • So far, we've only been working with a single component. Real Vue applications are typically created with nested components
  • A parent component can render another component in its template as a child component. To use a child component, we need to first import it:
    • import ChildComp from './ChildComp.vue'
  • Then, we can use the component in the template as: <ChildComp />
  • Other helpful links: link1; link2
  • A child component can accept input from the parent via props. First it needs to declare the props it accepts:
    <!-- ChildComp.vue -->
    <script setup>
    const props = defineProps({
      msg: String
    })
    </script>
  • defineProps() is a compile-time macro and doesn't need to be imported. Once declared, the msg prop can be used in the child component's template. It can also be accessed in JavaScript via the returned object of defineProps(), i.e. props.msg
  • The parent can pass the prop to the child just like attributes. To pass a dynamic value, we can also use the v-bind syntax: <ChildComp :msg="greeting" />
  • In addition to receiving props, a child component can also emit events to the parent:
    <script setup>
    const emit = defineEmits(['response'])    // declare emitted events
    emit('response', 'hello from child')    // emit with argument
    </script>
  • The parent can listen to child-emitted events using v-on - here the handler receives the extra argument from the child emit call and assigns it to local state:
    • <ChildComp @response="(msg) => childMsg = msg" />
  • In addition to passing data via props, the parent component can also pass down template fragments to the child via slots:
    • <ChildComp> This is some slot content! </ChildComp>
  • In the child component, it can render the slot content from the parent using the <slot> element as outlet
  • Content inside the outlet will be treated as "fallback" content: it will be displayed if the parent did not pass down any slot content
    • <!-- in child template -->
      <slot>Fallback content</slot>

Style guide: link

Simple expressions in templates

  • Component templates should only include simple expressions; refactor complex ones into computed properties or methods
  • Bad
    {{
      fullName.split(' ').map((word) => {
        return word[0].toUpperCase() + word.slice(1)
      }).join(' ')
    }}
  • Good
    <!-- In a template -->
    {{ normalizedFullName }}
    
    // The complex expression has been moved to a computed property
    const normalizedFullName = computed(() =>
      fullName.value
        .split(' ')
        .map((word) => word[0].toUpperCase() + word.slice(1))
        .join(' ')
    )

Directive shorthands

  • Directive shorthands (: for v-bind:, @ for v-on:, and # for v-slot) should be used always or never

Simple computed properties

  • Complex computed properties should be split into as many simpler properties as possible
  • Bad
    const price = computed(() => {
      const basePrice = manufactureCost.value / (1 - profitMargin.value)
      return basePrice - basePrice * (discountPercent.value || 0)
    })
  • Good
    const basePrice = computed(() => manufactureCost.value / (1 - profitMargin.value))
    const discount = computed(() => basePrice.value * (discountPercent.value || 0))
    const finalPrice = computed(() => basePrice.value - discount.value)

Overview

  • Vue is a JS framework for creating fast, reliable, and interactive web apps and pages
    • Can also be used to create stand-alone widgets (i.e. search bar, contact form, or other interactive things)
  • Core features of Vue
    • Declarative Rendering: Vue extends standard HTML with a template syntax that allows us to declaratively describe HTML output based on JavaScript state.
    • Reactivity: Vue automatically tracks JavaScript state changes and efficiently updates the DOM when changes happen.
  • Vue uses components that can be injected into pages and re-used
  • Vue websites are usually SPAs (Single Page Application)
    • These are websites where the page routing is done in the browser and not on the server
    • Actions and page navigation can happen on the client browser handled by Vue instead of sending requests to the server
  • Vue SPA common process:
    • First the client sends the initial request to the server and it returns a bare-bones HTML document with limited or no content
    • Along with the HTML, the server also sends a Vue JavaScript bundle
    • Vue then takes control of the website in the browser and renders the Vue components needed for the page
      • It also also takes over the linking and routing of other pages on the site
      • Ex: if the /contact link is clicked, Vue will intercept the request and render the Contact component

Vue directives / events

  • A directive is a special attribute that starts with the v- prefix
  • List of Vue built-in directives
  • YouTube tutorial explanation (#2 and #3)
  • Vue directives can be added to HTML elements to allow us to react to certain events
  • Ex: <button v-on:click="age++">Increase age</button>
    • Whenever the button is clicked, it will increase the age variable
    • The quotations value is JavaScript code to be implemented when the event happens
    • v-on: can be shorthanded to @; ex: @click
  • v-on or @ shortcut: notes link
  • v-for: notes link
  • v-bind or : shortcut: notes link

Conditional rendering (v-if / v-show)

  • Sometimes we may want to show content only based on specific conditions
    • Ex: if a user is logged in, show a logout icon. If not, show a sign in icon
  • Can do this with v-if / v-show directive to only include / show a component if the criteria is met
  • Ex: <p v-if:"title == 'Harry Potter'>"The book is Harry Potter</p>
  • v-if="": conditionally render an element based on the truthy-ness of the expression value
    • If the expression is false, it removes the element from the DOM
    • v-else: denotes the "else block" for v-if or a v-if / v-else-if chain
  • v-show="": toggle the element's visibility based on the truthy-ness of the expression value
    • If the expression is false, it just hides the element with CSS, but doesn't remove it from the DOM
    • v-show has a lot less overhead than removing it from the DOM with v-if
    • Use it if you need to toggle something very often, and if not then use v-if

Handling events

  • The Event object gives us a lot of info about what event occurred and the state when it occurred
  • Scenarios:
    • No event object, no parameters: HTML: @click="func"; JS: func(){
    • No event object, with parameters: HTML: @click="func(arg)"; JS: func(param){
    • Event object, no parameters: HTML: @click="func"; JS: func(e){
    • Event object and parameters: HTML: @click="func($event, arg)"; JS: func(e, param){
  • Can also pass Vue objects to the method
  • Ex:
    • <li v-for="book in books" @click="toggleFav(book)"> book_name </li>
    • In Vue / JS, you can then just manipulate the object directly: book.isFav = !book.isFav

Attribute binding (v-bind)

  • Attribute binding is when we bind dynamic values to HTML attributes
  • Can use v-bind for this
    • <a v-bind:href="url">dynamic url</a>
    • This will fill in the value of the data() attribute url
  • Can also just use a : instead of the whole v-bind:
    • <a :href="url">dynamic url</a>

Dynamic class styling

  • Can use v-bind (or :) to dynamically / conditionally add class values to elements
    • :class="{ class_name: expression}"
  • Ex: <li v-for="book in books" :class="{ fav: book.isFav }"></li>
    • This will give it a class of "fav" if the value of isFav on the object in the books array in data() is true

Components / SFCs

  • Each SFC (.vue file) contains all the HTML, CSS, and JS needed for that component of the web page
  • To communicate between components you can use a pinia data store or an event bus
  • Ex: when a submit button is clicked in one component, have it display data in another component
    • Inside the component with the button, have a function that updates the store
    • Inside the other function, have an element that uses the store value as its data source
    • When the button is clicked and the data is updated in the store, then the display data will automatically be updated in the other component

nextTick() vs onMounted

Overview

  • nextTick is a method used to defer the execution of a function until after the DOM has been updated in response to reactive data changes

  • onMounted is a lifecycle hook used for performing actions after a component is fully mounted in the DOM for the first time

  • When to Use nextTick:

    • Use it when you need to wait for a reactive data change to fully update the DOM before executing code. This is useful when you need to access or manipulate DOM elements after a state change
    • It’s ideal for scenarios where you are dynamically updating the component and need to ensure that the DOM reflects those updates before performing further actions
  • When to use onMounted:

    • Use it for code that should run once when the component is first mounted, such as initial DOM manipulations, setting up event listeners, or initializing data-fetching operations
    • It’s ideal for tasks that depend on the entire component being rendered and available in the DOM

nextTick()

  • link
  • nextTick() allows you to wait for the next DOM update cycle before executing a piece of code
  • It can be used immediately after a state change to wait for the DOM updates to complete
  • Vue updates the DOM asynchronously, meaning that when you change data, the DOM isn't updated immediately. If you need to perform an action that depends on the DOM being fully updated (like manipulating the DOM directly or calculating layout properties), you should use nextTick to ensure the DOM is ready
  • Example:
    • function showElement() {
        isVisible.value = true;
        // Wait for the DOM to update before accessing the element
        nextTick(() => {
          const element = document.querySelector('div[ref="myElement"]');
          console.log('Element is now in the DOM:', element);
        });
      }
      • When showElement is called, isVisible is set to true, which triggers Vue to render the <div> with the ref="myElement"
      • The nextTick() method is used to ensure the DOM is updated before trying to access the newly rendered element
  • Can be used two ways: with a callback or promise:
    • // Can pass a callback directly to nextTick, which will be executed after the DOM update
      nextTick(() => {
        // Code to run after DOM update
      });
      
      // Or can handle the promise it returns and use async/await syntax
      await nextTick();
      // Code to run after DOM update
      

onMounted

Misc.

Creating a Vue app

  • To create a Vue app, run npm create vue@latest. Choose 'yes' for Vue router, Pinia, and DevTools; no for the rest
  • Create the Vue app: const app = Vue.createApp()
  • Then need to attach it to an element in the DOM: app.mount('#<element_id>')
  • By doing this, Vue will then control anything within that element
    • This includes any kind of dynamic data we want to output inside the element or any events or interactions that occur inside it (mouse click or movement events)
    • Ex: if a user clicked on a button inside a controlled div, then we could have it so that Vue reacts a certain way
  • In order to control a Vue app section, need to pass a root component object to Vue.createApp()
    • This object will be the component controlling the defined app element
    • Inside the object, you can include any data, functions, etc. you want to give it access to
    • Can also include the component HTML template that will be rendered inside the app element
    • It's better to define the template within the HTML file though
  • The HTML (template) within the controlled element can have dynamic data output to it
  • The purpose of using Vue is to make websites, or widgets within a website, more interactive and dynamic
    • One part of this is being able to output dynamic variables / data
    • Ex: dynamically displaying a certain value based upon which button was clicked
    • It doesn't matter what type the dynamic data is
  • We do this by adding a data() function and methods property
    • app.js index.html
      const app = Vue.createApp({
          data() {
              return {
                  title: 'Harry Potter',
                  author: 'JK Rowling'
              }
          },
          methods: {
              changeTitle() {
                  this.title = 'Fantastic Beasts'
              }
          }
      });
      app.mount('#app');
      ...
      <div id="app">
          <p>{{ title }} by {{ author }} (age {{ age }})</p>
          <button v-on:click="age++">Increase age</button>
          <button @click="changeTitle">Change book title</button>
      </div>
      ...
  • methods property
    • With methods: {...} added to the Vue.createApp(), we can add functions that our Vue app can use
    • This can be helpful to change variable values
  • Can then call a defined method in the directive
    • @click="changeTitle"
    • Can also pass in arguments: @click="changeTitle('title1')"
  • Can also change data within the HTML:
    • <button @click="title = 'Fantastic Beasts'">Change book title</button>
⚠️ **GitHub.com Fallback** ⚠️