Vuejs: Difference between revisions
Lsokolowski1 (talk | contribs) m (→Not That) |
Lsokolowski1 (talk | contribs) mNo edit summary |
||
(250 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
{{Cat|JavaScript|Private}} | {{Cat|JavaScript|Private}} | ||
<slideshow style="nobleprog" headingmark="⌘" incmark="…" scaled="true" font="Trebuchet MS" > | <slideshow style="nobleprog" headingmark="⌘" incmark="…" scaled="true" font="Trebuchet MS" > | ||
;title: | ;title: Vuejs Training Course | ||
;author: Lukasz Sokolowski | ;author: Lukasz Sokolowski | ||
</slideshow> | </slideshow> | ||
== Vuejs == | == Vuejs == | ||
Line 30: | Line 30: | ||
=== Framework === | === Framework === | ||
'''Progressive Framework''' - ''"framework that can grow with you and adapt to your needs"'' | '''Progressive Framework''' - ''"framework that can grow with you and adapt to your needs"'' | ||
* framework and ecosystem | * framework and '''ecosystem''' | ||
* designed to be '''flexible''' and incrementally adoptable | * designed to be '''flexible''' and incrementally adoptable | ||
* can be used in | * can be used in | ||
Line 41: | Line 41: | ||
== Overview of Vue JS == | == Overview of Vue JS == | ||
* Declarative rendering | * '''Declarative''' rendering | ||
* Component composition | * Component '''composition''' | ||
<!-- TODO: | |||
* Hot-reloading | * Hot-reloading | ||
* Time-travel debugging | * Time-travel debugging | ||
--> | |||
=== Declarative rendering === | === Declarative rendering === | ||
* Declaring Reactive State | |||
** '''data()''', reserved prefixes ( '''$''', '''_''' ) | |||
** strong use of '''JavaScript Proxies''' | |||
*** ( <small>''https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy''</small> ) | |||
* 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 === | === 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 | |||
<syntaxhighlight lang="html"> | |||
<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> | |||
</syntaxhighlight> | |||
==== Options vs Composition ==== | |||
{| class="wikitable" | |||
|+ API Styles in Vue | |||
|- | |||
! 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 - <small>''https://vuejs.org/guide/extras/composition-api-faq''</small> | |||
==== Options vs Composition, Example ==== | |||
{| class="wikitable" | |||
|- | |||
! Options !! Composition | |||
|- | |||
| <syntaxhighlight lang="html"> | |||
<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> | |||
</syntaxhighlight> || <syntaxhighlight lang="html"> | |||
<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> | |||
</syntaxhighlight> | |||
|} | |||
<!-- TODO: | |||
=== Hot-reloading === | === Hot-reloading === | ||
=== Time-travel debugging === | === Time-travel debugging === | ||
--> | |||
== Setting up a development environment == | == Setting up a development environment == | ||
* Nodejs | * Nodejs, '''nvm''' | ||
* IDEs - '''VSC''' + official ext | * IDEs - '''VSC''' + official Vue ext | ||
** Alternatives with some '''LSP/LSIF''' support: ''WebStorm, Sublime, vim, emacs'' | ** ''Useful plugin'' - '''es6-string-html''' | ||
*** <small>''https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html''</small> | |||
* Alternatives with some '''LSP/LSIF''' support: ''WebStorm, Sublime, vim, emacs'' | |||
* git | * git | ||
* '''Vite''' vs VueCLI (migratable) | * '''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 == | == Creating your first application == | ||
Line 68: | Line 213: | ||
* Second think about the data and behavior that can support the functionality | * Second think about the data and behavior that can support the functionality | ||
* Third think about how to build a user interface for it | * 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' | |||
</pre> | </pre> | ||
Line 80: | Line 228: | ||
#** Names in '''round brackets''' above are the '''properties''' that will support those '''features''' | #** Names in '''round brackets''' above are the '''properties''' that will support those '''features''' | ||
#** We need to include these properties in our '''component''' | #** We need to include these properties in our '''component''' | ||
=== Exercise === | |||
* [[Vuejs#Exercises|Exercise 1.1.]] | |||
* [[Vuejs#Exercises|Exercise 1.2.]] | |||
== Working with Templates == | == Working with Templates == | ||
* Vue uses an '''HTML-based template syntax''' | |||
** declaratively '''binds''' | |||
*** the rendered '''DOM''' to the underlying component instance's '''data''' | |||
* 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''' - <syntaxhighlight inline lang="js">methods : { setInvoice(){} }</syntaxhighlight> | |||
* Or just functions in '''composition's''' <syntaxhighlight inline lang="js">setup() { function setInvoice(){} }</syntaxhighlight> | |||
* '''always called''' whenever a '''re-render''' happens | |||
** must be '''returned''' together with properties | |||
'''Computed property''' - solves '''complex''' logic that includes '''reactive''' data | |||
* New '''option''' - <syntaxhighlight inline lang="js">computed: { getInvoicesPerFranchisee(){} }</syntaxhighlight> | |||
* Or just ref in '''composition's''' setup, wrapped into <syntaxhighlight inline lang="js">computed()</syntaxhighlight> | |||
* '''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 === | ||
* [[Vuejs#Exercises|Exercise 2.]] | |||
* [[Vuejs#Exercises|Exercise 3.]] | |||
== Reactive programming == | == Reactive programming == | ||
* '''unobtrusive''' reactivity system | |||
* adjust to changes in a '''declarative''' way | |||
* '''Proxies''' for reactive objects | |||
* '''getters/setters''' for refs (simple properties) | |||
* ''Reactive Effec''t => '''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''' | |||
<!-- TODO: immutable data, state machines, rxjs, signals, vapoor mode, etc --> | |||
== Directives and data rendering == | == 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: <small>''https://github.com/vuejs/core/blob/main/packages/shared/src/globalsAllowList.ts#L3''</small>) | |||
*** 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; <syntaxhighlight inline lang="js">v-for="(el, index) in listOfEls" :key="index"</syntaxhighlight> | |||
* '''v-on:''' / '''@''' - handling events; <syntaxhighlight inline lang="js">v-on:click="updateInv"</syntaxhighlight> | |||
** '''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 <small>''https://vuejs.org/api/built-in-directives.html''</small> | |||
=== Exercise === | |||
* [[Vuejs#Exercises|Exercise 4.]] | |||
* [[Vuejs#Exercises|Exercise 5.1.]] | |||
* [[Vuejs#Exercises|Exercise 5.2.]] | |||
* [[Vuejs#Exercises|Exercise 6.]] | |||
* [[Vuejs#Exercises|Exercise 7.1.]] | |||
* [[Vuejs#Exercises|Exercise 7.2.]] | |||
* [[Vuejs#Exercises|Exercise 8.]] | |||
* [[Vuejs#Exercises|Exercise 9.1.]] | |||
* [[Vuejs#Exercises|Exercise 10.1.]] | |||
* [[Vuejs#Exercises|Exercise 10.2.]] | |||
<!-- TODO: | |||
== Applying transitions == | == Applying transitions == | ||
--> | |||
== 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:<syntaxhighlight lang="js"> | |||
: property bindings (v-bind) | |||
@ event bindings (v-on) | |||
{{ }} expressions (interpolation) | |||
</syntaxhighlight> | |||
==== Props VS Emitting ==== | |||
Components can '''talk''' | |||
* '''one-way''' bound, '''parent''' to child - '''''props''''' | |||
<syntaxhighlight lang="html"> | |||
<!-- in parent, called 'current-invoice' --> | |||
<!-- restriction: 'reasons' must be only small letters! --> | |||
<voided-invoice v-bind:reasons="getReasons"/> | |||
(...) methods: { getReasons(){} } | |||
<!-- in child, called 'voided-invoice' --> | |||
props: { reasons: { type: Object, required: true } }, | |||
template: `<div>{{ reasons.overdue }}</div>` | |||
</syntaxhighlight> | |||
* '''one-way''' bound, '''child''' to parent - '''''emitting''''' | |||
<syntaxhighlight lang="html"> | |||
<!-- 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) } } | |||
</syntaxhighlight> | |||
<!-- TODO | |||
==== Interpolation ==== | |||
* Replaces the content of the '''markup''' with the value of the expression ('''howManyTries''') | |||
** The value of the component property will be displayed as the contents inside the interpolation tags | |||
* Declaration syntax:<syntaxhighlight inline lang="js">{{expression}}</syntaxhighlight> | |||
** Similar to a JavaScript expression | |||
** Is always evaluated in the '''context''' of the component | |||
** Interpolation tags read the value of the property directly from the component without any need for additional code | |||
* Changes made to component properties are '''automatically synchronized''' with the view | |||
** '''Easier debugging''' when we need to see the '''state''' of the model | |||
** No need to put a breakpoint in code for the value of a component property or method call | |||
* [[Angular_Fundamentals#Example_1_.E2.8C.98|Example]] | |||
<syntaxhighlight lang="html"> | |||
<p class="text-info">No of tries : | |||
<span class="badge">{{howManyTries}}</span> | |||
</p> | |||
</syntaxhighlight> | |||
==== Expressions ==== | |||
* Pieces of plain JavaScript code | |||
** Evaluated in the '''context''' of the component '''instance''' | |||
** '''Associated''' with the '''template''' instance in which they are used | |||
* Should be kept '''simple''' (readability) | |||
** When become complex, should be moved into a method in the component | |||
* New meanings for operators '''|''' and '''?.''' | |||
==== Expressions Con't ==== | |||
Restrictions | |||
* ''Assignment'' is prohibited, except in event bindings | |||
* The ''new'' operator is prohibited | |||
* ''++, -- and bitwise'' operators are not supported | |||
* '''No referring''' to anything in the ''global namespace'' | |||
* No referring to a ''window'' or ''document'' | |||
* No calling ''console.log'' | |||
===== Safe navigation operator ===== | |||
'''?.''' checks for null values in lengthy property paths | |||
* Example <syntaxhighlight inline lang="js">{{ invoice?.item }}</syntaxhighlight> | |||
** If ''null'' value (invoice), it stops processing the path | |||
** But lets the application '''continue running''' | |||
* Good for data loaded ''asynchronously'' | |||
** Because it might not be immediately available to the view | |||
* Prevents the application from crashing | |||
** Loads the data when it is available | |||
==== Binding data ==== | |||
[[File:BindingDataAng4a.png|320px|Binding data in Angular]] | |||
* Property binding | |||
* Event binding | |||
* [[Angular_Fundamentals#Example_1_.E2.8C.98|Example]] | |||
===== Property binding ===== | |||
<syntaxhighlight inline lang="js">[ ]</syntaxhighlight> | |||
* Binding works by '''linking''' the value of the property in '''component class''' to the value of some '''element in the view''' | |||
* The binding is '''dynamic''' | |||
** As the value of the property changes | |||
** The value of the element will be synchronized to the same value | |||
** No need to write any code to do that | |||
<syntaxhighlight lang="html"> | |||
<input type="text" [value]="findJewel" (input)="findJewel = $event.target.value" /> | |||
</syntaxhighlight> | |||
===== Event binding ===== | |||
<syntaxhighlight inline lang="js">( )</syntaxhighlight> | |||
* Event of element is '''bound''' to an expression | |||
* The expression sets the property in component class to '''$event.target.value''' | |||
** The value being entered by the user | |||
* Angular sets up an '''event handler''' for the event that we are binding to | |||
<syntaxhighlight lang="html"> | |||
<button (click)="verifyTheTry()" class="btn btn-primary btn-sm">Verify</button> | |||
<button (click)="initializeGame()" class="btn btn-warning btn-sm">Restart</button> | |||
</syntaxhighlight> | |||
--> | |||
==== 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''' | |||
<!-- TODO * HTML imports --> | |||
==== 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'' | |||
<!-- TODO | |||
==== HTML imports ==== | |||
Provide a way to '''load''' resources (HTML, CSS, JS) in a '''single bundle''' | |||
* Angular does not use HTML imports | |||
** It relies on JavaScript '''module loading''' instead | |||
--> | |||
=== Exercise === | |||
* [[Vuejs#Exercises|Exercise 11.1.]] | |||
* [[Vuejs#Exercises|Exercise 11.2.]] | |||
* [[Vuejs#Exercises|Exercise 11.3.]] | |||
* [[Vuejs#Exercises|Exercise 12.]] | |||
== Routing == | == 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 | |||
*** <syntaxhighlight inline lang="js"><RouterView /></syntaxhighlight> | |||
** user '''navigates''' around the application | |||
*** <syntaxhighlight inline lang="js"><RouterLink to="/courses">Training Catalog</RouterLink></syntaxhighlight> | |||
** then the '''URL updates''' accordingly | |||
*** <syntaxhighlight inline lang="js">$route.fullPath</syntaxhighlight> | |||
** 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 | |||
*** <syntaxhighlight inline lang="js">const routes = [ | |||
{ path: '/courses', component: CoursesView }, | |||
{ path: '/training-methods', component: TrainingMethodsView }, | |||
]</syntaxhighlight> | |||
** declaring router and using it with app | |||
*** <syntaxhighlight inline lang="js">const router = createRouter({ history: createMemoryHistory(), routes, })</syntaxhighlight> | |||
*** <syntaxhighlight inline lang="js">createApp(App).use(router).mount('#app')</syntaxhighlight> | |||
** related '''docs''' - <small>''https://router.vuejs.org/''</small> | |||
* alternatives - other community libraries or our own vanilla plugin/component/composition | |||
=== Exercise === | |||
* [[Vuejs#Exercises|Routing labs]] | |||
== Managing state == | == 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. <syntaxhighlight inline lang="html"><button @click="store.getInvoiceDetails()"></syntaxhighlight> | |||
* 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 === | |||
* [[Vuejs#Exercises|Pinia labs]] | |||
<!-- TODO: | |||
== Creating animations == | == Creating animations == | ||
== Refactoring components == | == Refactoring components == | ||
== Server-side rendering == | == Server-side rendering == | ||
Line 111: | Line 582: | ||
=== State management === | === State management === | ||
=== Build tooling === | === Build tooling === | ||
--> | |||
== Testing your application == | == 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 <small>''https://vitest.dev/''</small> | |||
* Good alternatives - jest, jasmine | |||
<!-- TODO now | |||
== Debugging and performance == | == Debugging and performance == | ||
--> | |||
<!-- TODO | |||
== Embedding Vue.js into existing pages == | == Embedding Vue.js into existing pages == | ||
--> | |||
== Deploying your application to production Vue-CLI == | == Deploying your application to production Vue-CLI == | ||
* '''Vite''' as a replacement for '''Vue-CLI''' | * '''Vite''' as a replacement for '''Vue-CLI''' | ||
** npm run dev | ** npm run dev | ||
** npm run build | ** npm run build | ||
** interactive CLI commands: | ** '''interactive''' CLI commands: | ||
press r + enter to restart the server | press r + enter to restart the server | ||
press u + enter to show server url | press u + enter to show server url | ||
Line 144: | Line 630: | ||
* '''Awesome''' | * '''Awesome''' | ||
** <small>https://github.com/vuejs/awesome-vue</small> | ** <small>https://github.com/vuejs/awesome-vue</small> | ||
* Tailwind CSS - good alternative(and more) to Bootstrap | |||
** Setup <small>''https://v2.tailwindcss.com/docs/guides/vue-3-vite''</small> | |||
== 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 | |||
##* <syntaxhighlight inline lang="js">jewels: ['ruby','diamond','agate','amber','aquamarine','amethyst','opal','tourmaline','emerald','onyx','pearl','sapphire']</syntaxhighlight> | |||
## Update our main html template with '''howManyTries''' | |||
##* <small>''Exercise: improve it with dynamic data''</small> (''index.html'') | |||
##* observe it in the browser - use '''Vue dev-tools''' | |||
# Design the '''engine''' of our game - '''initGame()''' method | |||
#* it should '''set values''' for our game properties | |||
#* supposed to '''randomly choose''' the jewel ( <syntaxhighlight inline lang="js">Math.floor((Math.random() * 12) + 1)</syntaxhighlight> ) | |||
#* 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''':<syntaxhighlight lang="js"> | |||
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' }, | |||
]</syntaxhighlight> | |||
# Communicate '''The Gamers''' - win or loose, huh? (---' | |||
## interactive message shown to gamer (''Drax the '''Destroy'''er'') | |||
##* first use '''only one''' type of directive '''(&''' | |||
##** <small>''Exercise: improve this section with game logic''</small> (index.html) | |||
##* test it in '''Vue dev-tools''' | |||
##* later on, improve it with two '''different''' directives '''(&''' | |||
## do same thing, but play '''hide-and-seek''' only | |||
##* test it in '''Vue dev-tools''' | |||
# ''"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 <syntaxhighlight inline lang="js">this.$el.querySelector()</syntaxhighlight> | |||
##* <small>''Exercise: improve this header with a red background, when hovered''</small> (index.html) | |||
# '''''"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) | |||
# '''''"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) | |||
# '''''"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'' - <syntaxhighlight inline lang="js">app.component('gems', {})</syntaxhighlight> | |||
##* '''synchronize''' it with parent's main '''template''' (''index.html'') | |||
##* add new '''html snippet''' as option - <syntaxhighlight inline lang="js">template: /*html*/ `<div>works</div>`</syntaxhighlight> | |||
##* 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'') | |||
## '''''"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''' | |||
# 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) | |||
<!-- TODO: adding tailwind css, improve route, pinia, and full-stack presu and exercises --> | |||
<!-- | |||
ad. 4.1-2. - v-if=hinting + v-else=hinting VS v-if=hinting===false + v-else=hinting===true VS v-if=hinting===false + v-if=hinting===true VS v-show ways | |||
ad. 5.2. - computed VS methods, methods works | |||
--> |
Latest revision as of 10:07, 22 November 2024
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' -->
<!-- restriction: 'reasons' must be only small letters! -->
<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 howManyTries
- 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)