Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Practical Guide to Developing with Vue 3

Tech Nov 6 1

To debug Vue applications, use the Developer Tools panel acccessible by right-clicking in Chrome and selecting "Inspect" or pressing F12.

Run project scripts defined in package.json using npm or pnpm. For development, execute npm run serve (or pnpm dev) to start the server, and npm run build for production builds.

Configuring TypeScript

Errors in tsconfig.node.json, such as an invalid --lib option, often stem from a TypeScript version mismatch. Update TypeScript to the latest version:

npm install -D typescript@latest

Ensure trailing commas are removed within array configurations. For linting errors related to line endings (e.g., Delete ␍⏎), run the project's lint command:

npm run lint:eslint

Project Initialization

Install Node.js and configure npm. Use a package manager like pnpm for efficiency:

npm install -g pnpm

Create a new Vue 3 project using Vite, the recommended build tool:

npm init vue@latest
cd your-project
pnpm install
pnpm dev

The main entry points are main.js, App.vue, and the router configuration.

Composition API with <script setup>

The <script setup> syntax is the standard for Vue 3's Composition API, offering a concise syntax without the need for manual return statements.

<script setup lang="ts">
import { useRouter } from 'vue-router';

const router = useRouter();
const emit = defineEmits(['update', 'remove'])

interface ComponentProps {
  title: string;
  isActive?: boolean;
}
const props = withDefaults(defineProps<ComponentProps>(), { title: 'Default' });

const handleClick = () => {
  router.push('/home');
};
</script>

The setup() function serves as the entry point for the Composition API. It executes before the created lifecycle hook, and its this context is undefined.

Raective State Management

Use ref for primitive values and reactive for objects. ref returns a reactive reference object requiring .value access.

<script setup>
import { ref, reactive } from 'vue'

const counter = ref(0)
const increment = () => {
  counter.value++
}

const userProfile = reactive({ name: 'Alex', score: 100 })
const updateScore = () => {
  userProfile.score += 10
}
</script>

To destructure reactive object properties while maintaining reactivity, use toRefs.

<script setup>
import { reactive, toRefs } from 'vue';

const profile = reactive({ username: 'roy', points: 1000 });
const { username, points } = toRefs(profile);

function addPoints() {
  points.value += 500;
}
</script>

Core Template Directives

  • Event Handling (v-on or @)
    <button @click="handleClick">Click</button>
    <button @click.stop="preventBubble">Stop Propagation</button>
    
  • Attribute Binding (v-bind or :)
    <img :src="imageUrl">
    <div :class="{ active: isActive }"></div>
    <div v-bind="multipleAttributes"></div>
    
  • Two-Way Binding (v-model)
    <input v-model="userInput" />
    
  • One-time Interpolation (v-once)
    <span v-once>Static: {{ staticMessage }}</span>
    
  • Raw HTML (v-html)
    <div v-html="rawHtmlContent"></div>
    
  • Conditional Rendering (v-if)
    <p v-if="isVisible">This is conditionally shown.</p>
    

Component Communication

  • Props (Parent to Child) Parent component:
    <template>
      <ChildComponent :item-data="itemData" :count="countValue" />
    </template>
    <script setup>
    import { ref } from 'vue';
    const countValue = ref(0);
    const itemData = { id: 1 };
    </script>
    
    Child component:
    <template>
      <div>{{ props.itemData.id }} - {{ count }}</div>
    </template>
    <script setup>
    const props = defineProps(['itemData', 'count']);
    </script>
    
  • Custom Events (Child to Parent) Use defineEmits and the $emit method.
  • Global Event Bus Utilize a third-party library like mitt for cross-component communication.
  • v-model with Components Implement two-way binding on custom components.
  • useAttrs Access non-prop attributes passed to a component.

Component Lifecycle and Properties

  • computed: Derives reactive state from other reactive sources.
    import { computed, ref } from 'vue';
    const firstName = ref('John');
    const lastName = ref('Doe');
    const fullName = computed(() => `${firstName.value} ${lastName.value}`);
    
  • methods: Define functions for event handlers or logic.
  • watch: Perform side effects in reaction to state changes.
    import { watch, ref } from 'vue';
    const searchTerm = ref('');
    watch(searchTerm, (newVal, oldVal) => {
      console.log(`Term changed from ${oldVal} to ${newVal}`);
      // Perform search
    });
    
  • mounted: Execute code after the component's DOM is rendered.

Routing Setup with Vue Router

Define routes in src/router/index.ts:

import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '@/views/HomeView.vue';
import UserProfile from '@/views/UserProfile.vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: HomeView },
    { path: '/user/:id', component: UserProfile, props: true },
    {
      path: '/settings',
      component: SettingsLayout,
      children: [
        { path: 'profile', component: ProfileComponent },
        { path: 'security', component: SecurityComponent }
      ]
    }
  ]
});
export default router;

Use the router in components:

<template>
  <nav>
    <router-link to="/">Home</router-link>
    <router-link :to="{ name: 'User', params: { id: userId } }">Profile</router-link>
  </nav>
  <router-view></router-view>
</template>
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const userId = route.params.id;
</script>

