Skip to main content
โšก Calmops

Vue.js: Getting Started

Vue.js: Getting Started

Vue.js is a progressive JavaScript framework for building user interfaces. This article covers Vue.js fundamentals.

Introduction

Vue.js provides:

  • Reactive data binding
  • Component-based architecture
  • Simple template syntax
  • Progressive enhancement
  • Excellent developer experience

Understanding Vue.js helps you:

  • Build interactive UIs
  • Create reusable components
  • Manage application state
  • Handle user interactions
  • Build single-page applications

Vue.js Setup

Installation and Project Setup

# โœ… Good: Create Vue project with Vite
npm create vite@latest my-app -- --template vue
cd my-app
npm install
npm run dev

# โœ… Good: Create Vue project with Vue CLI
npm install -g @vue/cli
vue create my-app
cd my-app
npm run serve

# โœ… Good: Add Vue to existing project
npm install vue

Basic Vue Application

// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

// App.vue
<template>
  <div id="app">
    <h1>{{ message }}</h1>
    <button @click="count++">Count: {{ count }}</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!',
      count: 0
    }
  }
}
</script>

<style scoped>
h1 {
  color: #42b983;
}
</style>

Vue Templates

Template Syntax

<!-- โœ… Good: Text interpolation -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ count + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
  </div>
</template>

<!-- โœ… Good: Attribute binding -->
<template>
  <div>
    <img :src="imageSrc" :alt="imageAlt" />
    <a :href="url">Link</a>
    <button :disabled="isDisabled">Click</button>
  </div>
</template>

<!-- โœ… Good: Event handling -->
<template>
  <div>
    <button @click="handleClick">Click me</button>
    <input @input="handleInput" />
    <form @submit.prevent="handleSubmit">
      <button type="submit">Submit</button>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello',
      count: 0,
      imageSrc: '/image.jpg',
      imageAlt: 'Image',
      url: 'https://example.com',
      isDisabled: false
    }
  },
  methods: {
    handleClick() {
      this.count++
    },
    handleInput(event) {
      this.message = event.target.value
    },
    handleSubmit() {
      console.log('Form submitted')
    }
  }
}
</script>

<!-- โœ… Good: Conditional rendering -->
<template>
  <div>
    <p v-if="seen">Now you see me</p>
    <p v-else>Now you don't</p>
    <p v-show="visible">Visible</p>
  </div>
</template>

<!-- โœ… Good: List rendering -->
<template>
  <div>
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]
    }
  }
}
</script>

Vue Components

Single File Components

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <p>Email: {{ user.email }}</p>
    <button @click="$emit('edit', user.id)">Edit</button>
  </div>
</template>

<script>
export default {
  props: {
    user: {
      type: Object,
      required: true
    }
  },
  emits: ['edit']
}
</script>

<style scoped>
.user-card {
  border: 1px solid #ccc;
  padding: 1rem;
  border-radius: 4px;
}
</style>

<!-- App.vue -->
<template>
  <div>
    <UserCard
      v-for="user in users"
      :key="user.id"
      :user="user"
      @edit="handleEdit"
    />
  </div>
</template>

<script>
import UserCard from './components/UserCard.vue'

export default {
  components: {
    UserCard
  },
  data() {
    return {
      users: [
        { id: 1, name: 'John', email: '[email protected]' },
        { id: 2, name: 'Jane', email: '[email protected]' }
      ]
    }
  },
  methods: {
    handleEdit(userId) {
      console.log('Edit user:', userId)
    }
  }
}
</script>

Props and Emits

