Vuejs
Vuejs
Vuejs Training Materials
Copyright Notice
Copyright © 2004-2023 by NobleProg Limited All rights reserved.
This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise.
Introduction
- JavaScript framework for building UI (user interfaces)
- What do we need?
- HTML, CSS, and JavaScript
- Declarative, component-based programming model
- Declarative rendering
- Reactivity
- Progressive framework
- Single-file components (SFC)
- API Styles - options VS composition
Not That
BUT still - we'll have a lot to see! (-'
Framework
Progressive Framework - "framework that can grow with you and adapt to your needs"
- framework and ecosystem
- designed to be flexible and incrementally adoptable
- can be used in
- enhancing static HTML without a build step
- embedding as Web Components on any page
- Single-Page Application (SPA)
- Fullstack / Server-Side Rendering (SSR)
- Jamstack / Static Site Generation (SSG)
- targeting desktop, mobile, WebGL, and even the terminal
Overview of Vue JS
- Declarative rendering
- Component composition
Declarative rendering
- Declaring Reactive State
- data(), reserved prefixes ( $, _ )
- strong use of JavaScript Proxies
- Declaring Methods - methods()
- this is bind automatically (no arrows here!)
- accessible in template (mostly as event listeners)
- Deep Reactivity - by default
- detects even mutation of nested obj or array
- DOM Update Timing - not synchronous, buffered until the nextTick(), fixable with async..await
- in the update cycle - ensures each component updates only once (with all the state changes)
- Stateful Methods - dynamically created methods
- in reused components - to avoid interference use created() lifecycle hook
Component composition
- SFC - Single-File Components
- Options API vs Compositions API - two styles
SFC
- HTML-like file format (*.vue)
- logic (JS), template (HTML), and styles (CSS) - together
- for build-tool-enabled projects
- recommended way
- Example
<script>
export default {
data() {
return {
coursesInCart: 0
}
}
}
</script>
<template>
<button @click="coursesInCart + 1">You have: {{ coursesInCart }} courses in your bucket!</button>
</template>
<style scoped>
button {
font-size: 48px;
}
</style>
Options vs Composition
Compare of | Options API | Composition API |
---|---|---|
Component's logic | Object with options (data, methods, mounted, etc) | Imported API functions (ref, reactive, onMounted, etc) |
Properties | Exposed on this in functions | Declared with functions (ref) |
Syntax (some bits only) | the usual <script> | <script setup>, less boilerplate |
Common web use cases | covered | covered |
Centered on | component instance (this), beginner-friendly, organized | state directly in function, free-form, more flexible |
For production use | no build tools, low-complex cases | builds and full apps (+SFC) |
Options vs Composition Con't
- different interfaces - same underlying system
- Options are implemented on top of the Composition
- fundamental concepts and knowledge - shared across them
- more here - https://vuejs.org/guide/extras/composition-api-faq
Options vs Composition, Example
Options | Composition |
---|---|
<script>
export default {
// Properties returned from data() become reactive state and will be exposed on `this`.
data() {
return {
invoice: { id: 'INV_integer', type: 'default', owner: 'countryPrefix_franchisee' }
},
// Methods are functions that mutate state and trigger updates.
// They can be bound as event handlers in templates.
methods: {
setInv() {
this.invoice = { id: 'INV2378', type: 'consultancy', owner: 'NPUK_franchisee' }
}
},
// Lifecycle hooks are called at different stages of a component's existence.
// This function will be called when the component is mounted.
mounted() {
console.log(`The invoice for this ${this.invoice.type} is ${this.invoice.id}.`)
}
}
</script>
<template>
<button @click="setInv">Send invoice from: {{ invoice.owner }}</button>
</template>
|
<script setup>
import { ref, onMounted } from 'vue'
// reactive state
const invoice = ref( { id: '', type: '', owner: '' } )
// functions that mutate state and trigger updates
function setInv() {
invoice.value = { id: 'INV2378', type: 'consultancy', owner: 'NPUK_franchisee' }
}
// lifecycle hooks
onMounted(() => {
console.log(`The default invoice has an empty ID: ${invoice.value.id}.`)
})
</script>
<template>
<button @click="setInv">Send invoice from: {{ invoice.owner }}</button>
</template>
|
Setting up a development environment
- Nodejs, nvm
- IDEs - VSC + official Vue ext
- Useful plugin - es6-string-html
- Alternatives with some LSP/LSIF support: WebStorm, Sublime, vim, emacs
- git
- Vite vs VueCLI (migratable)
- ??
vue itself (later on)
npm create vue@latest # answer the interactive questions accordingly (proj-name, ts, routing, pinia, testing, etc) cd proj-name ; npm i ; npm run format ; npm run dev
OR
npx create-vue proj-name # answer the questions cd proj-name ; npm i ; npm run format ; npm run dev
Creating your first application
Build game "Find the jewel". The objective of the game is to find a random computer-chosen jewel in as few tries as possible. Approach: * First think about the functionality we want to offer * Second think about the data and behavior that can support the functionality * Third think about how to build a user interface for it Test it with 'live-server' (if not yet installed in the training machine, run 'sudo npm i -g live-server') * in command line go to application's root folder and execute the command 'live-server --cors'
- Designing the component
- Detailing the features that we want the app to support
- Choosing random jewel (origJewel)
- Providing input for a user to find the jewel (findJewel)
- Tracking the number of tries already made (howManyTries)
- Giving the user hints to improve their try based on their input (hinting)
- Giving a success message if the user found the proper jewel (hinting)
- Determining what we need to display to the user and what data we need to track
- Names in round brackets above are the properties that will support those features
- We need to include these properties in our component
- Detailing the features that we want the app to support
Exercise
Working with Templates
- Vue uses an HTML-based template syntax
- declaratively binds
- the rendered DOM to the underlying component instance's data
- declaratively binds
- syntactically valid HTML
- parseable by spec-compliant browsers and HTML parsers
- compiled into highly-optimized JS - when the app state changes
- intelligently re-renders the minimal no of components
- applies the minimal amount of DOM manipulations
- can be written directly as render functions instead
- JSX is optionally supported
- but not the same level of compile-time optimizations
Methods and computed properties
Method - manages actions, mutates state, etc
- New option -
methods : { setInvoice(){} }
- Or just functions in composition's
setup() { function setInvoice(){} }
- always called whenever a re-render happens
- must be returned together with properties
Computed property - solves complex logic that includes reactive data
- New option -
computed: { getInvoicesPerFranchisee(){} }
- Or just ref in composition's setup, wrapped into
computed()
- cached based on its reactive dependencies
- getter-only by default, changeable with giving it the setter
- should be side-effect free, don't:
- mutate other state
- make async requests
- mutate the DOM inside a computed getter
- also mutating its own computed value
Exercise
Reactive programming
- unobtrusive reactivity system
- adjust to changes in a declarative way
- Proxies for reactive objects
- getters/setters for refs (simple properties)
- Reactive Effect => watch(option) or watchEffect()(api)
- Runtime vs Compile-time - Vue works with Runtime
- Debugging (only in dev mode!)
- components - hooks again: renderTracked() and renderTriggered()
- computed and watcher - onTrack() and onTriger() callbacks in computed() 2nd arg
- Ext state systems - shallowRef
Directives and data rendering
Data binding - syntax Name:Argument.Modifiers="Value"
- {{ }} - text interpolation, JS expressions (not statements!); interpreted as plain text
- functions too, but without side effects (changing data, triggering asynchronous code)
- restricted JS globals (more here: https://github.com/vuejs/core/blob/main/packages/shared/src/globalsAllowList.ts#L3)
- fixable by adding our custom globals to app.config.globalProperties
- v-html - raw HTML somewhere
- v-bind: / : - attribute directive / shortcut
- from Vue 3.4+ even shorter, if same name :id="id" => :id
- multiple attrs - just v-bind without argument
Built-in directives, some
Syntax - prefix v-
- v-if, v-else - logic for adding/removing elements; destroying
- v-show - conditional rendering; hiding only (display prop)
- v-for: - listing elements;
v-for="(el, index) in listOfEls" :key="index"
- v-on: / @ - handling events;
v-on:click="updateInv"
- v-on:[eventName] - dynamic, 'eventName' should be string or null
- v-on:submit.prevent - with modifier, does event.preventDefault()
- v-bind:[attrName] - also dynamic, 'attrName' should be string or null
- v-model - 2 way data binding, between model(data) and view(template)
- more here https://vuejs.org/api/built-in-directives.html
Exercise
- Exercise 4.
- Exercise 5.1.
- Exercise 5.2.
- Exercise 6.
- Exercise 7.1.
- Exercise 7.2.
- Exercise 8.
- Exercise 9.1.
- Exercise 10.1.
- Exercise 10.2.
Dividing the application into smaller, self-contained components
- Component Pattern instead of MVC
- Constructs
- expressions, data binding syntax
- Change detection
- Web components
Component pattern
In web applications
- No messes of spaghetti code in applications
- Reasoning about specific parts of the application in isolation from the other parts
- Maintenance costs are less
- Each component's internal logic can be managed separately
- No affecting the other parts of the application
- Self-describing components
- Makes the application easier to understand at a higher level of abstraction
Constructs
- Usually they live in the HTML template
- They are linking the view HTML and component code
- Similar to standard HTML with new symbols:
: property bindings (v-bind) @ event bindings (v-on) {{ }} expressions (interpolation)
Props VS Emitting
Components can talk
- one-way bound, parent to child - props
<!-- in parent, called 'current-invoice' -->
<voided-invoice v-bind:reasons="getReasons"/>
(...) methods: { getReasons(){} }
<!-- in child, called 'voided-invoice' -->
props: { reasons: { type: Object, required: true } },
template: `<div>{{ reasons.overdue }}</div>`
- one-way bound, child to parent - emitting
<!-- in parent, called 'current-invoice' -->
<voided-invoice v-bind:reasons="getReasons" @void-confirmed="excludeInv"/>
(...) methods: { getReasons(){}, excludeInv(){} }
<!-- in child, called 'voided-invoice' -->
props: { reasons: { type: Object, required: true } },
template: `<div>{{ reasons.overdue }}</div>`
(...) methods: { giveConfirmation(){ this.$emit('void-confirmed', this.confirmed) } }
Change detection ⌘
- State maintenance
- Change detection
State maintenance ⌘
- Vue apps are dynamic
- Dynamic values are kept up-to-date as the data in an application gets updated
- Component is the container for the state
- all the properties in the component instance and their values are available for the template instance that is referenced in the component
- we can use these values directly in expressions and bindings in the template without having to write any plumbing code to wire them up
Change detection ⌘
Vue keeps track of changes in the component as it runs
- Is reacting to events in the application
- Uses change detectors
- Go through every component to determine whether anything has changed that affects the view
- Event triggers the change detection cycle
- Identifies that the property that is being used in the view has changed
- Updates the element in the view that is bound to property with the new value of it
Change detection Con't ⌘
- Multi-step process where Vue
- First updates the components and domain objects in response to an event
- Then runs change detection
- Lastly re-renders elements in the view that have changed
- This is done on every browser event
- Also other asynchronous events (XHR requests, timers, etc)
- one-way data binding
- Change detection in Vue is reactive and one way
- Makes just one pass through the change detection graph
- It vastly improves the performance of Vue
Web components ⌘
Standards for web browsers
- Custom elements
- Shadow DOM
- Templates
Custom elements ⌘
Enable new types of element to be created
- Other than the standard HTML tag names (div, p, etc)
- Custom tags provides a location on the screen that can be reserved for binding a component
- Separation of component from the rest of the page
- Makes it possible to become truly self-contained
Shadow DOM ⌘
Provides a hidden area on the page for scripts, CSS, and HTML
- Markup and styles in it
- Will not affect the rest of the page
- Will not be affected by the markup and styles on other parts of the page
- Component can use it to render its display
Templates ⌘
Repeatable chunks of HTML
- Have tags that can be replaced with dynamic content at runtime using JavaScript
- Web Components standardize templating
- Provide direct support for it in the browser
- Can be used to make the HTML and CSS inside the Shadow DOM used by the component dynamic
Exercise
Routing
Vue Router - separate library, officially-supported, recommended by Vue creators
- official solution especially for Vue's single page apps (SPAs)
- to tie the browser URL to the content seen by the user
<RouterView />
- user navigates around the application
<RouterLink to="/courses">Training Catalog</RouterLink>
- then the URL updates accordingly
$route.fullPath
- and page doesn't need to be reloaded from the server
- built on Vue's component system
- configurable routes - component shown for each URL path
const routes = [ { path: '/courses', component: CoursesView }, { path: '/training-methods', component: TrainingMethodsView }, ]
- declaring router and using it with app
const router = createRouter({ history: createMemoryHistory(), routes, })
createApp(App).use(router).mount('#app')
- related docs - https://router.vuejs.org/
- to tie the browser URL to the content seen by the user
- alternatives - other community libraries or our own vanilla plugin/component/composition
Exercise
Managing state (Pinia)
- For simple and medium apps - Vue's own Reactivity API
- Vue's reactivity system is decoupled from the component model (extremely flexible!)
- first - try lifting the state up
- if we go bigger, then use top level store.js
- put data and shared methods there
- use them in components, i.e.
<button @click="store.getInvoiceDetails()">
- large-scale production apps - use Pinia
- state management library (previous was Vuex)
- maintained by the Vue core team(!)
Managing state con't
Pinia implements considerable things
- conventions for team collaboration are stronger
- better integration with Vue DevTools
- timeline, in-component inspection, time-travel debugging
- Hot Module Replacement
- Server-Side Rendering support
Exercise
Testing your application
Mostly
- Unit - inputs produce the expected output or side effects (function, class, or composable)
- Component - mounts, renders, can be interacted with, and behaves as expected.
- import more code, more complex, executes longer
- End-to-end - features that span multiple pages
- real network requests to our production-built app
- often involve backend (database, etc)
- Vue guys recommend Cypress, other popular options: Playwright, Nightwatch, WebdriverIO
- How? - use Vitest
- designed, created and maintained by Vue / Vite team members
- integrates with Vite-based projects with minimal effort, is really fast
- More here https://vitest.dev/
- Good alternatives - jest, jasmine
Deploying your application to production Vue-CLI
- Vite as a replacement for Vue-CLI
- npm run dev
- npm run build
- interactive CLI commands:
press r + enter to restart the server press u + enter to show server url press o + enter to open in browser press c + enter to clear console press q + enter to quit
Scaling your application
- Lighter-weight way (no-build-step usage)
- TS - vue-tsc instead of just tsc
- Testing - Vitest rather then Jest; Cypress is preferred
- More here
Helpers
- Official devtools
- Testing
- Awesome
- Tailwind CSS - good alternative(and more) to Bootstrap
Exercises
- Simple ftj - discussing app skeleton (index.html, main.js)
- Map the state in data() (main.js)
- add all the properties from designing part
- use this property below as our main model for now
jewels: ['ruby','diamond','agate','amber','aquamarine','amethyst','opal','tourmaline','emerald','onyx','pearl','sapphire']
- Update our main html template with howmManyTries
- Exercise: improve it with dynamic data (index.html)
- observe it in the browser - use Vue dev-tools
- Map the state in data() (main.js)
- Design the engine of our game - initGame() method
- it should set values for our game properties
- supposed to randomly choose the jewel (
Math.floor((Math.random() * 12) + 1)
) - and of course allow to cheat (-'
- oh, don't forget to spoil(run) it, do it with hook, yeahhh.. let's go meaty-nasty now! (--'
- Describe first jewel in a new paragraph - say "ruby is red"
- use computed property
- for that let's improve slightly our model:
gems: [ { id:1, jname: 'ruby', jcolor: 'red' }, { id:2, jname: 'diamond', jcolor: '#b9f2ff' }, { id:3, jname: 'agate', jcolor: 'blue' }, { id:4, jname: 'amber', jcolor: 'orange' }, { id:5, jname: 'aquamarine', jcolor: 'aquamarine' }, { id:6, jname: 'amethyst', jcolor: 'violet' }, { id:7, jname: 'opal', jcolor: 'turquoise' }, { id:8, jname: 'tourmaline', jcolor: 'black' }, { id:9, jname: 'emerald', jcolor: 'green' }, { id:10, jname: 'onyx', jcolor: 'gray' }, { id:11, jname: 'pearl', jcolor: '#EAE0C8' }, { id:12, jname: 'sapphire', jcolor: '#2554C7' }, ]
- Communicate The Gamers - win or loose, huh? (---'
- interactive message shown to gamer (Drax the Destroyer)
- first use only one type of directive (&
- Exercise: improve this section with game logic (index.html)
- test it in Vue dev-tools
- later on, improve it with two different directives (&
- first use only one type of directive (&
- do same thing, but play hide-and-seek only
- test it in Vue dev-tools
- interactive message shown to gamer (Drax the Destroyer)
- "Fire!! Gather them together, quickly!" - grouping kids (2Kill'em all..), (----'
- Show all the jewels in one view (use 'ul')
- For each jewel add its length at the end - ruby 4
- ..then divide it by the size of jewels array.. - ruby 0.3333333333333333
- ..and then round it to integer.. - ruby 1
- ..and then wrap it with logic: if 1, show ' is long', otherwise show ' is short'.. - ruby is short
- ..and then - improve it with (...) ?? (small discussion together)
- Help the gamer - when the number of tries is 5, Restart game button should become huge (48px)
- add new css class(.gameKiller) and bind it (how?)
- "Bring me some action!" - Sly?
- Restarting the game should re-initialize our state in model
- Mower the title - when Bloody is hovered, change its background to red..
- do it for only h2 elements - use
this.$el.querySelector()
- Exercise: improve this header with a red background, when hovered (index.html)
- do it for only h2 elements - use
- "Take me to the battle!" - Arnie?
- Let's finish our engine - Verify button plays the game
- we need two-way bounds, use proper directive then
- what about the engine(random choice) itself, huh? something there is just wrong.. what? how to fix it? (-'
- "What about sex, huh?" - Bridget?
- Make sure gamer can add new jewel
- cloning is prohibited.. (no same jewel twice)
- no ghosts too.. (empty jewel)
- HOMEWORK: separate one gem from the pack and.. kill it! (add Remove button)
- Make sure gamer can add new jewel
- "True pack never leaves any fellow behind!" - Thor?
- List gems in divs
- use gem id as key
- make gems shine (colorize them accordingly)
- Improve gem description (p), it should use the actual hovered gem
- new property (pointing the gem), new method (handling the choice)
- List gems in divs
- "How to kill one stone with many birds?" - my Boss? (--------------'
- Cut gems related family and put it into its own separate - guess what..? (-;
- create new folder components and filo components/Gems.js -
app.component('gems', {})
- synchronize it with parent's main template (index.html)
- add new html snippet as option -
template: /*html*/ `<div>works</div>`
- and finally parent calls the child - and.. it refuses and it cries and it.. (you know what, the usual..)
- play it with dev-tools
- move here everything related only to gems (not jewels! leave them be)
- create new folder components and filo components/Gems.js -
- "I'm a sex machine!" - adding new gem in another component (-'
- gems should be the father.. yup.. 3rd level now(!), yay !!
- use full form element
- use input as button
- "Make them talk!" - Don Padre(The Godfather)?
- root component tells gems what should be in its brand new h4 list remark (props)
- mount another instance of gems, do not pass the prop - what can you tell? (-'
- gems tells son how many brothers and sisters he has (props)
- add-gem tells father what to do and how.. (ahhh.. kids.. they become grown-ups so fast these days..)
- use emitting and custom event
- root component tells gems what should be in its brand new h4 list remark (props)
- Cut gems related family and put it into its own separate - guess what..? (-;
- Migration - painful, boring(!?), nightmare..
- Migrate our app into Composition style
- use official vue CLI
- use TS (TypeShit.. oups.. TypeShut.. aaahh.. TypeScript)
- why tooling and bundling and change-detection and hot-reloading and..
- Routing - how to navigate with a yellow submarine
- route our ftj app
- State management - SCRUMs, UMLs, NVCs, etc
- Pinia and its flavors
- "Stack it in your..!" - Hamish (the "detective")?
- Wrapping into full-stack, with db, rest api, etc
- ? (HOMEWORKS)