<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@3Demo Time!
SFC Support for VS Code
How to Start a Vue Project with Vite
(in a StackBlitz Playground)
https://vite.new/vueDemo 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