<!-- Button.vue -->
<template>
  <button
    :class="['btn', `btn-${variant}`]"
    :disabled="disabled"
    @click="$emit('click')"
  >
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: {
    variant: {
      type: String,
      default: 'primary',
      validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  emits: ['click']
}
</script>

<!-- Form.vue -->
<template>
  <form @submit.prevent="handleSubmit">
    <input
      v-model="formData.email"
      type="email"
      placeholder="Email"
    />
    <input
      v-model="formData.password"
      type="password"
      placeholder="Password"
    />
    <Button variant="primary" @click="handleSubmit">
      Login
    </Button>
  </form>
</template>

<script>
import Button from './Button.vue'

export default {
  components: { Button },
  data() {
    return {
      formData: {
        email: '',
        password: ''
      }
    }
  },
  methods: {
    handleSubmit() {
      this.$emit('submit', this.formData)
    }
  }
}
</script>

Vue Reactivity

Reactive Data

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="count++">Increment</button>
    <button @click="resetCount">Reset</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    resetCount() {
      this.count = 0
    }
  }
}
</script>

<!-- โœ… Good: Computed properties -->
<template>
  <div>
    <p>Full name: {{ fullName }}</p>
    <input v-model="firstName" placeholder="First name" />
    <input v-model="lastName" placeholder="Last name" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`
    }
  }
}
</script>

<!-- โœ… Good: Watchers -->
<template>
  <div>
    <input v-model="query" placeholder="Search..." />
    <p>Results: {{ results.length }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      query: '',
      results: []
    }
  },
  watch: {
    query(newValue) {
      if (newValue) {
        this.searchUsers(newValue)
      } else {
        this.results = []
      }
    }
  },
  methods: {
    async searchUsers(query) {
      const response = await fetch(`/api/search?q=${query}`)
      this.results = await response.json()
    }
  }
}
</script>

Vue Composition API

Setup Function

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { ref, computed, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)

    const increment = () => {
      count.value++
    }

    const doubleCount = computed(() => count.value * 2)

    watch(count, (newValue) => {
      console.log('Count changed to:', newValue)
    })

    return {
      count,
      increment,
      doubleCount
    }
  }
}
</script>

<!-- โœ… Good: Composition API with lifecycle -->
<template>
  <div>
    <p>User: {{ user.name }}</p>
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    const user = ref(null)

    onMounted(async () => {
      const response = await fetch('/api/user')
      user.value = await response.json()
    })

    onUnmounted(() => {
      console.log('Component unmounted')
    })

    return { user }
  }
}
</script>

<!-- โœ… Good: Custom composables -->
<script>
// useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)

  const increment = () => {
    count.value++
  }

  const decrement = () => {
    count.value--
  }

  const reset = () => {
    count.value = initialValue
  }

  const doubleCount = computed(() => count.value * 2)

  return {
    count,
    increment,
    decrement,
    reset,
    doubleCount
  }
}

// Component.vue
import { useCounter } from './useCounter'

export default {
  setup() {
    const { count, increment, decrement } = useCounter(0)

    return {
      count,
      increment,
      decrement
    }
  }
}
</script>

Vue Router

Basic Routing

// router.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from './pages/Home.vue'
import About from './pages/About.vue'
import UserProfile from './pages/UserProfile.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/users/:id', component: UserProfile }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

// App.vue
<template>
  <div>
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/about">About</router-link>
    </nav>
    <router-view></router-view>
  </div>
</template>

Best Practices

  1. Use v-for with keys:

    <!-- โœ… Good: Always use key -->
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
    
    <!-- โŒ Bad: No key -->
    <li v-for="item in items">
      {{ item.name }}
    </li>
    
  2. Use computed properties:

    <!-- โœ… Good: Computed property -->
    <p>{{ fullName }}</p>
    
    <!-- โŒ Bad: Method in template -->
    <p>{{ getFullName() }}</p>
    
  3. Use event modifiers:

    <!-- โœ… Good: Event modifiers -->
    <form @submit.prevent="handleSubmit">
      <button @click.stop="handleClick">Click</button>
    </form>
    
    <!-- โŒ Bad: Manual prevention -->
    <form @submit="handleSubmit">
      <button @click="handleClick">Click</button>
    </form>
    

Summary

Vue.js is powerful. Key takeaways:

  • Use Vue templates for reactive UIs
  • Create reusable components
  • Use computed properties for derived state
  • Use watchers for side effects
  • Use Composition API for complex logic
  • Implement routing with Vue Router
  • Follow Vue best practices
  • Build scalable applications

Next Steps

Comments