Creating Custom Directives

Define a directive for role-based element control:

// directive/auth.ts
import { App, Directive, DirectiveBinding } from 'vue';
const permissionSet = new Set(['create', 'edit', 'view']);
const authDirective: Directive = {
  mounted(el: Element, binding: DirectiveBinding) {
    const requiredPermission = binding.value;
    if (!permissionSet.has(requiredPermission)) {
      el.parentNode?.removeChild(el);
    }
  }
};
export default {
  install(app: App) {
    app.directive('auth', authDirective);
  }
};

Register it in main.ts:

import AuthDirective from './directive/auth';
app.use(AuthDirective);

Use in templates:

<button v-auth="'create'">Create</button>
<button v-auth="'delete'">Delete</button> <!-- Will be removed -->

TypeScript and Arrow Functions

Arrow functions provide concise syntax. When using generics in .tsx or .vue files with <script setup lang="ts">, a trailing comma may be required.

const identity = <T,>(value: T): T => value;
const processItem = <T extends Record<string, unknown>>(item: T): T => {
  console.log(item);
  return item;
};

Integrating UI Libraries

For Element Plus:

pnpm add element-plus @element-plus/icons-vue

Configure in main.ts:

import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';

const app = createApp(App);
app.use(ElementPlus);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component);
}
app.mount('#app');

Use components:

<template>
  <el-button type="primary" :icon="Search">Search</el-button>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue';
</script>

HTTP Client with Axios

Install Axios and create an instance with interceptors.

npm install axios

Basic usage:

import axios from 'axios';
axios.get('/api/data', { params: { id: 1 } })
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

axios.post('/api/user', { name: 'John' })
  .then(response => console.log(response.data));

Create a configured service instance:

// utils/request.js
import axios from 'axios';
import { ElMessage } from 'element-plus';

const httpClient = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
});

httpClient.interceptors.response.use(
  response => {
    const { data, meta } = response.data;
    if (meta.status === 200) {
      return data;
    } else {
      ElMessage.error(meta.msg);
      return Promise.reject(new Error(meta.msg));
    }
  },
  error => {
    return Promise.reject(error);
  }
);
export default httpClient;

API layer abstraction:

// api/user.js
import httpClient from '@/utils/request';
export function fetchUsers() {
  return httpClient({ url: '/users' });
}
export function createUser(payload) {
  return httpClient({ 
    method: 'post', 
    url: '/users', 
    data: payload 
  });
}

State Management with Pinia

Pinia offers a simpler API compared to Vuex. Define a store:

// stores/user.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
  state: () => ({ 
    name: 'Guest', 
    token: null 
  }),
  getters: {
    isLoggedIn: (state) => !!state.token
  },
  actions: {
    setUserInfo(payload) {
      this.name = payload.name;
      this.token = payload.token;
    },
    async fetchProfile() {
      const data = await api.getProfile();
      this.name = data.name;
    }
  }
});

Use in a component:

<template>
  <div>Welcome, {{ user.name }}</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';

const userStore = useUserStore();
const { name } = storeToRefs(userStore);

const login = () => {
  userStore.setUserInfo({ name: 'Admin', token: 'abc123' });
};
</script>

For persistence, use pinia-plugin-persistedstate.

Browser Storage

  • localStorage: Persistent storage.
    localStorage.setItem('user', JSON.stringify(userData));
    const user = JSON.parse(localStorage.getItem('user'));
    localStorage.removeItem('user');
    
  • sessionStorage: Session-only storage.
    sessionStorage.setItem('formData', JSON.stringify(formState));
    const data = JSON.parse(sessionStorage.getItem('formData'));
    

Environment Variables and Modes

Vite exposes environment variables prefixed with VITE_.

# .env.development
VITE_API_BASE_URL=/api
VITE_APP_TITLE=Development App

Access in code:

const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
const isProduction = import.meta.env.PROD;

In Webpack-based projects (Vue CLI), use VUE_APP_ prefix.

Type Assertions and Record Utility

  • Type Assertion (as)
    const element = document.getElementById('input') as HTMLInputElement;
    
  • Record<K, T> Utility Type Defines an object type with keys of type K and values of type T.
    type Ingredient = 'flour' | 'sugar' | 'eggs';
    const inventory: Record<Ingredient, number> = {
      flour: 100,
      sugar: 50,
      eggs: 12
    };
    

Icon Integration

Use Iconify for a vast icon collection. Install the Vue integration:

pnpm add @iconify/vue

Create a wrapper component IconifyIcon.vue:

<!-- components/IconifyIcon.vue -->
<template>
  <component :is="component" :icon="icon" v-bind="$attrs" />
</template>
<script setup lang="ts">
import { Icon } from '@iconify/vue';
const props = defineProps({
  icon: { type: String, required: true }
});
const component = Icon;
</script>

Use it in templates:

<template>
  <IconifyIcon icon="mdi:home" height="20" />
  <el-button>
    <IconifyIcon icon="ri:refresh-line" /> Refresh
  </el-button>
</template>
<script setup>
import IconifyIcon from '@/components/IconifyIcon.vue';
</script>
Tags: Vue 3

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.