<div :class="classes"></div>
//...
computed: {
classes() {
let classes = ''
if(this.isActive) {
classes += 'active '
}
if(this.hasError) {
classes += 'text-error'
}
return classes
}
}
<div :class="{ active: isActive }"></div>
CSS Class
Expression
<div class="active"></div>
<div :class="{ active: isActive, 'text-red': hasError }"></div>
data() {
return {
active: true,
hasError: true
}
}
<div class="active text-red"></div>
<div class="classObject"></div>
data() {
return {
active: true,
hasError: false,
error: {}
}
},
computed: {
classObject() {
return {
active: this.isActive && !this.hasError,
'text-danger': this.hasError && this.error.type === 'fatal'
}
}
}
<div :class="['active', 'text-danger']"></div>
<div :class="[{ active: true }, { 'text-danger': false }]"></div>
<div :class="[isActive ? activeClass : '', errorClass]"></div>
Applied when isActive is truthy
Array and Object Syntax
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="styleObject"></div>
computed: {
styleObject: {
color: this.font.color,
fontSize: this.font.size + 'px'
}
}
E.g. to computed or data
⏰ 25 mins
⏰ 30 mins
Vue.createApp({
data() {},
computed: {},
methods: {}
}).mount('#app')
===
Vue.createApp({
data() {},
computed: {},
methods: {}
}).mount('#app')
Like little Vue application instances with a name
// CounterButton.vue
<script>
export default {
data() {},
computed: {},
methods: {}
}
</script>
Like little Vue application instances with a name
Single File Component
(SFC)
// CounterButton.vue
<script>
export default {
// the logic for your component
}
</script>
<template>
<!-- The HTML for your component -->
</template>
<style>
/* The styles for your component */
</style>
A special Vue file format optimized for writing components
will require additional setup (more on this in a few)
// CounterButton.vue
<script>
export default {
data() {
return {
count: 0,
};
},
};
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
CounterButton.vue Example
// CounterButton.vue
<script>
export default {
data() {
return {
count: 0,
};
},
};
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
CounterButton.vue Example
// App.vue
<script>
import CounterButton from "@/CounterButton.vue"
</script>
<template>
<CounterButton></CounterButton>
</template>
CounterButton.vue Example
// App.vue
<script>
import CounterButton from "@/CounterButton.vue"
export default{
components: { CounterButton }
}
</script>
<template>
<CounterButton></CounterButton>
<CounterButton></CounterButton>
<CounterButton></CounterButton>
<CounterButton></CounterButton>
</template>
It's possible to define components without Single File Components
but it's very uncommon and not nearly as developer friendly
How to Support Single File Components
Vite is a build tool that can compile .vue files (among other things)
How to Start a Vue Project with Vite
npm init vue@3
Demo Time!
SFC Support for VS Code
How to Start a Vue Project with Vite
(in a StackBlitz Playground)
https://vite.new/vue
Demo Time!
This is how you'll build most of your applications
but everything we've discussed so far works the same in this context
⏰ 25 mins
// ProductCard.vue
<template>
<div>
<a :href="link">
<img :src="image" :alt="title">
</a>
<p>{{ title }}</p>
<p>{{ price }} kr</p>
</div>
</template>
What title? What price?
// ProductCard.vue
<script>
export default{
props: ['title', 'price', 'link', 'image']
}
</script>
<template>
<div>
<a :href="link">
<img :src="image" :alt="title">
</a>
<p>{{ title }}</p>
<p>{{ price }} kr</p>
</div>
</template>
// ProductCard.vue
<script>
export default{
props: ['title', 'price', 'link', 'image']
}
</script>
<template>
<div>
<a :href="link">
<img :src="image" :alt="title">
</a>
<p>{{ title }}</p>
<p>{{ price }} kr</p>
</div>
</template>
// App.vue
<script>
import ProductCard from "@/components/ProductCard.vue"
export default {
components: ProductCard,
data() {
return {
items: [
{ title: 'Socks', price: 3, link: 'comfysocks.com', image: 'socks.jpg' },
{ title: 'Shoes', price: 118, link: 'comfyshoes.com', image: 'shoes.jpg' },
{ title: 'Shirt', price: 19, link: 'comfyshirts.com', image: 'shirt.jpg' }
]
}
}
}
</script>
<template>
<ProductCard
v-for="item in items"
:title="item.title"
:price="item.price"
:link="item.link"
:image="item.image"
></ProductCard>
</template>
pass data from top to bottom
// ProductCard.vue
<script>
import ProductCardImage from "@/components/ProductCardImage.vue"
export default{
components: ProductCardImage
//...
}
</script>
<template>
<div>
<ProductCardImage :image="image"></ProductCardImage>
<p>{{ title }}</p>
<p>{{ price }} kr</p>
</div>
</template>
From Vuejs.org
<div id="app">
<UserCard :user="user"></UserCard>
<NotificationMessage type="success"></NotificationMessage>
<NavbarItem text="home" url="/"></NavbarItem>
</div>
// ProductCard.vue
<script>
export default{
props: ['title', 'price', 'link', 'image'],
methods:{
doThing(){
this.title = "something else" // ❌ Big no, no!
}
}
}
</script>
<template>
...
</template>
app.component('product-card', {
template: '#product-card-template',
props: {
title: {
type: String,
required: true
},
price: {
type: Number
}
}
})
⏰ 20 mins
// ClickCounter.vue
<script>
export default{
data () {
return {
count: 0
}
},
methods: {
increase () {
this.count++
this.$emit('increased', this.count)
}
}
}
</script>
<template>
<button v-on:click="count++">hit me {{ count }}</button>
</template>
event name
payload
// App.vue
<script>
export default{
data() {
return {
counter: null
}
},
methods: {
setCounter (count) {
this.counter = count
}
}
}
</script>
<template>
<ClickCounter @increased="setCounter"></ClickCounter>
</template>
event name
event handler
event payload
// App.vue
<script>
export default{
data() {
return {
counter: null
}
},
}
</script>
<template>
<ClickCounter @increased="counter = $event"></ClickCounter>
</template>
inline handler
event payload
Like ordinary HTML events do
<ProductCardImage>
<ProductCard>
<ProductList>
<ProductCardImage>
<ProductCard>
<ProductList>
$emit('image-broken')
✅
<ProductCardImage>
<ProductCard>
<ProductList>
$emit('image-broken')
$emit('image-broken')
✅
Just don't do it too much!
(Might need Pinia for global state management)
// ClickCounter.vue
<script>
export default{
emits: ['increased'],
data () {
return {
count: 0
}
},
methods: {
increase () {
this.count++
this.$emit('increased', this.count)
}
}
}
</script>
<template> ....</template>
app.component('click-counter', {
template: '<button v-on:click="increased">hit me {{ count }}</button>',
emits: ['increased'],
props: ['initialCount'],
data () {
return {
count: this.initialCount ?? 0
}
},
methods: {
increase () {
this.count++
this.$emit('increased', this.count)
}
}
})
export default {
emits: {
// no validation
click: null,
// with validation
submit: payload => {
if (payload.email && payload.message) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
}
Events are logged in devtools
⏰ 20 mins
{
props: ['id'],
data () {
return {
blogPost: null
}
},
created () {
axios.get('api/posts/' + this.id).then(response => {
this.blogPost = response.data
})
}
}
https://vuejs.org/guide/essentials/lifecycle.html
⏰ 20 mins