A progressive, incrementally-adoptable JavaScript framework for building UI on the web.
$ yarn global add @vue/cli # install cli
Or,
$ npm install -g @vue/cli # install cli
$ vue -V # see cli version, e.g. 3.x
$ npm install -g vue # install vue globally
# Create and run an app
$ vue create <project-name> # create a project
$ npm run dev # run the app, url: localhost:8080
$ npm run build # compressed/minified files
- el: Connect to DOM
- data: Store Data to be used
- methods: Methods of this Vue Instance
- computed: Dependent Properties
- watch: Execute code upon data changes
- Components extends the Vue instance
<div class="column is-one-third" v-for="(faq, index) of faqs" :key='index'>
<div class="card">
<div class="card-content">
<p class="title">{{ faq.title }}</p>
<p class="answer">{{ faq.body }}</p>
</div>
</div>
</div>
<!-- v-for and v-text directives -->
<li v-for="faq in faqs" v-text="name"></li>
- We can't use interpolation syntax
{{ }}
inside any HTML attributes, so, we need to bind by using Vue's Directives like: v-bind:title="title". See below example:
<template>
<style>
.color-red { color: red; }
</style>
<button v-bind:title="title">Hover over me</button>
<button :class="{ 'is-loading': isLoading }" @click="toggleClass">Click me</button>
<button :disabled="isDisable" @click="toggleDisable">Disable me</button>
<h1 :class="className">Hello World</h1>
</template>
<script>
data: {
className: 'color-red'
title: "This title is being binded by JS"
}
data: {
isLoading: false,
isDisable: false
}
methods: {
toggleClass() {
this.isLoading = true;
}
toggleDisable() {
this.isDisable = true;
}
}
</script>
- We can use
<component>
to render dynamic components.
<template>
<div>
<button @click="selectedComp = 'Comp1'">Component 1</button>
<button @click="selectedComp = 'Comp2'">Component 2</button>
<button @click="selectedComp = 'Comp3'">Component 3</button>
<component :is="selectedComp">
<p>Default Content</p>
</component>
</div>
</template>
<script>
import Comp1 from './Comp1.vue'
import Comp2 from './Comp2.vue'
import Comp3 from './Comp3.vue'
export default {
data: {
return {
selectedComp: 'Comp1',
}
}
components: {
'comp1': Comp1,
'comp2': Comp2,
'comp3': Comp3,
}
}
</script>
- we can use
<keep-alive>
to prevent destroying a component. - Useful two lifecycle hooks:
Activated()
&Deactivated()
.
<template>
<div>
<button @click="selectedComp = 'Comp1'">Component 1</button>
<button @click="selectedComp = 'Comp2'">Component 2</button>
<button @click="selectedComp = 'Comp3'">Component 3</button>
<keep-alive>
<component :is="selectedComp">
<p>Default Content</p>
</component>
</keep-alive>
</div>
</template>
<script>
import Comp1 from './Comp1.vue'
import Comp2 from './Comp2.vue'
import Comp3 from './Comp3.vue'
export default {
data: {
return {
selectedComp: 'Comp1',
}
}
components: {
'comp1': Comp1,
'comp2': Comp2,
'comp3': Comp3,
}
}
</script>
We can use v-if
to lazily load off-screen
components.
<template>
<div id="app">
<modal v-if="isModalVisible"/>
</div>
<button @click="isModalVisible = true">Open Modal</button>
</template>
<script>
export default {
data() {
return {
// Modal will not be downloaded until this value will equal 'true'
isModalVisible: false
}
},
components: {
'Modal': () => import('./components/Modal')
}
}
</script>
<template>
<div>
<h1 v-once>{{ title }}</h1>
<!-- <h1>Hello World</h1> -->
<p>{{ sayHello() }} - <a v-bind:href="link">Google</a></p>
<!-- Hello changed - <a href="http://google.com">Google</a></p> -->
</div>
</template>
<script>
data: {
title: 'Hello World!',
link: 'http://google.com'
},
methods: {
sayHello: function() {
this.title = 'Hello changed',
return this.title;
}
}
</script>
Normally vue render the variable as text (it's safe for security). If we need to render as HTML then use v-html
.
N.B: It can cause bad cors attack. Use this if the source is trusty/clean.
<template>
<div>
<p v-html="finishedLink"></p>
</div>
</template>
<script>
data: {
finishedLink: '<a href="http://google.com">Google</a>'
}
</script>
<template>
<div>
<p v-on:mousemove="updateCoordinates">Coordinates: {{ x }} / {{ y }}</p>
</div>
</template>
<script>
data: {
x: 0,
y: 0
}
methods: {
updateCoordinates(e) {
this.x = e.clientX;
this.y = e.clientY;
}
}
</script>
<template>
<div>
<button v-on:click="increase(2, $event)">Click me</button>
<p>Counter: {{ counter }}</p>
</div>
</template>
<script>
data: {
counter: 0
}
methods: {
increase(step, e) {
this.counter += step;
e.preventDefault()
}
}
</script>
<template>
<div>
<p v-on:mousemove="updateCoordinates">Coordinates: {{ x }} / {{ y }}
<span v-on:mousemove.stop="">DEAD SPOT</span>
</p>
</div>
</template>
<script>
data: {
x: 0,
y: 0
}
methods: {
updateCoordinates(e) {
this.x = e.clientX;
this.y = e.clientY;
}
}
</script>
Alternate:
<template>
<div>
<p v-on:mousemove="updateCoordinates">Coordinates: {{ x }} / {{ y }}
<span v-on:mousemove="doStopPropagation">DEAD SPOT</span>
</p>
</div>
</template>
<script>
data: {
x: 0,
y: 0
}
methods: {
updateCoordinates(e) {
this.x = e.clientX;
this.y = e.clientY;
},
doStopPropagation(e) {
e.stopPropagation();
}
}
</script>
<template>
<div>
<input type="text" v-on:keyup.enter.space="alertMe">
<!-- event will be triggered for any enter/space in input field -->
</div>
</template>
<script>
data: {
x: 0,
y: 0
}
methods: {
alertMe() {
alert('Alert!');
}
}
</script>
<template>
<div>
<button v-on:click="counter++">Click me</button>
<p>Counter: {{ counter > 10 ? 'Greater that 10' : 'Smaller or equal to 10' }}</p>
</div>
</template>
<script>
data: {
counter: 0
}
methods: {
increase(step, e) {
this.counter += step;
e.preventDefault()
}
}
</script>
<template>
<div>
<input type="text" v-model="name">
<p>{{ name }}</p>
</div>
</template>
<script>
data: {
name: 'Jhon'
}
</script>
Vue.js has introduced a .Sync
modifier to v-bind
which update the Parent data from Child easily (from >= 2.3.0). Just emit the update
event.
<!-- parent -->
<child-component :inputData.sync="parentData"></child-component>
<!-- child -->
<input>type="text" v-model="inputData" @keyup="$emit('update:inputData', inputData);"</input>
<template>
<div id="app">
<button v-on:click="counter++">Increase</button>
<button v-on:click="counter--">Decrease</button>
<button v-on:click="secondCounter++">Increase Second</button>
<p>Counter: {{ counter }} | {{ secondCounter }}</p>
<p>Counter: {{ result() }} | {{ output }}</p>
</div>
</template>
<script>
data: {
counter: 0,
secondCounter: 0
},
// caching the result, don't need to calculated always
computed: {
output: function() {
console.log('Computed');
return this.counter > 5 ? 'Greater than 5' : 'Smaller than 5'
}
},
// always trigger cause Vue does not know what is the codes in methods actually.
methods: {
result: function() {
console.log('methods');
return this.counter > 5 ? 'Greater 5' : 'Smaller than 5'
}
}
</script>
Watched props can often be confused with computed
properties, because they operate in a similar way.
Watch is for side effects. A
side effect
is anything that happens outside of our component, or anythingasynchronous
.
Common examples are:
- Fetching data
- Manipulating the DOM
- Using a browser API, such as local storage or audio playback
None of these things affect our component directly, so they are considered as side-effects
.
However, there are cases where we might want to use a
watcher
to update something in ourdata
.
- An Alternative to computed properties
Watching
for
// template same as before
<script>
data: {
counter: 0,
secondCounter: 0
},
// caching the result, don't need to calculated always
computed: {
output: function() {
console.log('Computed');
return this.counter > 5 ? 'Greater than 5' : 'Smaller than 5'
}
},
watch: {
// counter matches with data 'counter', watching when the value of 'counter' changed, if change then this counter
// is triggered with the updated value.
counter: function(value) => {
var vm = this;
setTimeout(function(){
vm.counter = 0;
}, 2000)
}
}
</script>
We can watch arrays or objects nested properties by setting deep: true
in our watcher and rearranging the handler function:
watch: {
myObject: {
// This will let Vue know to look inside the object/array
deep: true,
// We have to move our method to a handler field
handler(newValue, oldValue)
console.log('myObject has changed!');
},
// Will fire as soon as the component is created
immediate: true,
}
<template>
<div id="app">
<div class="demo" @click="attachRed = !attachRed" :class="divClasses"></div>
<div class="demo" @click="attachRed = !attachRed" :class="{ red: attachRed }"></div>
<div class="demo" :class="[color, { red: attachRed }]"></div>
</div>
<hr>
<input type="text" v-model="color">
</template>
<style>
.red {
background-color: red;
}
.green {
background-color: green;
}
</style>
<script>
el: '#app'
data: {
attachRed: false,
color: 'green'
},
computed: {
divClasses() {
return {
red: this.attachRed,
blue: !this.attachRed
}
}
}
</script>
<template>
<div id="app">
<div class="demo" :style="{ backgroundColor: color }"></div> <!-- Or, :style="{ background-color: color }" -->
<div class="demo" :style="myStyle"></div>
</div>
<hr>
<input type="text" v-model="color">
<input type="text" v-model="width">
</template>
<script>
data: {
color: 'gray';
width: 100;
},
computed: {
myStyle: function() {
return {
backgroundColor: this.color,
width: this.width + 'px'
}
}
}
</script>
<template>
<div id="app">
<div class="demo" :style="{ backgroundColor: color }"></div> <!-- Or, :style="{ background-color: color }" -->
<div class="demo" :style="myStyle"></div>
<div class="demo" :style="[ myStyle, { height: width + 'px' }]"></div>
</div>
<hr>
<input type="text" v-model="color">
<input type="text" v-model="width">
</template>
<script>
data: {
color: 'gray';
width: 100;
},
computed: {
myStyle: function() {
return {
backgroundColor: this.color,
width: this.width + 'px'
}
}
}
</script>
<template>
<div id="app">
<p v-if="show">if statement <span> Nested element </span></p>
<p v-else>else statement</p>
<button @click="show = !show">Switch </button>
</div>
</template>
<script>
data: {
show: false
}
</script>
<template>
<div id="app">
<p v-if="show">if statement <span> Nested element </span></p>
<p v-else>else statement</p>
<template v-if="show">
<h1>Heading</h1>
<p>Inside a template </h1>
</template>
<button @click="show = !show">Switch </button>
</div>
</template>
<script>
data: {
show: false
}
</script>
<template>
<div id="app">
<p v-show="show">if statement <span> Nested element </span></p>
<button @click="show = !show">Switch </button>
</div>
</template>
<script>
data: {
show: false
}
</script>
<div id="app">
<ul>
<li v-for="(ingredient, ind) in ingredients">{{ ingredient }} - {{ ind }}</li>
</ul>
</div>
<script>
data: {
ingredients: ['meat', 'fruit', 'cookies'],
positions: [
{name: 'Max', age: '27', color: 'red'},
{name: 'Anna', age: 'unknown', color: 'blue'}
]
}
</script>
<div id="app">
<ul>
<!-- <template> </template> does not rendered in HTML only inner elements rendered -->
<template v-for="(ingredient, index) in ingredients">
<h1>{{ ingredient }}</h1>
<h1>{{ index }}</h1>
</template>
</ul>
</div>
<script>
data: {
ingredients: ['meat', 'fruit', 'cookies'],
}
</script>
<div id="app">
<ul>
<li v-for="person in persons">
<div f-for="(value, key, index) in person">({{ index }}) {{key}}: {{ value }} </div>
</li>
</ul>
</div>
<script>
data: {
positions: [
{name: 'Max', age: '27', color: 'red'},
{name: 'Anna', age: 'unknown', color: 'blue'}
]
}
</script>
<div id="app">
<ul>
<li v-for="person in persons">
<div f-for="n in 10">{{ n }} </div>
</li>
</ul>
</div>
<div id="app">
<ul>
<li v-for="person in persons">
<div f-for="(value, key, index) in person" :key="ingredient">({{ index }}) {{key}}: {{ value }} </div>
<button @click="ingredients.push('spices')">Add New Ingredient</button>
</li>
</ul>
</div>
<script>
data: {
ingredients: ['meat', 'fruit', 'cookies'],
}
</script>
- If we change the by using
$refs
it changes in the DOM directly not in the Vue instance (template). So, if vue rerender the previous value will be reflected!
<template>
<button @click="changeInnerText" ref="myButton">Set Ref</button>
</template>
methods: {
changeInnerText() {
this.$refs.myButton.innerText = 'Text';
}
}
<div id='app1'></div>
var vm1 = new Vue({
template: '<h1>Hello VM1</h1>'
})
vm1.mount('app1');
// Alternate:
vm1.mount();
document.getElementById('app1').appendChild(vm1.$el);
<template>
<button @click="title='Changed'">Update Title</button>
<button @click="destroy">Destroy</button>
</template>
<script>
new Vue({
el: '#app',
data: {
title: 'The VueJS Instance'
},
beforeCreate() {
console.log('beforeCreate()');
},
created() {
console.log('created()');
},
beforeMount() {
console.log('beforeMount()');
},
mounted() {
console.log('mounted()');
},
beforeUpdate() {
console.log('beforeUpdate()');
},
updated() {
console.log('updated()');
},
beforeDestroy() {
console.log('beforeDestroy()');
},
destroyed() {
console.log('destroyed()');
},
methods() {
destroy() {
this.$destroy();
}
}
})
</script>
we can add lifecycle dynamically
import { onMounted, onUnmounted } from 'vue';
export default {
beforeCreate() {
onMounted(() => {
console.log('mounted');
})
onUnmounted(() => {
console.log('unmounted');
})
}
}
// parent.vue
<template>
<app-child>
<h1 slot="headerH1">Pass this template from parent using Slot</h1>
<p slot="paragraph">{{ name }}</p>
<p>Default slot</p>
<!-- we can use interpolation also -->
</app-child>
</template>
<style>
import ./child.vue
data: () {
return {
name: 'Hello World'
}
}
</style>
// child.vue
<template>
<div class="title">
<slot name="headerH1"></slot>
</div>
<div class="paragraph">
<slot name="headerH1"></slot>
</div>
<!-- if the slot has no name it is default slot -->
<slot></slot>
</template>
<script>
</script>
<style>
// the child's style will be applied when we use slot
h1 {
color: red;
}
</style>
Hooks workflow:
bind(el, binding, vnode)
: Once directive is Attachedinserted(el, binding, vnode)
: Inserted in Parent Nodeupdate(el, binding, vnode, oldVnode)
: Once Component is Updated (without Children)componentUpdated(el, binding, vnode, oldVnode)
: Once Component is Updated (with Children)unbind(el, binding, vnode)
: Once directive is Removed
-
The default mode for vue-router is hash mode - it uses the URL hash to simulate a full URL so that the page won't be reloaded when the URL changes.
-
To get rid of the hash, we can use the router's
history mode
, which leverages thehistory.pushState
API to achieve URL navigation without a page reload:
const router = new VueRouter({
mode: 'history',
routes: [...]
})
N.B. Since our app is a single page client side app, without a proper server configuration, the user will get a 404 not found error. So, if no matched then, server should serve index.html
always!
<input v-model="something">
v-model
is the syntactic sugar for:
<input
v-bind:value="something"
:value="something" (shorthand syntax)
v-on:input="something=$event.target.value"
@input="something=$event.target.value" (shorthand)
>
- MUTATIONS:
- methods that changes the state with a minimum amount of logic.
- must be synchronous
- ACTIONS:
- set value in state (committing one or more mutations)
- set and get value using a rest api
- can be asynchronous
- GETTERS:
- get values from state
- transforming state values if required (similar as computed property of a Component)
// service worker caching
new SWPrecacheWebpackPlugin({
cacheId: 'my-vue-app',
filename: 'service-worker.js',
staticFileGlobs: ['dist/**/*.{js,html,css}'],
runtimeCaching: [
{
urlPattern: /^http:\/\/res\.cloudinary\.com\//,
handler: 'cacheFirst'
}
],
minify: true,
stripPrefix: 'dist/'
});
Vue elements will reuse elements that have the same tag name when using v-if
on them.
<template>
<div id="app">
<div v-if="isUsername">
<Label>Username</Label>
<input />
</div>
<div v-else>
<Label>Email</Label>
<input />
</div>
<button @click="toggle"> Toggle </button>
</div>
</template>
<script>
new Vue({
el: "#app",
data: {
isUsername: true
},
methods: {
toggle() {
this.isUsername = !this.isUsername
}
}
})
</script>
Here, <input />
element will not be replaced.
To solve this we just need to add a key to each of them so Vue knows they are distinct element:
div id="app">
<div v-if="isUsername" key="username"> <!-- key="username" -->
<Label>Username</Label>
<input />
</div>
<div v-else key="email"> <!-- key="email" -->
<Label>Email</Label>
<input />
</div>
<button @click="toggle"> Toggle </button>
</div>
Vue Observable introduced in Vue v2.6.0
could be a lightweight alternative to statement management in smaller applications. So, let's create an object users
:
import Vue from "vue";
const state = Vue.observable({
users: {
c6676a9aca4c270086ef31a35cc80446: {
name: "Ibrahim Ezzy",
twitter: "3zzy",
bio: "Software Imagineer. Front-end, UI & Design."
},
"4d50982553c3286d65182075c178245f": {
name: "Tim Apple",
twitter: "tim_cook",
bio: "Chief Executive Officer of Apple"
}
}
});
A generic setState
method to update any object within the state:
const mutations = {
setState({ object, objectPath, value, upsert = false } = {}) {
console.log("setState args: ", { object, objectPath, value, upsert });
if (state[object] === undefined || value === undefined)
console.error("setState: Invalid Object or Value");
if (objectPath === undefined) state[object] = value;
if (objectPath && Array.isArray(objectPath) && objectPath.length) {
let navigate = [object, ...objectPath.slice(0, -1)],
valueObj = navigate.reduce((obj, prop) => {
if (typeof obj[prop] !== "object") {
if (upsert) {
obj[prop] = {};
} else {
console.error(`setState: property '${prop}' doesn't exist`);
}
}
return obj[prop];
}, state);
Vue.set(valueObj, objectPath[objectPath.length - 1], value);
}
}
// other specific mutations ...
};
And a generic getState
method to get any object from the state:
const getters = {
getState({ object, objectPath } = {}) {
if (state[object] === undefined) console.error("getState: Invalid Object.");
if (objectPath === undefined) return state[object];
if (objectPath && Array.isArray(objectPath) && objectPath.length) {
let navigate = [object, ...objectPath.slice(0, -1)],
valueObj = navigate.reduce((obj, prop) => {
if (obj[prop] === undefined) {
console.error(`getState: property '${prop}' doesn't exist`);
}
return obj[prop];
}, state),
value = valueObj[objectPath[objectPath.length - 1]];
if (value === undefined) console.error(`getState: Invalid object path`);
return value;
}
}
// other specific getters ...
};
Now, we can access users
like so:
data() {
return {
users: getters.getState({
object: "users"
})
};
}
and update (or, create) using setState
:
methods: {
updateName(e, id) {
console.log(e.target.innerText, id);
mutations.setState({
object: "users",
objectPath: [id, "name"],
value: e.target.innerText
});
}
}
Obviously these functions only work with Objects
but we get the idea.
-
Global guards (on the instance)
- These are called
each time the URL changes
- Guards:
beforeEach
,beforeResolve
,afterEach
- These are called
-
Route guards (on router definition)
- These are
only called when the associated ROUTE is matched
- Guards:
beforeEnter
- These are
-
Route Component guards
- These are
only called when a ROUTE COMPONENT is used/unused
- Guards:
beforeRouterEnter
,beforeRouterUpdate
,beforeRouterLeave
- These are
NOTE: All guards except afterEach
are asynchronous
. They are called in sequence, therefore, you need to explicitly call the next()
methods to tell the router that we are done and that he can continue the sequence. This is also called middleware pattern
.
- When to use each of them??
Assuming, we are navigating from /
to /contact
:
beforeRouteLeave --
called on the/
route componentbeforeEach --
calledglobally
when a new navigation starsbeforeEnter --
called when/contact
route matchesbeforeRouteEnter --
called when/contact
route component matchesbeforeResolve --
calledglobally
when route component guards are doneafterEach --
calledglobally
when everything is resolved
Filters are useable in two places -
- Mustache interpolations
v-bind
expressions
Filters should be appended to the end of the JavaScript expression, denoted by the pipe
symbol.
// in mustaches
{{ message | capitalize }}
// in v-bind
<div v-bind:itemId="itemId | formatId"></div>
Assign a filter method in Component's options -
filters: {
capitalize(value) {
if (!value) return ''
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
}
}
Assign a filter using Vue.filter()
before initialize Vue instance
:
Vue.filter('capitalize', (value) {
if(!value) return "";
value = value.toString();
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
});
new Vue({
...
})
Filter can be chained:
{{ message | filterA | filterB }}
If we pass one argument then it will be passed as second
parameters, first
parameter will be message
.
{{ message || filterA("arg1", "arg2")) }}
Here filterA
is defined as a function taking three
arguments. The value of message
will be passed into the first argument.
{{ ... }}
is calledInterpolation
orString Interpolation
v-on:click="method" -> @click="method"
v-bind:title -> :title
Ref: https://github.com/sajibcse68/compare-vue
- Template
(Compiled into)
Render Function --- (returns) New Virtual DOM(returns)
Virtual DOM --- (diffed against Old Virtual DOM) DOM Updates(generates)
Actual DOM --- Actual DOM
(Essentially) A lightweight JavaScript data format to represent what the actual DOM should look like at a given point in time
.
- Actual DOM
- document.createElement('div')
- "[object HTMLDivElement]" --> Browser Native Object (expensive)
- Virtual DOM
- vm.$createElement('div')
- { tag: 'div', data: { attrs" {}, ... }, children: [] } --> Plain JavaScript Object (cheap)
Another benefits: Decouples rendering login from the actual DOM - enables rendering capabilities in non-browser environments, e.g. server-side and native mobile rendering.
If we need to compile templates on the client(e.g. passing a string to the template
option, or mounting to an element using its in-DOM HTML as the template), we need the Compiler
to compile the template.
// this requires the compiler
new Vue({
template: '<div>{{ hi }}</div>
})
// this does not
new Vue({
render(h) {
return h('div', thisl.hi)
}
})
-
when using
vue-loader
orvueify
, templates inside*.vue
fiels are pre-compiled into JavaScript at build time. -
Since the runtime-only builds are roughly
30%
lighter-weight than their full-build counterparts, we should use it whenever we can.
Need this configure as alias:
// webpack
moudule.exports = {
// ...
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
}
}
}
// babel.config.js
chainWebpack: config => {
config.resolve.alias
.set('vue$', 'vue/dist/vue')
},
Say, we need to generate the Breadcrumb
manually using <router-link>
(if we use anchor <a>
element directlly then HTML5/Vue History mode does not work). So, we need to Compile it manually inside JS.
<template>
<component :is="breadcrumb"></component>
</template>
import Vue from "vue";
export default {
data() {
return {
breadcrumb: null
}
},
computed: {
...mapGetters(['beadcrumb']) // coming from vuux
/**
* <div><li><router-link to="/dashboard"><i class="fa fa-server"></i><span>dashboard</span></router-link>
* </li><li><router-link to="/dashboard/product"><span>product</span></router-link></li></div>
**/
},
methods: {
renderBreadcrumb() {
this.breadcrumb = Vue.compile(this.breadcrumb); // compile manually & unpdate 'breadcrumb' data
}
},
mounted() {
this.renderBreadcrumb();
},
watch: {
// it will trigger when 'breadcrumb' changes
breadcrumb() {
this.renderBreadcrumb();
}
}
}
-
When we pass a plain JavaScript object to a Vue instance as its data option, Vue.js will walk through all of its properties and convert them to getter/setters using Object.defineProperty
-
Due to the limitation of ES5, Vue.js cannot detect property addition or deletion
-
Since Vue.js performs the getter/setter conversion process during instance initialization, a property
must be present in the data object
in order for Vue.js to convert it and make itreactive
var data = { a: 1 } var vm = new Vue({ data: data }) // `vm.a` and `data.a` are now reactive vm.b = 2 // `vm.b` is NOT reactive data.b = 2 // `data.b` is NOT reactive
-
Add a property and make it
reactive
after an instance has been createdvm.$set('b', 2) // `vm.b` and `data.b` are now reactive Vue.set(data, 'c', 3) // `vm.c` and `data.c` are now reactive1
-
New properties added to the object
will not trigger
changes so, create afresh object
with properties from both the original object and the mixin object// instead of `Object.assign(this.someObject, { a: 1, b: 2 })` this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Reactivity Workflow:
-
Vue.js computed properties are not simple getters. Each computed property keeps track of its own reactive dependencies
-
When a computed property is evaluated, Vue.js updates its
dependency list
and caches the result value. The cached value is onlyinvalidated
when one of the tracked dependencies have changed -
Because of computed property caching, the getter function is not always called when you access a computed property
var vm = new Vue({ data: { msg: 'hi' }, computed: { example: function () { return Date.now() + this.msg } } })
The computed property example
has only one dependency: vm.msg
. Date.now()
is not a reactive dependency, because it has nothing to do with Vue’s data observation system. Therefore, when you programmatically access vm.example
, you will find the timestamp to remain the same unless vm.msg triggers a re-evaluation
.
In some use cases we may want to preserve the simple getter-like behavior, where every time we access vm.example
it simply calls the getter
again. We can do that by turning off caching
for a specific computed property.
computed: {
example: {
cache: false,
get: function () {
return Date.now() + this.msg
}
}
}
N.B. However, note this only affects programmatic access inside JavaScript; data-bindings are still dependency-driven. When we bind to a computed property in the template as {{example}}
, the DOM will only be updated when a reactive dependency has changed.
Need to Know:
$on()
- allows to declare a listener on Vue instance with which to listen to events$emit()
- allows to trigger events on the same instance (self)
Using Refs:
import ChildForm from './components/ChildForm'
new Vue({
el: '#app',
data: {
item: {}
},
template: `
<div>
<ChildForm :item="item" ref="form" />
<button type="submit" @click.prevent="submit">Post</button>
</div>
`,
methods: {
submit() {
this.$refs.form.submit()
}
},
components: { ChildForm },
})
Using Event Bus:
import ChildForm from './components/ChildForm'
new Vue({
el: '#app',
data: {
item: {},
bus: new Vue(),
},
template: `
<div>
<ChildForm :item="item" :bus="bus" ref="form" />
<button type="submit" @click.prevent="submit">Post</button>
</div>
`,
methods: {
submit() {
this.bus.$emit('submit')
}
},
components: { ChildForm },
})
// ChildForm.vue
<template>
...
</template>
<script>
export default {
name: 'NowForm',
props: ['item', 'bus'],
methods: {
submit() {
...
}
},
mounted() {
this.bus.$on('submit', this.submit)
},
}
</script>