Prerequisites

Webpack

将 js 代码打包到一个文件中

webpack

安装npm install webpack -g

使用webpack <input> <output>

vue cli(deprecated)

Vue CLI the official webpack-based toolchain for Vue. It is now in maintenance mode and we recommend starting new projects with Vite unless you rely on specific webpack-only features.

安装npm install -g @vue/cli

创建项目vue create my-project

# serve
vue-cli-service serve --host 0.0.0.0 --port 8080

# build
vue-cli-service build

# lint
vue-cli-service lint

vite

Vue.js - Tooling

Vite - Getting Started

创建项目npm create vite@latest

# serve at localhost
vite

# serve at 0.0.0.0
vite --host

# build only
vite build

# preview
vite preview

配置文件 vite.config.mjs

import {defineConfig} from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    watch: {
      usePolling: true, // hot reload
    }
  },
});

初始化

npm create vue@latest # 实际也是使用的 vite 构建
# or
npm create vite@latest

可以看到创建了以下文件

public/
src/
|- App.vue
|- main.js
index.html
package.json
vite.config.js

其中,package.json 内容如下,可以看出其实是使用 vite 来构建的项目,与 npm create vite@latest 命令类似。不过用 vite 初始化支持更多的 web 框架,例如 react、nuxt 等

{
  "name": "demo",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.4.21"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "vite": "^5.1.6"
  }
}

src/main.js 是入口文件,这里引入根组件 App.vue

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

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

修改 App.vue 即可进行 vue 应用的开发

安装依赖包

npm install

运行

npm run dev

环境配置

# install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | zsh
export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node/

# install node
nvm install node
nvm install 12.19.0 # 指定版本
nvm use 12.19.0 # 使用版本
nvm alias default 12.19.0 # 设置为 default 版本

# npm registry
npm config set registry https://registry.npm.taobao.org

# 安装 module
npm install <module> # 为当前目录安装 module,只会安装到当前目录的 node_modules 目录下
npm install <module> -g # 全局安装 module
npm install --save <module> # -S: 安装 moduel 的同时将其添加到 package.json 的 dependencies 中
npm install --save-dev <module> # -D: 只在开发过程中用到的模块,添加到 package.json 的 devDependencies 中

# update npm
npm update -g

# install cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org

# intall webpack
# i 表示 install, -D 表示 --save-dev, -S 表示 --save
npm i -D webpack
npm i -D webpack-cli

# intall vue-cli
npm install vue-cli -g

# 运行Web服务
npm run dev

webpack 配置文件

module.exports = {
    entry: './src/scripts/index.js',    // 需要被打包的js文件路径及文件名
    output: {
        path: __dirname + '/dist',    // 打包输出的目标文件的绝对路径(其中__dirname为当前目录的绝对路径)
        filename: 'scripts/index.js'   // 打包输出的js文件名及相对于dist目录所在路径
    }
};

Syntax

Template Syntax

text

<span>Message: {{ msg }}</span>

v-html

<p>Using v-html directive: <span v-html="rawHtml"></span></p>

v-bind:Attribute 绑定

<div v-bind:id="dynamicId"></div>

or using shorthand

<div :id="dynamicId"></div>

boolean variable

<button :disabled="isButtonDisabled">Button</button>

bind multiple attributes

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}
<div v-bind="objectOfAttrs"></div>

valid expression

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

call function

<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</time>

dynamic arguments

<a v-bind:[attributeName]="url"> ... </a>
<!-- shorthand -->
<a :[attributeName]="url"> ... </a>

v-on:事件处理

<a v-on:click="doSomething"> ... </a>

shorthand

<a @click="doSomething"> ... </a>

dynamic arguments

<a v-on:[eventName]="doSomething"> ... </a>
<!-- shorthand -->
<a @[eventName]="doSomething"> ... </a>

inline handlers

<script setup>
const count = ref(0)
</script>

<template>
  <button @click="count++">Add 1</button>
  <p>Count is: {{ count }}</p>
</template>

method handlers

<script setup>
const name = ref('Vue.js')

function greet(event) {
  alert(`Hello ${name.value}!`)
  // `event` is the native DOM event
  if (event) {
    alert(event.target.tagName)
  }
}
</script>

<template>
  <button @click="greet">Greet</button>
</template>

call methods in inline handlers

<script setup>
function say(message) {
  alert(message)
}
</script>

<template>
  <button @click="say('hello')">Say hello</button>
  <button @click="say('bye')">Say bye</button>
</template>

v-if:条件渲染

<h1 v-if="awesome">Vue is awesome!</h1>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>


<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-for:列表渲染

<div v-for="item in items">
  {{ item.text }}
</div>

<div v-for="(item, index) in items"></div>
<div v-for="(value, key) in object"></div>
<div v-for="(value, name, index) in object"></div>

<div v-for="item in items" :key="item.id">
  {{ item.text }}
</div>

v-model:表单输入绑定

text:use value property and input event

<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />

multiline text:use value property and input event

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

checkbox:use checked property and change event

<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

multiple checkboxes

<script setup>
const checkedNames = ref([])
</script>

<template>
<div>Checked names: {{ checkedNames }}</div>

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>

<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>

<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
</template>

radio:use checked property and change event

<div>Picked: {{ picked }}</div>

<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>

<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>

select:uses value as a prop and change as an event

<div>Selected: {{ selected }}</div>

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

Ref

declare reactive state

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

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>

nested refs

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

const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // these will work as expected.
  obj.value.nested.count++
  obj.value.arr.push('baz')
}
</script>

reactive(): unlike a ref which wraps the inner value in a special object, reactive() makes an object itself reactive:

<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
</script>

