Vue
作者的重心已经完全偏向React技术栈,本章节的内容可能和最新版本的Vue有部分出入
基础
<div id="counter">
Counter: {{ counter }}
</div>
<script>
const Counter = {
data() {
return {
counter: 0
}
},
computed: { // 计算属性
A() {
return counter * counter
}
},
watch: {
counter(newValue, oldValue) { // 当counter改变触发该函数
}
},
methods: {
fn() {
console.log(this.counter)
}
},
mounted() {
}
}
const app = Vue.createApp(Counter)
app.mount('#counter')
</script>
组件注册
通常使用app.component('name', {})
注册全局组件,或是使用Vue单文件组件。
指令
v-bind
(缩写为:
)、v-on
(缩写为@
)、v-for
、v-model
、v-if
、v-show
等
<!--
post: {
id: 1,
title: 'aka'
}
-->
<blog-post v-bind="post"></blog-post>
<!-- 等价于 -->
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。相比之下,
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此, 如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
生命周期
beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeUnmount
、mounted
组件通信
父子组件通信借助props
和父组件对子组件自定义事件的监听;兄弟组件可以使用事件总线来通信;或者用Vuex。
Props
和React不同,在Vue中我们需要指定组件接收哪些props
属性,当我们给组件传递非props
属性时,该属性会默认挂载在组件的根元素上。
而Vue3模仿Fragment实现了多根节点的组件,此时我们需要显示的定义这些非props
属性应该被挂载在哪个节点。
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
Provider and Inject
简单来说是Vue版本的Context,不过默认情况下Provider/Inject
绑定不是响应式的。
Teleport
简单来说就是Vue版本的Portals,通常的使用场景是Modal。
对于一个Modal遮罩组件,逻辑上弹出的遮罩在组件的内部,但从页面布局的角度来看遮罩应该是全局的(因为绝对定位),所以可以使用Teleport来使组件内部的元素被传送到外部,比如放在body
元素下。
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
}
}
})
组合式API
Todo
感觉有点像React的Hook,在以前的Vue开发一个模块功能的代码被分散在data
、computed
、watch
、computed
、lifecycle
当中,而组合式API使得一个模块相关的代码高度内聚,方便管理。
Vuex
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
countPlus: state => {
return state.count + 1
}
},
mutations: {
increment: (state, payload) => {
state.count += payload
}
}
})
new Vue({
el: '.app',
store,
computed: {
count: function() {
return this.$store.state.count
}
},
methods: {
increment: function() {
this.$store.commit('increment', 10)
}
},
template: `
<div>
{{ count }}
<button @click='increment'>点我</button>
</div>
`
})
mutation 和 action
action类似于mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作,Mutation只能包括同步操作。
Vue Router
<div class="app">
<router-link :to="/login"></router-link>
<router-link :to="/logout"></router-link>
<router-view></router-view>
</div>
const routes = [
{
path: '/login',
component: { template: '<login></login>' }
},
{
path: '/logout',
component: { template: '<logout></logout>' }
},
]
const router = new VueRouter({
routes
})
const vm = new Vue({
el: '.app',
router
})
动态路由匹配
如同/user/:id,/user/akara和/user/messiah都可以匹配到这个路由
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
嵌套路由
简单来说就是router-view里面还有router-view
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
路由导航
router-link
提供了声明式导航,我们也可以使用this.$router.push
进行函数式导航
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
原理
使用Vue能够让我们快速搭建单页应用,一次性拿到完整的页面,之后可以借助Vue-Router来根据不同路由渲染不同的组件。
Vue-Router有两种模式,Hash
模式和History
模式,其中Hash
模式的Url结构类似:https://example.com/#user/akara
;History
模式 的Url结构类似:https://example.com/user/akara
。
对于Hash
模式而言,我们用a
标签即可轻松实现路径的切换,并且单纯改变哈希值不会触发页面的刷新,也就不存在任何的副作用。接下来我们只需要使用onhashchange
监听哈希的变化,从而实现组件的切换即可。
对于History
模式而言,我们不能使用a
标签的原生行为来实现路径的切换,这样会导致页面的刷新。所以我们会手动阻止a
标签的默认行为,当点击时使用history.pushState
方法给浏览器的浏览记录中添加一个新的记录,同时手动执行组件的更新。但这还不算完,如果此时用户点击浏览器的回退或前进按钮,我们就会重新发请求导致页面的刷新,为了避免这样的行为我们会监听浏览器的popstate
事件(用户点击回退或前进按钮会触发popstate
事件),并在该事件的回调函数中手动执行组件的更新。
// 使用history模式
const router = new VueRouter({
mode: 'history',
routes: [...]
})
不过使用History模式需要后端进行配置,如果不配置,当用户访问 https://example.com/user/akara
的时候会返回404。所以我们需要设置当URL匹配不到任何资源的时候,同返回同一个index.html
。