Options:
import CDN package in page/ download JS files / install using npm
Official CLI (uses webpack): yarn global add @vue/cli
Use Vite tool (fast serve due to native ES Module import)
Vue Devtools - browser plugin for debugging
Builds:
for CDN or without bundler: vue(.runtime).global(.prod).js
for with a bundler: vue(.runtime).esm-bundler.js
for server side: vue.cjs(.prod).js - for Node.js serverside rendering via require()
Runtime ONLY vs. Runtime + Compiler
.vue are pre-compiled, no need compiler
provide template as string at run time requires compiler
Application - the first component
Component:
format: with code or single-file-component (recommended)
to develop: provide (or export, in single-file-component) component options, see https://v3.vuejs.org/api/options-api.html
capability:
data:
binding
computed: dependency-aware cached
watch: data/computed triggered function / method call
methods
parent / children communications:
props is REACTIVE, TYPE CHECKED, and PARENT=>CHILD: https://v3.vuejs.org/api/options-data.html#props
provide / inject: https://v3.vuejs.org/guide/composition-api-provide-inject.html
through $refs directly manipulate instance (NOT TYPE CHECKED): https://v3.vuejs.org/guide/component-template-refs.html
children access parent as $parent (NOT TYPE CHECKED)
$emit custom events
lifecycle hooks: https://v3.vuejs.org/api/options-lifecycle-hooks.html
[advanced]
custom events: https://v3.vuejs.org/api/options-data.html#emits
custom directives: https://v3.vuejs.org/guide/custom-directive.html#intro https://v3.vuejs.org/api/options-assets.html#directives
custom rendering function: provide a custom rendering function
Nesting
Distributed by slots: https://v3.vuejs.org/guide/component-basics.html#content-distribution-with-slots
yarn global add @vue/cli @vue/cli-service-global
vue create hello-world
vue upgrade --next
$ yarn create @vitejs/app <project-name> // select: vue, vue-ts
// alternative try create-vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev
Use Volar plugin for type checking
Always open browser debug console or bring warnings & erros to front by configuring app instance warningHandler & errorHandler
Supporting libraries: https://github.com/vuejs/awesome-vue
Might explore (may be useful):
Bit - Manage and reuse vue components between projects. Easily isolate and share components from any project without changing its source code, organize curated collections and install in different projects.
ComponentFixture - is a component design to develop and test other components, automatically binding their props.
import { createApp } from 'vue'
const app = createApp({})
const vm = app.mount('#app')
<div id="app"></div>
component: register global components - available in sub components
app.component('component-name', componentInstance) // register component, name: all lower case, W3C rule
config (NOT options, don't get confused): app.config({}) or (preferred for better typing) app.config.xxx=xxx
errorHandler: handle uncaught errors
warnHandler: runtime warnings (only during development)
globalProperties: properties available to all components
optionMergeStrategies - about custom option
performance - enables performance tracing in development mode only in supported browsers
compilerOptions - runtime compiler options
directive: custom directives
mixin: minins
mount / unmount: mount the root component
provide: component provide/inject
use: installs a plugin
version
Lifecycle hooks: https://v3.vuejs.org/api/options-lifecycle-hooks.html
names:
w3c rule
kebab-case
Registration:
local / global (with App instance)
Module system:
components can be import / exported
One-way Data Flow:
reactivity parent -> child
do not mutate prop inside child : result in runtime warning
instead pass prop as initial value to a data
however objects / arrays passed as reference CAN be mutated inside child
Validation:
see Component Options
attribute or event listener, that is
passed into the component
but not defined in "props" or "emits"
they are accessible in $attrs property
attribute inheritance:
if component returns a single root node (such as a <div>)
non-prop attributes (attributes and event listeners) automatically added to the root node (so no need to redefine)
to disable, pass "inheritAttrs" to component's options (scenario: disable inheritance to an outside element, bind $attrs to an inner element)
for multiple root component, if $attrs not bound explicitly, will result in runtime warning
see "Event" section
see "Slots"
Register for reference:
in template, register a HTML element or component <input ref="input" />
access the instance: this.$refs.input.focus()
https://v3.vuejs.org/api/options-api.html
data() : function return data object
MY STUPID MISTAKE: ()=>{label:value} is WRONG, ()=>({key:value}) is CORRECT
why function? object literal will be shared between instances
declare root level properties upfront, added root level property has no reactivity
once instance created accessible at $data
"$" and "_" reserved for build-in / internals
props: ['prop1', 'prop2']: component props to accept data from parent
props: ['title'], // basic, as array of strings
name: in template camelCased prop names will be write as kebab-cased
as Object:
props: { title: String, likes: Number } // name : runtime Type, produces runtime warning
String, Number, Boolean, Array, Object, Function, Promise, or any other constructor
propB: [String, Number], // multiple types
propC: { type: String, required: true }, // with requirements
required : boolean
default :
number, string, etc.: default: 100
object, array: default() {return { message: 'hello' }} // must return from a factory function, otherwise all components share same default
validator(value){/* validator returns true or false */} // validation produces a console warning, and instance is not available at this time
computed: type { [key: string]: Function | { get: Function, set: Function } }, computed values
CACHED, based on dependency (method not cached)
by default getter only, can also provide a setter
computed: { computedValue() { return "value"
computed: { fullName: { get() {//... }, set(newValue) {//... }}
methods: { [key: string]: Function }
do not use => arrow function
"this" will be bound to component instance
debouncing & throttling: use Lodash
watch: { [key: string]: string | Function | Object | Array} - watch key (include data & computed), callback - function, or method name
emits: Array<string> | Object - custom events that can be emitted from component - see Event section
template: string (no need for single-file-component)
render: custom render function (advanced)
Lifecycle hooks - named functions, "this" point to the active instance
custom directives
components: local components, only made available to current component instance
mixins: array of mixins
extends: declarative extending another component - either an option object or a constructor - make easier to extend single file components
provide / inject
setup: Function, as entry point, called before "beforeCreate" hook (advanced)
name: component name, allows component recursively invoke itself
inheritAttrs (advanced): boolean (default true) - parent attribute NOT bind to props "fallthrough"
compilerOptions:
delimiters (default '{{', '}}'
...
Component properties manipulated within component is type-checked.
Accessing child / parent component: need to cast $ref.childComponent and $parent to InstanceType<typeof Component>
(this.$parent as InstanceType<typeof App>).intVal
These starts with $
Ref: https://v3.vuejs.org/api/instance-properties.html
$data: the reactive data object
$props: props received
$el: the root DOM element, may be the "placeholder" node for "fragments" component
$options: the instantiation options (for custom option properties)
$parent: parent component
$root: root component
$slots: named slots (for content distribution)
$refs: named reference to child [DOM elements] and child [component instances], to appear here, child DOM elements / component instances need to have a "ref" attribute, see https://v3.vuejs.org/guide/component-template-refs.html
$attrs: non-prop attributes, https://v3.vuejs.org/guide/component-attrs.html
data options becomes instance properties
computed becomes instance properties
methods becomes instance methods
Single file template:
allow each section (template, script, style) use different pre-processors (so use a different language than / convert to HTML, JS & CSS)
can still separate file outside, example: <script src="./my-component.js"></script>
Tooling:
Vue CLI deals with tooling, otherwise config web-pack with vue-loader
need to build in esm
if use CLI / webpack, need to rely on "rollup"
using components:
certain restrictions (like <table> restricts what can be inside): use <tr is="vue:actual-component">
case insensitility:
HTML component names, attribute names are case insensitive: camelCase prop names and event handler parameters need to use kebab-cased equivalents
props: ['postTitle'], // defined in camelCase
<blog-post post-title="hello!"></blog-post> // use in kebab-case
Template Expressions:
Inside template, expect Javascript (not Typescript) Expression (not statement) syntax ONLY
Restricted list of globals: https://github.com/vuejs/vue-next/blob/master/packages/shared/src/globalsWhitelist.ts#L3
{{ }}
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
flow: reactive, one-way down
parent reactive pass down to child, and child prop is reactive (further pass down)
one way, no child -> parent (use event)
WARNING: do not mutate prop
when using:
without using v-bind, pass in string
v-bind without argument: pass in multiple attributes
Number props: <my-component :likes="42"> provides number 42, without using v-bind, will be string 42
Boolean props: including prop indicates true, or use v-bind to give boolean value
example: <div v-bind:id="dynamicId"></div>
v-bind - the directive
":id" - the directive's "argument" (here what HTML element attribute to bind)
argument:
<a v-bind:href="url"> // "href" is the argument
Dynamic argument: <a v-bind:[attributeName]="url"> ... </a>
inside []: a Javascript expression (keep it simply, this is inside a HTML attribute)
if JS evaluates null, remove the binding
"url" : a Javascript expression
modifiers: <form v-on:submit.prevent="onSubmit">...</form>
separate with argument with a dot, used by certain directives
shorthand syntax:
v-on:someEvent => @someEvent
<a v-on:click="doSomething"> => <a @click="doSomething">
<a v-on:[event]="doSomething"> => <a @[event]="doSomething">
v-bind:someAttribute => :someAttribute
Different from "prop", can pass data down the whole component tree from parent to several levels deep down
What / how to provide & inject
simple data: provide : {key : "value"}
instance data: provide()
reactivity:
what is passed? if it's a value (such as this.todos.length) it won't react
pass a ref or reactive object
todoLength: Vue.computed(() => this.todos.length)
Provide:
in options, provide the data in "provide" option
provide: {user: 'John Doe' },
provide() {return { todoLength: this.todos.length } // provides instance property
what CANNOT be provided
component instance property, directly
Inject:
in options, "inject" option, list the injected data
inject: ['user'],
v-text (same as {{ }}) : content will be escaped
v-html:
SECURITY IMPLICATION: no user content injection!
<div v-html="jsExpressionEvaluatesToRawHtml"></div>
v-pre:
<span v-pre>{{ this will not be compiled }}</span>
no compilation and all childrean
able to display raw {{ }} (another way is to change compile option)
v-cloak:
combine with CSS rule, able to apply a style when an element is still in compile (not ready), to hide ugly thing before ready
v-once:
render ONCE only <span v-once>This will never change: {{msg}}</span>
Options:
v-bind:class / v-bind:style (with enhancement provided by vue)
evaluate to string - nothing special, but calculate final string is annoying
bind to "class" of HTML element:
an Object
<div :class="{ active: isActive }"></div>
an object, key is class, value is truthiness - determine the presence of the class
can co-exist with plain "class": <div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
a string array
"class" attribute of <your-component>:
if <your-component> has SINGLE ROOT element, "class" of the <your-component> adds to (and do not replace existing) the classes of that root element
if <your-component> has MULTIPLE ROOT elements, use $attrs component property in the template, <p :class="$attrs.class">Hi!</p>
"style" of HTML element binding:
an Object: just CSS as JS Object <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
an Array of multiple objects, merge together
vendor prefix https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix automatic
can provide an array of multiple prefixed values, only the last with browser support rendered
https://v3.vuejs.org/api/sfc-style.html#state-driven-dynamic-css
Use in Component, as a distribution outlet for inner content
Define: in DEFINING component template,
<slot></slot> (name "default")
with fall back content: <slot>fallback content when absent</slot>
named: <slot name="footer"></slot>
Dynamic name (dynamic directive argument): <template v-slot:[dynamicSlotName]>
scoped: <slot :bindedName="definingComponentData"></slot> // bind "item" inside slot with "item" in DEFINING component so INNER CONTENT in USING COMPONENT can access!!
Use: in USING template (if slot not defined content will be discarded)
<your-component>some INNER CONTENT, may include other components</your-component>
named: <your-component><template v-slot:footer> <p>Here's some content in footer</p> </template></your-component> (provide content to specific slot, use "default" for default slot)
shorthand: <template #footer>
with scope binding: <your-component><template v-slot:default="slotProps">{{slotProps.bindedName}}
how it works: wraps the slot content in a function: function(slotProps)
destructing syntax (fit in function parameter): <todo-list v-slot="{ item }"><span class="green">{{ item }}</span></todo-list>
no need "slotProps"
shorthand: <todo-list #default="{ item }">
"item" is defined in scoped slot binded-name
destructive and rename syntax: <todo-list v-slot="{ item: todo }"><span class="green">{{ todo }}</span></todo-list>
fallback, in case slot prop undefined: <todo-list v-slot="{ item = 'Placeholder' }"><span class="green">{{ item }}</span></todo-list>
abbriviated syntax, when defined only single default slot with binding: <your-component v-slot="slotProps"> // save a "template", when DEFINING component only has single default slot
Effect: inner content ("some content....") rendered in place of <slot>
https://v3.vuejs.org/guide/component-slots.html#scoped-slots
Default, INNER CONTENT (inside component, i.e. <your-component>{{someVariable}}</your-component>) has access to:
same instance properties as rest of template (i.e. in using scope), i.e. someVariable in the using component's data
NOT the "your-component" component
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope.
SCOPED SLOTS (slot props):
normally USING template provides INNER CONTENT, SLOT defined in DEFINING component, INNER CONTENT(in parent template) has no access to data (scope) in DEFINING(child) component
scoped - uses v-bind syntax to bind data of DEFINING component to SLOT as a prop <slot :item="item"></slot>, so that INNER CONTENT in USING template has access to it
v-slot directive (use with <template> to provide content to a slot, in above "scoped"):
shorthand: #
argument: slot name (default to "default")
expect: optional - only when pass props to the slot
use with:
<template>: pass content to named slot
components: for a lone default slot with props
Dynamic switch component, like tab content
Syntax:
<component :is="currentTabComponent"></component>
https://v3.vuejs.org/api/built-in-components.html#component
props: is
expects:
string: HTML tag name or Component name (registered)
component's options object (the Component)
Keep alive:
by default, not kept alive - each time on switch a new component is (re)created
state lost
<keep-alive> <component :is="currentTabComponent"></component> </keep-alive>
https://v3.vuejs.org/guide/component-dynamic-async.html#async-components
defineAsyncComponent(()=>new Promise((resolve, reject)=>{ resolve({ // the component options }) } )) - load component only when necessary
parameter: a component loader function
returns, a Promise<ComponentOptions>
inline component https://v3.vuejs.org/guide/component-dynamic-async.html#async-components
however this requires compiler at client side (bad)
use with single-file-component:
defineAsyncComponent itself does not add lazy loading - the promise resolution does
therefore there's no way to define a single-file-component as a "lazy component", it needs to be LOADED as a "lazy component"
defineAsyncComponent(() => import('./components/AsyncComponent.vue'))
Use with <suspense> : at moment suspense is still an experimental feature, ignore for now
in single-file-component, export default defineAsyncComponent
<template> is not compiled and put in the options
if provide in "template" prop, requires client side template compiler
in parent (user) component
static import: import large from "./LargeComponent.vue"
use defineAsyncComponent: components: { large: defineAsyncComponent<any>(()=>(new Promise((resolve)=>{resolve(large)}))) },
this works but there's no defered loading (async), must use dynamic import
Tip:
to observe the effect, use developer tool, observe network
Use with single-file-component module system:
method 1 (let user determine and dynamically load):
in component, export a normal component
in user (parent):
do NOT normal static import
register as:
components: { large: defineAsyncComponent(()=>import("./LargeComponent.vue")) }, // this will do delayed async import
v-show:
to element's "display" CSS property
<h1 v-show="jsExpressionEvalToBoolean">Hello!</h1>
v-if, v-else, v-else-if:
conditional rendering - false means not in DOM
higher rendering cost
<h1 v-if="jsExpressionEvalToBoolean">Hello!</h1>
<template v-if="ok"> // a block of sibling elements
NOT recommended to use with v-for, but if did used together, v-if evaluates first
v-for:
expects: Array | Object | number | string | Iterable
on observed arrays:
common mutation methods trigger view changes
for methods return new array (filter), replace
syntax:
RECOMMENDED TO ALWAYS PROVIDE KEY(A STRING OR NUMBER) <div v-for="item in items" :key="item.id"> // for reordering
<div v-for="item in items"> {{ item.text }} </div>
<div v-for="(item, index) in items"></div>
<div v-for="(value, key) in object"></div>
<div v-for="(value, name, index) in object"></div>
<span v-for="n in 10" :key="n">{{ n }} </span> // range, 1-10
<template v-for="item in items" :key="item.msg"> // render a block of sibling elements
<my-component v-for="(item, index) in items" :item="item" :index="index" :key="item.id"></my-component> // loop and pass into components
filtering / sorting:
use computed value when possible
(in nested) use a method
define events
emits: ['enlargeText'] // list events, note: name camel / kebab-case conversion
submit: ({ email, password }) => {// provides an validation function here}
emits: { addBook(payload: { bookName: string }) { // Typescript define the type
emit events
$emit('event') // emits a custom event
$emit('enlargeText', 0.1) // emit event object, in inline statement will be $event
handle events
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
methods: { onEnlargeText(enlargeAmount) { // if expression is a method, event value will be the first parameter
automatic camel / kebab-case transformation
v-on (@):
Expects: function, inline statement, object
Syntaxes:
<button @click="greetMethod">Greet</button> // greetMethod receives the event as first parameter
<button @click="say('hi')">Say hi</button> // statement
<button @click="warn('Form cannot be submitted yet.', $event)"> //statement, $event is the original event
<button @click="one($event), two($event)"> // multiple handlers
Using modifiers
<a @click.stop="doThis"></a>
multiple modifiers: @click.prevent.self // order matters
Argument: event
HTML events
Custom events on custom components
Modifiers (HTML):
.stop - call event.stopPropagation() - stop bubbling up the DOM
.prevent - call event.preventDefault() - prevent default UI behaviour if not explicitly handled (i.e. checkbox checked upon click, form submission)
.capture - add event listener in capture mode - (event, browser then (1) from <html> -> event source, look for capturing handlers (2) then event source -> <html> for bubbling handlers)
.self - only trigger handler if event was dispatched from this element.
.{keyAlias} - only trigger handler on certain keys. for list see: https://v3.vuejs.org/guide/events.html#key-modifiers
.once - trigger handler at most once. (also able to use on component)
.left - only trigger handler for left button mouse events.
.right - only trigger handler for right button mouse events.
.middle - only trigger handler for middle button mouse events.
.passive - attaches a DOM event with { passive: true }, indicate the handler will never call preventDefault(), use for improving performance, NOT TO MIX with .prevent
v-bind (shorthand :):
argument: attributeOrProperty (optional)
expects: any (with argument) | Object (without argument, bind more attributes)
modifiers: .camel - translate kebab-case attribute name to camelCase <svg :view-box.camel="viewBox"></svg>
special bindings:
class & style: see above Class and Style binding
v-model on HTML inputs:
automatic two-way binding
source of truth: instance data (ignores initial state of form elements)
Limit to: <input>, <select>, <textarea>, components
automatic use different property and liste on different events on different elements:
<text><textarea>: value | on input event
<input type="checkbox">: checked | on change event
<select>: value | on change event
Examples:
Text: <input v-model="message" placeholder="edit me" />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
BAD EXAMPLE: <textarea>{{ text }}</textarea>
<input type="checkbox" id="checkbox" v-model="checked" /><label for="checkbox">Check/uncheck me</label>
bind multiple checkboxes to an array <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" /><label for="jack">Jack</label>
<input type="radio" id="one" value="One" v-model="picked" />
Bind value:
by default:
radio: the "value" attribute <input type="radio" v-model="picked" value="a" />
checkbox: boolean <input type="checkbox" v-model="toggle" />
select: option "value" attribute <option value="abc">ABC</option>
v-bind:value able to bind value to object/expression rather than string: <input type="radio" v-model="pick" v-bind:value="a" />
Modifiers:
.lazy - listen to "change" event (value changed), not "input" event (gets user input)
.number - cast valid input string to numbers
.trim - trim input
customize model modifier:
provide "modelModifiers" into component props as modelModifiers: {default: () => ({})} // use a factory
in code check this.modelModifiers.modifierName // if there is arg modifier name will be arg+"Modifiers"
v-model on components
on components v-model actually do (syntax sugar):
<custom-input
:model-value="searchText" // provide modelValue property
@update:model-value="searchText = $event" // on update:model-value event update
></custom-input>
component needs to provide:
prop: "modelValue" passes in value
event: "update:modelValue" triggers update, payload with the new value
example:
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
In component can use a computed with getter / setter for model binding: getter returns modelValue prop, setter emits update event
v-model with argument change the name of prop and event
<my-component v-model:title="bookTitle"></my-component>
binds: propname "title", event name "update:title"
with this method can bind multiple data with different names - so a component can "export" multiple binding sites
Trace: code that make a result, source that (is read during running of the code, therefore) contributes to result
Detect: change of source
Re-run: code that derive the result
Effect (the code):
a wrapper around a function, initiates tracking before function is called
used on: rendering, computed properties, etc.
API:
watchEffect
Source and Detect of Change:
Object properties:
wrap in a Proxy (intercept action to an object with trap - a handler), track read and write, then use Reflect API to do the normal work
when accessed from a reactive proxy, child ALSO wrapped and converted to a proxy
Primitive:
wrap in a ref, access through myRef.value
it seems whenever access ref.value, update is carried out - asynchonously
to use a ref in template:
if the ref is directly attached to context (the component instance) it's automatically shallow unwraped (meaning no need to use .value)
if ref is nested in a NORMAL object, an array, a map, etc., then need to use .value
if ref is nested in a reactive object (a proxy), it's automatically unwrapped (no need .value)
"readonly" returns a readonly reactive object
note: not "deep readonly", just "shallow read only"
Typing "reactive":
const book = reactive<Book>({ title: 'Vue 3 Guide' })
It's a proxy, read / write is tracked
It's deep - children accessed through reactive object will also be converted to a reactive object
My feeling: two way creates more issues if dependency graph is too complex
takes a getter function (no input), executes it, tracks access to reactive objects, returns a readonly ref changes accordingly (optionally can make it two way, so these refs "linked")
const plusOne = computed(() => count.value + 1)
count is a ref, its tracted
plusOne is a readonly ref, when count is updated, plusOne is updated
If give both a getter and setter function, return ref that can be updated (and then update the source, make it twoway)
API: watchEffect( (onInvalidateRegister)=>{}, option)
arguments:
first (effect): a function,
accepts one argument of type InvalidateCbRegistrator (a function),
uses for register a "on invalidate" handler
the handler get called when effect is about to re-run or watcher is stopped
returns nothing (for side effect only)
options
returns:
a stop handle
how it works:
creates an "effect",
executes the effect function
tracks reference access during execution
automatically re-executes when reference changes - ONLY IN "NEXT TICK"
Lifecycle:
"When watchEffect is called during a component's setup() function or lifecycle hooks, the watcher is linked to the component's lifecycle and will be automatically stopped when the component is unmounted."
In other case, returns a stop handle
const stop = watchEffect(() => {})
stop() // later
On Invalidate:
Usage: the "effect" function can receive a parameter, a registration function
Can then use the registration function to register a "on invalidate" handler
watchEffect(onInvalidate => { do something asynchonously; onInvalidate(() => { invalidate handler }) })
the on-invalidate handler is called when:
effect is about to re-run
however watcher is stopped (i.e. the corresponding component is unmounted)
Flushing:
vue buffers invalidated effects (to avoid duplicated calls) and flushes them asynchronously
component update is also an effect in the buffer, and is flushed LAST (better not mute state during component update)
can invoke "watchEffect" and give second argument to pass option to change the flushing time ("pre" to "post" or "sync")
Debugging with onTrack and onTrigger (only work in development mode):
pass them in options (second parameter)
called when a ref is tracked / when triggered
API: watch (source, handler, option)
feature (comparing to watchEffect):
perform side effect lazy - only called when watched source has changed (??)
more specific about what state to trigger re-run (not everything traced, explicitly specify source)
get both previous and current value
source:
for reactive object's property:
be a getter function (no input parameter) that returns a value
use this for watching reactive object and array
be a ref
be an array of above
handler(oldValue, newValue)
for ref, oldValue and newValue
for reactive object and array, both newValue & oldValue points to the same current object
option
deep: weather to watch deep on reactive objects (default false)
Lifecycle, on invalidate, flushing, debugging: same as watchEffect
ref(value) : return a ref
toRef(reactiveObject, key): return a ref referring to a REACTIVE object's property
toRefs(reactiveObject): return a normal object, each property a ref to one of the REACTIVE object's properties
Typing refs:
in most case inferred from initial value
explicitly typed: const year = ref<string | number>('2020') // year's type: Ref<string | number>
referring a child component: const modal = ref<InstanceType<typeof MyModal>>()
if needed can cast to Ref<T>
Identity issue:
proxy (reactive object) is not === with original object, this also leads to problem with .includes() etc.
best never deals with original object only proxy
Reactive Object
only when access property THROUGH REACTIVE OBJECT retains reactivity
destruction syntax obtains the property VALUES of reactive object, not references
use toRefs / toRef to obtain REFERENCE from reactive object property
Recommended Configuration:
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
// this enables stricter inference for data properties on `this`
"strict": true,
"jsx": "preserve",
"moduleResolution": "node"
}
}
Use defineComponent to enable typing inference
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
// type inference enabled
})
</script>
Typing data: inference is normally enough, or use type assertion "as"
const Component = defineComponent({
data() {
return {
book: { // some book properties
} as Book
}
}
})
Typing global properties installed from plugin or somewhere:
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
// some augmented properties here
}
}
(make this declaration available to compiler)
(make the type declaration a module - put at least one import or export inside even it's just "export {}")
Typing computed, need an annotation
computed: {
// needs an annotation
greeting(): string {
return this.message + '!'
},
Typing props, cast to PropType<>, also make sure to use arrow function for default and validator
props: {
name: String,
id: [Number, String], // union
success: { type: String },
callback: {
type: Function as PropType<() => void>
},
book: {
type: Object as PropType<Book>,
required: true,
// Make sure to use arrow functions, or explicitly type "this" as void
default: () => ({
title: 'Arrow Function Expression'
}),
validator: (book: Book) => !!book.title
},
metadata: {
type: null // metadata is typed as any
}
}
Typing emits, annotate the payload validation function parameter
emits: {
addBook(payload: { bookName: string }) {
// perform runtime validation
return payload.bookName.length > 0
}
},
Typing refs, inference is enough for primitives, otherwise provide type
const year = ref(2020)
const year = ref<string | number>('2020') // year's type: Ref<string | number>
or cast to Ref<T>
Typing template instance and template ref
const modal = ref<InstanceType<typeof MyModal>>()
Typing reactive object:
const book = reactive<Book>({ title: 'Vue 3 Guide' }) // or
const book: Book = reactive({ title: 'Vue 3 Guide' }) // or
const book = reactive({ title: 'Vue 3 Guide' }) as Book
Typing computed:
will be inferred from return type
Typing HTML event handlers
const handleChange = (evt: Event) => {
console.log((evt.target as HTMLInputElement).value)
}
Options:
use a shared reactive object
Vuex
ignored for now
Component Testing:
https://testing-library.com/docs/vue-testing-library/intro/ Vue testing library (recommended)
https://next.vue-test-utils.vuejs.org/guide/ Vue testing utility (official, low level, for advanced user)
https://lmiller1990.github.io/vue-testing-handbook/v3/#what-is-this-guide handbook for using the testing utility
E2E Testing:
Application monitoring and error reporting tools (e.g., Sentry, LogRocket, etc.):
one component can deal with several LOGICAL responsibility - data acquiring, filtering, sorting, etc....
"normal" component group by functional parts: data, method, computed...
when component gets too large it's desirable to organise component by LOGICAL concerns
Use "setup" in options: setup(props, context)
a function receive props & context
props:
a Proxy object of the props (it is reactive, however it's properties are not, ES6 destructuring is NOT reactive)
use "toRefs" and "toRef" for reactive property
optional prop may be missing, so use "toRef"
no need to typing it, type is derived
context:
normal JS object, exposes
attrs: attributes, non-reactive, type Data (object of string key to unknown)
slots: non-reactive, object, name (string) map to Slot object
emit: the $emit method later in the instance
executed BEFORE component is created AFTER props resolved
"this" not referring to the component instance
no "data", "computed" and "methods"
return {//something}
anything returned here will be available for the rest of the component (instance) and the template
refs - automatically shallow unwrapped, no need to use ".value" in template
To divide concern, separate to several "composite functions" that return objects that eventually merge. Call these "composite functions" from the main "setup"
Props are passed into setup as a Proxy of the props object
For typing, the parameter passed into "setup" is already typed
Nothing needs to do with props - and perhaps best not to touch them. They are reactive anyway.
if just return plain data or object, will not be reactive
use reactive reference: import { ref } from 'vue'; ref([])
then work with ref.value
provide functions, will become methods of the composite instance
Use onNameOfLifecycle method
e.g. to register "mounted" hook, call "onMounted" (import from vue) function ("on"+hook) and give the callback
WARNING:
if lifecycle hook provided both from props (common way) and "onNameOfLifecycle" method, both will be called
lifecycle callback registered this (composition) way has no "this" - will be "undefined"
https://github.com/vuejs/vue-next/issues/4127 (reported as a bug)
lifecycle callback registered from props (the normal way) has "this" as the component instance
call "watch" function (import from vue), watch(theReactiveRefObject_Or_GetterFunction, (newValue, oldValue) => {//the callback})
to watch a prop:
const {msgProp} = toRefs(props);
toRefs "converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object"
const msgProp = toRef(props, "msgProp");
turn a single key into a ref
WARNING:
first parameter MUST BE a VALID WATCH SOURCE:
getter/effect function, ref, reactive object, or array of these types
in returned object use "computed" function imported from "vue" to convert
countOneMore: computed(()=>{ return count.value+1 })
import { provide } from 'vue'
provide('location', 'North Pole')
recommended to make provided value readonly
import { provide, reactive, readonly, ref } from 'vue'
provide('location', readonly(location))
import { inject } from 'vue'
const userLocation = inject('location', 'The Universe as Default Value')
See document
How to use:
provide mixin:
provide an object containing any component options
using (mixing in):
in component options, give the mixins as an array: mixins: [myMixin]
all options will be merged
Merging conflict resolution:
data - component's own data takes priority
hook functions - merge into an array, all will be called, component'w own called LAST
methods, components, directives - component's own takes priority
app.mixin()
affect every component instance created afterwards
see document
conflict prone, not encapsulated
things appears from nowhere (this), not knowing where they come from
cannot pass parameters to mixins
Main Usage:
directly manipulate HTML element
when doing that, if component has multiple fragment nodes, directive will be ignored and warning will be thrown
Registration:
scope
global - available to every component app.directive('focus', {...}
locally - to one component, provide in options "directives"
definition
name - in template will become "v-name"
definition object: provide hook functions (not the same as lifecycle hook functions, different API)
created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted
API: https://v3.vuejs.org/api/application-api.html#directive
hooks are passed with (el, binding, vnode, prevNode)
el - the element the directive is bound to, the root node
binding:
instance - the component instance
value - the value (resolved as JS expression), can take any JS value and object
oldValue - the previous value (only in beforeUpdate & updated)
arg: the argument, e.g. "v-my-directive:foo" then "foo"
modifiers: an object containing modifiers, e.g. v-my-directive.foo.bar gets { foo: true, bar: true }
dir: the directive definition object when defining
function shorthand: provide just the callback, (el, binding) => {} // same behaviour on mounted & updated
Comparing with Slot:
slot: child provides several slots within component template, parent provides content to fill into
teleport: child provides content to some parent element
In child component,
<teleport to="body">
<teleport to="#modals">
Somewhere, <div id="modals">
multiple teleport can target one element
skipped. Use JS to generate virtual DOM
Usage Scenario:
To use Application API to add some global assets or mix-ins
Plugin is:
an object with an "install" method, or
a function
Use:
app.use(i18nPlugin, i18nStrings)
Effect:
the "install" method or function get called
receive (app, userOptions)
key:
use in loop, must be UNIQUE
will re-order elements based on order change of keys
<li v-for="item in items" :key="item.id">...</li>
ref:
register an HTML element or child component under $refs of component instance
are created as result of render function: ONLY AVAILABLE AFTER RENDERING
non-reactive
<p ref="p">hello</p> // simple bind to $refs.p
<child-component :ref="(el) => child = el"></child-component>
bind "ref" to an expression
evaluates to a string - dynamic ref name
to a callback function: pass the element or component, and the function can bind or do whatever
is:
use with <component> for dynamic component
for dynamic component (i.e. tab) <component :is="currentView"></component> // component change according to "currentView"
use with HTML element
HTML customized build-in element: https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example
Vue component (prefix "vue:"): <tr is="vue:my-row-component"></tr>
use this because <table> limits what can appear inside, has to use <tr> with "is"
https://github.com/bingtimren/MyVsCodeSnippets/blob/master/html.json
https://github.com/bingtimren/MyVsCodeSnippets/blob/master/typescript.json
v-text (same as {{ }}) : content will be escaped
v-html: raw HTML !!! (SECURITY)
v-pre: no compiling, leave {{ as is
v-cloak: hide something before compiling is done
v-once: render once
v-show: bind to "display" attr
v-if, v-else, v-else-if
v-for: looping
v-on (see Event Handling)
See "Element Attribute Binding (v-bind) & Form Binding (two way, v-model)"
v-bind (shorthand :):
v-model: two way input binding
v-slot
Controlling Updates:
$forceUpdate instance method force update
Optimise:
use "v-once" for caching static components, but no need to over-use this