<button @click="state.count++">
  {{ state.count }}
</button>

Computed Properties

Vue.js - Computed Properties

  • 计算过程不要有副作用(side effect)
  • Think of it as a temporary snapshot - every time the source state changes, a new snapshot is created
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// a computed ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

Lifecycle Hooks

vue_lifecycle.png

registering lifecycle hooks

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

Watchers

Vue.js - Watchers

在每次响应式状态发生变化时触发回调函数

<script setup>
const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // fires on nested property mutations
  // Note: `newValue` will be equal to `oldValue` here
  // because they both point to the same object!
})

obj.count++
</script>

Eager watcher: executed immediately and then watch

<script setup>
watch(
  source,
  (newValue, oldValue) => {
    // executed immediately, then again when `source` changes
  },
  { immediate: true }
)
</script>

Once watcher: trigger only once

<script setup>
watch(
  source,
  (newValue, oldValue) => {
    // when `source` changes, triggers only once
  },
  { once: true }
)
</script>

Component

Using component

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

props

Define props

<script setup>
const props = defineProps(['title'])
console.log(props.title)
</script>

<template>
  <h4>{{ title }}</h4>
</template>

Passing props

<BlogPost title="My journey with Vue" />

Props with typing

<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})

props.foo // string
props.bar // number | undefined
</script>

Emit

<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

<!-- usage
<BlogPost
  @enlarge-text="postFontSize += 0.1"
 />
-->

slot

<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<!-- usage
<AlertBox>
  Something bad happened.
</AlertBox>
-->

Note from Vue 2

父页面传参用props

# component部分
Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

# 页面部分
<blog-post title="My journey with Vue"></blog-post>

注意:props一般不能改变

How to Use Props in Vue: The Ultimate Guide Vue Error: Avoid Mutating a Prop Directly

组件向要改变父页面参数要用 $emit

// component部分
<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

// 页面部分
<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"
></blog-post>

v-model 要使用下面这种方式绑定

// component部分
Vue.component('vmodel', {
  props: ['value'],
  template: '<input v-bind:value="value" v-on:input="$emit(\'input\', $event.target.value)">'
})

// 页面部分
<vmodel v-model="searchText"></vmodel>
引用对象

先引入组件

import ECharts from 'vue-echarts'

在页面中引用时,指定 ref

<v-chart ref="chart" :options="orgOptions"></v-chart>

然后通过 this.$refs.chart 即可引用该对象

API

Fetch

import { ref, onMounted } from 'vue';

const data = ref([]);

const fetchData = async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const result = await response.json();
    data.value = result;
  } catch (error) {
    console.error('Error fetching data:', error);
  }
};

onMounted(() => {
  fetchData();
});

post

const response = await fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(inputData.value),
});

Axios

Install & Import

安装

npm install --save axios vue-axios

导入

import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'

Vue.use(VueAxios, axios)

Request

this.axios
  .get(api)
  .then(
    (response) => {
      console.log(response.data)
    }
  )
  .catch(err => {console.log(err)})
传参
axios.get('https://httpbin.org/get?answer=42')

等价于

axios.get('https://httpbin.org/get', {params: {answer: 42}})

Router

Vue Router - Getting Started

创建 router

// router.js
import { createRouter, createWebHistory } from 'vue-router'

import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
import NotFoundPage from './NotFoundPage.vue'

const routes = [
  { path: '/', component: HomeView },
  { path: '/about', component: AboutView },
  { path: '/:pathMatch(.*)*', component: NotFoundPage, }, // catch-all route for 404
]

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

export default router

createWebHistory v.s. createMemoryHistory

Feature createWebHistory createMemoryHistory
URL Appearance Clean URLs (e.g., /home, /about) Does not affect browser’s address bar
Use Case Production environments, SEO SSR, testing, non-browser environments
Server Configuration Requires server-side fallback handling No server configuration needed
Navigation Stack Managed by browser’s history stack Managed in memory

使用 query 传参

路由需由 path 引入

<router-link :to="{path: '/home', query: {key: 'hello', value: 'world'}}">
  <button>跳转</button>
</router-link>  

跳转地址:/home?key=hello&value=world

参数:this.$route.query.key

使用 params 传参

路由需由 name 引入

<router-link :to="{name: '/home', params: {key: 'hello', value: 'world'}}">
  <button>跳转</button>
</router-link> 

跳转地址:/home

参数:this.$route.params.key

$router 方式

使用 query 传参

this.$router.push({
  path: '/detail',
  query: {
    name: 'admin',
    code: 10021
  }
});

跳转地址:/detail?name=admin&code=10021

参数:this.$route.query.name

使用 params 传参

this.$router.push({
  name: 'detail',
  params: {
    code: 10021
  }
});

跳转地址:/detail

参数:this.$route.params.code

unplugging-vue-router

Environment Variables

Vite - Env Variables and Modes

// only variables prefixed with `VITE_` are exposed to your Vite-processed code
console.log(import.meta.env.VITE_SOME_KEY) // "123"
console.log(import.meta.env.DB_PASSWORD) // undefined

TypeScript Guide

Using Vue with TypeScript

Markdown

Echarts

部署

  1. 打包
npm run build
  1. 将发布包文件夹 dist 上传至服务器

  2. 配置 Nginx

server {
    listen 80;
    server_name localhost;
    charset utf-8;
    root /path/to/dist;
}
  1. 或者配置 Caddy
your_domain {
	handle_path /web/* {
        root * /path/to/your/vue/app/dist
        file_server
        encode gzip
        try_files {path} /
	}
}
  • 如果需要配置子路径,需要在 vite.config.mjs 加上 base: "/prefix",且在 Caddyfile 配置 handle_path

References