- 1初始化
- 2响应式数据与插值表达式
- 3计算属性
- 4侦听器
- 5指令
- 6组件通信
- 7组件插槽
- 8Vue Router
- 9Vuex
- 10vue3
- 11vue3状态管理-pinia
这篇文章仅仅是分享 Vue 的部分内容,并没有完整地对其进行学习总结,想要完整地学习 Vue,请移至:https://cn.vuejs.org/guide/essentials/template-syntax.html
1初始化
npm create vite@latest
(npm 安装 vite,通过 vite 对 vue 项目进行构建)
在通过 vite 构建好 vue 项目文件夹后,输入命令行指令对 vue 项目进行初始化
也可以直接通过 npm create vue@latest
命令构建,vue 默认使用 vite 进行管理
当然,如果是简单的项目,vue 也支持 cdn 方式引入
2响应式数据与插值表达式
<!-- Basic.vue -->
<script>export default {data() {return {name: "Jack",}},methods: {output() {return `this student's name is Jack `}}}
</script><template><p>{{ name }}</p><p>{{ 1 + 2 + 3 + 4 }}</p><p>{{ 1 > 2 ? "right" : "wrong" }}</p><p>{{ output() }}</p>
</template>
vue 内置 data 方法可以通过 return 返回数据,methods 对象包含着需要定义的方法,插值表达式 {{ }},可以将 data 方法的返回值进行渲染,也可以内部进行一些逻辑的运算并对结果进行呈现
3计算属性
计算属性与 methods 对象类似,都是方法的集合,但是计算属性具有缓存性,在响应式变量没有改变时,不会重复计算,而是输出缓存结果
<!-- Computed.vue -->
<script>export default {data() {return {number: 2,}},computed: {compare() {console.log('compare function run')return this.number > 4 ? `${this.number} is over 4` : `${this.number} is not over 4`}}}
</script><template><p>{{ number }}</p><p>{{ compare }}</p><p>{{ compare }}</p>
</template>
注意,上述确实会生成两个具有相同内容的 p 标签,但是控制台输出仅有一次,即没有重复计算,并且,计算属性内部虽然写成函数形式,但是外部调用是不能加括号的
4侦听器
侦听器的主要功能是在响应式变量更新后,渲染页面的同时执行一些其它操作
<!-- Listener.vue -->
<script>export default {data() {return {isTrue: false}},methods: {change() {this.isTrue = true}},watch: {isTrue(newValue, oldValue) {console.log(`(new, old): (${newValue}, ${oldValue})`)}}}
</script><template><p>{{ change() }}</p>
</template>
5指令
指令 | 说明 |
---|---|
v-text | 渲染文本,无法对标签进行渲染 |
v-html | 可渲染标签 |
v-for | 循环渲染 |
v-if | 选择性渲染,不渲染时会被移除 |
v-show | 选择性隐藏 |
v-bind | 属性绑定响应式变量 |
v-on | 事件绑定 |
v-model | 绑定表单元素,实现双向数据绑定 |
<!-- Directives.vue -->
<script>export default {data() {return {text: "<span>text</span>",html: "<span>html</span>",name: "Jack",status: "pending",tasks: ["task one", "task two", "task three"],link: "https://cn.vuejs.org/guide/essentials/template-syntax.html",newTask: "",person: {age: 18,sex: "male",isStudent: true}}},methods: {toggleStatus() {if (this.status === "active") {this.status = "pending"} else if (this.status === "pending") {this.status = "inactive"} else {this.status = "active"}},addTask() {if (this.newTask.trim() !== "") {this.tasks.push(this.newTask)this.newTask = ""}},removeTask(index) {this.tasks.splice(index, 1)}}}
</script><template><p v-text="text"></p><p v-html="html"></p><br><p v-if="status === 'active'">{{ name }} is active</p><p v-else-if="status === 'pending'">{{ name }} is pending</p><p v-else>{{ name }} is inactive</p><br><p v-show="false">hidden content</p><form @submit.prevent="addTask"><input type="text" name="newTask" id="newTask" placeholder="add task" v-model="newTask"><button type="submit">add task</button></form><br><h2>cycle</h2><p v-for="i in 5">{{ i }}</p><br><h2>list of tasks</h2><ul><li v-for="(task, index) in tasks" :key="index"><span>{{ task }} </span><button @click="removeTask(index)">x</button></li></ul><br><h2>object cycle</h2><p v-for="(item, key, index) in person">item: {{ item }}, key: {{ key }}, index: {{ index }}</p><br><a v-bind:href="link">go to vue documnet</a><!-- <a :href="link">go to vue documnet</a> --><br><button v-on:click="toggleStatus">change status</button><!-- <button @click="toggleStatus">change status</button> -->
</template>
在 addTask 函数中,我们利用 JavaScript 中的 trim 函数实现空格的清除,实际上我们也可以在 v-model 后加入修饰符,如:v-model.trim,即可实现同样效果
6组件通信
- 父传子
vue 父传子组件通信与 react 类似,同样通过 props 机制进行数据传输
<!-- Comunication.vue -->
<script>export default {props: {name: String,age: {type: [Number, String],default: 18,// required: true, },isStudent: Boolean}}
</script><template><p>{{ name }} is {{ age }} years old, isStudent: {{ isStudent }}</p>
</template>
type 代表该数据的类型(可为多类型),default 为该数据默认值,required 代表该数据父组件必须传递
<!-- App.vue -->
<script>export default {components: {Communication}}import Communication from './components/Communication.vue';
</script><template><h1>Vue Demo</h1><Communication name="Jack" v-bind:is-student="parentIsStudent"/>
</template>
- 子传父
vue 子传父组件通信则需要通过自定义事件进行实现
<!-- Comunication.vue -->
<script>export default {data() {return {childCount: 0}},emits: ["childCountAdd"],methods: {addCount() {this.childCount++this.$emit("childCountAdd", this.childCount)}}}
</script><template><button v-on:click="addCount">add</button>
</template>
注意,在 vue3 中需要先在 emits 中声明自定义函数
<!-- App.vue -->
<script>export default {data() {return {childData: 0}},methods: {getChildCount(childCount) {this.childData = childCount}},components: {Communication}}import Communication from './components/Communication.vue';
</script><template><h1>Vue Demo</h1><Communication @childCountAdd="getChildCount"/><p>{{ childData }}</p>
</template>
7组件插槽
组件插槽,简单来说,就是标签内容,通过双标签实现差异化,不仅如此,插槽也可以实现父组件“调用”子组件变量
<!-- Slot.vue -->
<script>export default { data() {return {count: 0}}}
</script><template><br><slot>default content</slot><br><slot name="footer">default footer</slot><br><slot name="dataOne" v-bind:count="count">data content one</slot><br><slot name="dataTwo" v-bind:count="count">data content two</slot><br>
</template>
<!-- App.vue -->
<script>export default {data() {return { }},methods: { },components: {Slot}}import Slot from './components/Slot.vue';
</script><template><h1>Vue Demo</h1><Slot></Slot><Slot>content one</Slot><Slot>content two<template v-slot:footer>input footer two</template></Slot><Slot>content three<template #dataOne="dataObj">{{ dataObj.count }}</template></Slot><Slot>content three<template #dataTwo="{ count }">{{ count }}</template></Slot>
</template>
注意,父组件“调用”子组件变量,是因为变量本质是定义在子组件上,然后插槽的实质也是在子组件内部调用,然后该调用的返回值是一个对象,可以通过对象属性访问或者解构赋值的方式访问变量
8Vue Router
安装 vue-router 包:npm install vue-router@4
,vite 项目构建默认已安装 vue-router 插件
在 src 文件夹下创建 views 文件夹,该文件夹主要存储页面文件
<!-- Home.vue -->
<script>export default { }
</script><template><h1>Home Page</h1><p>This is home page.</p><a href="./#/About">goto</a>
</template>
<!-- About.vue -->
<script>export default { }
</script><template><h1>About Page</h1><p>This is about page.</p><a href="./#/">goto</a>
</template>
在 src 文件夹下创建 router 文件夹,并创建 index.js 文件
// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';const routes = [{ path: '/', name: 'Home', component: Home },{ path: '/about', name: 'About', component: About }
]const router = createRouter({history: createWebHashHistory(),routes
})export default router
注意:
属性 | 说明 |
---|---|
path | 实际 url 地址 |
name | 逻辑 url 地址,映射实际地址 |
component | 对应 url 地址显示组件 |
对于一些初始不用渲染的组件,我们可以使用懒加载进行优化,仅在使用时加载,如 About 组件,可以进行以下的懒加载优化
const About = () => import('@/views/About.vue')
以上,我们的简易 router 搭建完毕,然后我们需要引入 router
// src/main.js
import './assets/main.css'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 中添加 <router-view></router-view>
标签即可显示页面内容,也可以加入类似 <router-link to="/about">About</router-link>
的 nav 链接组实现各页面切换
动态路由:简单来讲就是为 url 添加查询参数实现不同的页面内容
<!-- Number.vue -->
<script>export default {props : ['number']}
</script><template><p>Number: {{ number }}</p>
</template>
{ path: '/number/:number', name: 'Number', component: Number, props: true }
(/router/index.js 中 routes 对象添加路径信息)
<!-- App.vue -->
<script>export default { }
</script><template><nav><router-link to="/number/2">Number2</router-link> |<router-link :to="{ name: 'Number', params: { number: 4 } }">Number4</router-link></nav><router-view></router-view>
</template>
注意,我们对于路径的设置存在两种方式,已在动态路由示例中演示
嵌套路由:简单来讲,即为原 url 路径添加子路径,与动态路由类似,但是嵌套路由是固定的不同路由,而动态路由则是根据某一查询参数变化而动态变化的路由
<!-- ProductComment.vue -->
<script>export default { }
</script><template><h1>Comment Page</h1><p>This page provides comments of some products.</p>
</template>
<!-- Product.vue -->
<script>export default { }
</script><template><h1>Product Page</h1><p>This is product page.</p><nav><router-link :to="{ name: 'product-comment'}">comment</router-link></nav><router-view></router-view>
</template>
{ path: '/product', name: 'Product', component: Product, children: [{ path: 'comment', name: 'product-comment', component: ProductComment }] }
(/router/index.js 中 routes 对象添加路径信息)
<router-link to="/product">Product</router-link>
(App.vue 中添加链接)
编程式导航:在某些情况下,实现自动跳转到某一路径
<!-- BackHome.vue -->
<script>export default {data() {return {timer: null}},created() {this.timer = setTimeout(() => {this.$router.push({ name: 'Home' })}, 3000)},beforeDestroy() {if (this.timer) {clearTimeout(this.timer)}}}
</script><template><h1>Back Home Page</h1><p>It will go home after 3 seconds.</p>
</template>
this.$router 能够指向 router 中设定的路由,上述代码实现 3 秒后跳转 home 页面
路由传参:各个 url 路径间传递数据
<!-- Deliver.vue -->
<script>
export default {data() {return {timer: null}},created() {this.timer = setTimeout(() => {this.$router.push({ name: 'product-comment', query: { comment: 'the product is good!' } })}, 3000)},beforeDestroy() {if (this.timer) {clearTimeout(this.timer)}}
}
</script><template><p>It will send a good commnet after 3 seconds in console.</p>
</template>
<!-- Product.vue -->
<script>
export default {created() {console.log(this.$route.query)}
}
</script><template><h1>Product Page</h1><p>This is product page.</p><nav><router-link :to="{ name: 'product-comment' }">comment</router-link> |<router-link :to="{ name: 'back-home' }">back</router-link> |<router-link :to="{ name: 'deliver-comment' }">deliver</router-link></nav><router-view></router-view>
</template>
注意此处的 this.$route 与之前的 this.$router 不同,之前的 router 是对路由进行一些操作,而 route 则是对路由数据进行一些操作
上述代码在由 Deliver 页面跳转至 ProductComment 页面后,会在控制台输出一条评论信息
导航守卫:对所有导航做统一设置
// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '@/views/Home.vue';const About = () => import('@/views/About.vue')
const Number = () => import('@/views/Number.vue')
const Product = () => import('@/views/Product.vue')
const ProductComment = () => import('@/views/product/ProductComment.vue')
const BackHome = () => import('@/views/product/BackHome.vue')
const Deliver = () => import('@/views/product/Deliver.vue')const routes = [{ path: '/', name: 'Home', component: Home },{ path: '/about', name: 'About', component: About },{ path: '/number/:number', name: 'Number', component: Number, props: true },{ path: '/product', name: 'Product', component: Product, children: [{ path: 'comment', name: 'product-comment', component: ProductComment }, { path: 'back', name: 'back-home', component: BackHome }, { path: 'deliver', name: 'deliver-comment', component: Deliver}] }
]const router = createRouter({history: createWebHashHistory(),routes
})router.beforeEach((to, from, next) => {console.log('router run')next()
})export default router
其中,router.beforeEach 是导航守卫函数之一,to 代表着前往路由,from 代表出发路由,next 代表切换路由函数,若不执行该函数,则页面不会相应更新
9Vuex
Vuex 是组件状态管理工具,安装 vuex 包:npm install vuex
,vite 项目构建默认已安装 vuex 插件
在 src 文件夹下创建 store 文件夹,并创建 index.js 文件
// src/store/index.js
import { createStore } from "vuex";const store = createStore({state() {},getters: {},mutations: {},actions: {},modules: {}
})export default store
然后,需要导入 store
// src/main.js
import './assets/main.css'import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
state:简单来讲,是全局数据存储,所有组件皆可使用 state 中的数据
// src/store/index.js
import { createStore } from "vuex";const store = createStore({state() {return {isLogin: true}}
})export default store
<!-- TestStore.vue -->
<script>
export default {data() {return {isLogin: this.$store.state.isLogin}}
}
</script><template>{{ isLogin }}
</template>
注意,通过 this.$store.state 访问 state 中的数据
实际上,可以直接通过赋值的方式修改 state 中的数据,但是这会破坏全局数据的一致性,所以并不建议这么做
通过 data 访问全局数据会更加规范,但是直接访问也是可行的
mutations:提供修改 state 中数据的途径
// src/store/index.js
import { createStore } from "vuex";const store = createStore({state() {return {count: 0}},mutations: {addCount(state, num) {state.count += num}}
})export default store
<!-- TestStore.vue -->
<script>
export default {methods: {add() {this.$store.commit('addCount', 1)}}
}
</script><template>{{ this.$store.state.count }}<button v-on:click="add">add</button>
</template>
注意访问 mutations 方法的方式是 this.$store.commit 请求,而非 this.$store.mutations 属性,当然也可以直接使用 mutations 方法,但是通过 methods 方法的方式使用 mutations 方法会更加规范
actions:mutations 是同步操作,对于异步操作,只会记录最终结果,而 actions 可以理解为异步的 mutations,但是注意,修改依旧依赖于 mutations 方法,actions 仅仅是添加异步操作
// src/store/index.js
import { createStore } from "vuex";const store = createStore({state() {return {count: 0,timer: null}},mutations: {addCount(state, num) {state.count += num},setDelayTimer(state, timer) {state.timer = timer},clearDelayTimer(state) {state.timer = null}},actions: {delayMinus(store, num) {if (store.state.timer) {clearTimeout(store.state.timer)}const timer = setTimeout(() => {store.commit('addCount', -num)store.commit('clearDelayTimer')}, 3000)store.commit('setDelayTimer', timer)}}
})export default store
<!-- TestStore.vue -->
<script>
export default {methods: {delayMinus() {this.$store.dispatch('delayMinus', 1)}}
}
</script><template>{{ this.$store.state.count }}<button @click="delayMinus">delay minus</button>
</template>
上述代码实现延迟 3 秒减一操作,若不进行删除定时器操作,效果类似,但是定时器冗余,可能会降低执行效率
注意,优先同步操作,后异步操作
getters:可以理解为全局的计算属性
// src/store/index.js
import { createStore } from "vuex";const store = createStore({state() {return {count: 0}},getters: {compare(state) {console.log('compare run')return state.count > -1}}modules() {}
})export default store
<!-- TestStore.vue -->
<script>
export default {methods: {compare() {console.log(this.$store.getters.compare)}}
}
</script><template><button @click="compare">compare</button>
</template>
modules:划分模块,各模块间不互相依赖,或者可以理解为封装,将针对某一部分用户的变量、方法等进行封装
// src/store/index.js
import { createStore } from "vuex";const store = createStore({modules: {student: {state() {return {id: '001'}},getters: {},mutations: {},actions: {}}}
})export default store
对此的访问也需要稍微改变: this.$store.state.student.id
当然,你也可以真正将 modules 中的对象封装为单独的 JavaScript 文件
// src/store/module/worker.js
const worker = {state() {return {job: 'cleaner'}},getters: {},mutations: {},actions: {}
}export default worker
// src/store/index.js
import { createStore } from "vuex";
import worker from "./module/worker";const store = createStore({modules: {student: {state() {return {id: '001'}},getters: {},mutations: {},actions: {}},worker: worker}
})export default store
10vue3
vue3 是当前 vue 的默认版本,与 vue2 存在一些差别,但是大部分内容是相同的
vue 现在存在选项式与组合式两种代码风格,vue2 语法多为选项式,而 vue3 则提供一种新的写法,即组合式 API
选项式 API 会导致某一功能的代码较为分散,组合式 API 则能够将同一功能的实现集中起来,会更加类似于 JavaScript
在组合式 API 中,直接通过 const 或者 let 声明的变量都不是响应式的,对其进行修改并不能生效,需要通过相应的函数创建响应式数据
响应式数据-reactive:reactive 函数能够创建响应式数据,但是仅能创建对象、数组、映射、元组等对象类型数据
<!-- Reactive.vue -->
<script>
import { reactive } from 'vue';export default {setup() {const numArray = reactive([1, 2, 3])function pushNumber() {numArray.push(4)}return {numArray,pushNumber}}
}
</script><template><p>{{ numArray }}</p><button @click="pushNumber">push</button>
</template>
在代码中,也能够看出组合式 API 的一种实现方式,即 setup 函数
reactive 函数创建的变量的各个维度都是响应式的,无论维度是深层次还是浅层次,如果不希望如此,可以使用 sallowReactive 函数,该函数仅仅会对数据的浅层次进行响应式处理
响应式数据-ref:ref 函数的使用范围会更加广泛,它对基本类型以及对象类型的数据都适用,但是 ref 创建的变量不能直接修改,而是应该对其 value 属性进行修改
<!-- Ref.vue -->
<script setup>
import { ref } from 'vue'const name = ref("Jack")
const status = ref("active")
const tasks = ref(["task one", "task two", "task three"])const toggleStatus = () => {if (status.value === "active") {status.value = "pending"} else if (status.value === "pending") {status.value = "inactive"} else {status.value = "active"}
}
</script><template><p v-if="status === 'active'">{{ name }} is active</p><p v-else-if="status === 'pending'">{{ name }} is pending</p><p v-else>{{ name }} is inactive</p><h2>list of tasks</h2><ul><li v-for="task in tasks" :key="task">{{ task }}</li></ul><button @click="toggleStatus">change status</button>
</template>
上述代码还给出组合式 API 实现的另一种方式,即 setup 属性,该方式并不需要 return 返回逻辑
只读数据-readonly:readonly 函数创建只读数据,该数据仅读不能进行写操作,所以是非响应式的
<!-- ReadOnly.vue -->
<script setup>
import { readonly } from 'vue';const readData = readonly({name: 'Jack',age: 18,isStudent: true
})
</script><template><p>{{ readData.name }} is {{ readData.age }} years old.</p><p>Is he a student? <br> {{ readData.isStudent }}</p>
</template>
如果希望数据浅层次只读,数据深层次可写,则可以使用类似的 shallowReadonly 函数,该函数对于数据深层次是可写的,但是数据仍然是非响应式的,需要自己手动刷新查看变动
计算属性-computed:与选项是 API 的计算属性类似
<!-- ComputedFun.vue -->
<script setup>
import { computed, ref } from 'vue';const content = ref('test')
const getLen = computed(() => {console.log('getLen run')return content.value.length
})
</script><template>{{ getLen }}{{ getLen }}
</template>
侦听器-watch:和组合式 API 类似,但是会存在一些特殊情况
<!-- Watch.vue -->
<script setup>
import { reactive, ref, watch } from 'vue';const count = ref(0)
watch(count, (newValue, oldValue) => {console.log(oldValue, newValue)
})function addCount() {count.value++
}const student = reactive({name: 'Jack',age: 18,friends: ['Tom', 'Aily']
})
watch(() => student.age, (newValue, oldValue) => {console.log(oldValue, newValue)
})function beOlder() {student.age++
}
</script><template><p>{{ count }}</p><button @click="addCount">add</button><p>{{ student }}</p><button @click="beOlder">old</button>
</template>
侦听器能够直接监听 reactive 和 ref 对象,但是对于对象类型的某一具体属性的监听,需要通过函数的形式实现
watchEffect:类似于 react 框架中的 useEffect,但是 watchEffect 会更加智能,会自动监听使用变量的值是否发生变化
<!-- WatchEffect.vue -->
<script setup>
import { ref, watchEffect } from 'vue';const count = ref(0)
const judge = ref(true)watchEffect(() => {console.log(`count: ${count.value}, judge: ${judge.value}`)
})function addCount() {count.value++
}function toggleJudge() {judge.value = !judge.value
}
</script><template><p>{{ count }}</p><button @click="addCount">add</button><p>{{ judge }}</p><button @click="toggleJudge">toggle</button>
</template>
11vue3状态管理-pinia
在项目构建时,如果没有勾选 pinia,需要通过 npm install pinia
安装并导入
// src/main.js
import './assets/main.css'import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'const app = createApp(App)app.use(router)const pinia = createPinia()
app.use(pinia)app.mount('#app')
然后,在 src/store 文件夹下创建 counter.js 文件
如果在构建时已经勾选 pinia,那么 counter.js 文件内容如下
// src/store/counter.js
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return {count,doubleCount,increment}
})
实际上,在 pinia 中进行状态管理,并没有特别之处,使用的基本语法与 vue3 中的语法几乎相同,ref 会被类比为 vuex 中的 state,computed 会被类比为 getters,pinia 没有 mutations 的概念,而 function 会被类比为 actions + mutations,最后在 pinia 中的模块概念实际就是该 JavaScript 文件,如 counter.js 文件,而 defineStore 就创建了 'counter' 这个模块
<!-- TestPinia.vue -->
<script setup>
import { useCounterStore } from '@/store/counter';
const counterStore = useCounterStore()
</script><template><p>{{ counterStore.count }}</p><p>{{ counterStore.doubleCount }}</p><button @click="counterStore.increment">add</button>
</template>