Vue#
1. 初识 Vue#
### 命令式与声明式
- 命令式
每一步都直接使用js原生实现
- 声明式
先列举要做什么,在编译过程再自动生成逻辑
tip. 牺牲性能,提升编程体验。
### 虚拟DOM
- 虚拟DOM是真实DOM的一个JS对象抽象。其中虚拟DOM即JS对象。
- 真实DOM的属性众多,直接操作DOM,对浏览器的检索压力太大,性能不足。因此使用虚拟DOM,再通过虚拟DOM的变化(diff)去映射操作真实DOM,以提高性能。
- 在Vue中,虚拟DOM的组成部分就是VNode(js对象),每一个VNode拥有children - VNode数组,这样就构成一个树形结构,这便是虚拟DOM树。
- VNode.elm 指向了真实 Node, VNode.context 指向vue实例
### 运行时与编译时
- 运行时
- 使用render渲染函数将虚拟DOM直接转换为真实DOM
- 运行时 有一定性能支出
- 虚拟DOM与真实DOM结构类似,是直接的映射关系,无法分析用户提供的内容。
- 编译时
- 解析Template模板内容,转化为真实DOM
- 编译过程可以读取模板语法,可以分析用户内容。
- 性能较佳
- 运行时+编译时 Vue
- 利用template模板,生成render函数
- 使用render函数将虚拟DOM转化为真实DOM
### Vue UI
- 模板描述 声明式
- render函数 命令式 render函数(h函数)返回值是一个虚拟DOM(VNode)
### 初始渲染器
- renderer 对象 使用函数 createRenderer 生成
- 参数: 虚拟DOM对象(VNode) 挂载容器(真实DOM元素)
- renderer 作用是将虚拟DOM挂载在真实DOM元素上
### 区分
- render是vue提供给用户,操作虚拟DOM的途径
- renderer 是vue将虚拟DOM转化为真实DOM的工具
2. 响应系统#
### 副作用函数 与 响应式数据
1. 拥有一个全局变量 A,它被函数innerText与视图关联。
2. 当我们修改了变量A,关联元素并不会更新
3. 只有再一次执行 innerText,视图才会更新
- 我们称 innerText 就是 副作用函数。
- 这个涉及视图变化的变量A 就是响应式数据
### 核心逻辑
- 必须有 setter getter / 访问器
- 类似高级语言中的 访问器,虚拟DOM对象 以及 数据对象 都可以使用访问器,来实现类似高级语言中的数据绑定模式。
### 基本原理
- getter: 响应式数据
当触发读取操作,则将该副作用函数列入“名单”
- setter: 响应式数据
当触发设置操作,再次执行副作用函数以更新视图
### 实现原理
- Vue2 中 使用 Object.defineProperty()
- Vue3 中 使用 Proxy() Reflect
### 响应性调度
- 当响应数据发生变化,副作用函数”名单“被执行,那么控制”名单“的执行顺序,就很重要。
- 响应性调度的关键在于 异步编程Promise 队列模型jobQueue
- **惰性执行**
- **计算属性** 也依赖于调度系统
- watch 监听器,当监听对象发生变化,执行回调。
- 当监听器的回调中有异步操作,则可能导致执行结果不符合预期,使用 onInvalidate() 停止过期的异步请求。
### 实现
- 对象的响应性 Proxy + Reflect
- 非对象的响应性(基本类型) ref
3. 渲染#
4. 组件原理#
5. 编译器#
### DSL 编译器
- 特定语言编译器
- 本质是 将模板 编译为 render 渲染函数
1. parse 模板 -> AST对象
2. transform AST -> JavascriptAST
3. generate JsAST -> render函数
Vue - 基础#
模板语法#
- 在标签中插入响应式文本,使用
{{ msg }}
- 为
属性
设定响应式数据,使用v-bind:id=""
或使用简写:
- 若
2
中绑定的数据是布尔类型,则 vue 会处理该属性存在或不存在。 - 使用不带参数的
v-bind=""
语法,可以将带有多个属性的 json 对象绑定在标签上。 - Vue 的所有数据绑定都支持 js 表达式
- 可以使用一个组件暴露的方法,该方法会在组件每次更新时调用,因此不应产生任何副作用,例如改变数据和异步操作。
- ** 模版中的表达式只能访问到有限的全局对象列表,该列表可以在
app.config.globalProperties
中显示添加。
## 指令
- 是指带有·v-·前缀的特殊属性,
- 指令的期望值是 js表达式,用来在表达式发生变化时响应更新DOM
- `v-bind:href="xxx.api"`后可以带 指令参数
- `:[attributeObj]="xxx.api"` 参数可以时动态的,但表达式需要用方括号包围
- ** HTML 中的字符会自动处理为小写,且不可以为空格 **
- ** 推荐使用 计算属性**
响应式基础#
** 使用组合式API **
创建一个响应式对象或数组#
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Vue3 使用 Javascript Proxy 来监听响应式对象。
使用 setup
函数定义返回响应式对象,供给模板使用
import { reactive } from 'vue'
// 组件的默认行为
export default {
// setup 用于组合式 API 的钩子函数
setup(){
// 定义 响应式对象
const state = reactive({ count: 0 })
// 定义 方法
function increment(){
state.count++
// 全局API 更新DOM的下一个Tick
nextTick(()=>{
// xxx
})
}
// 暴露 state 到模板
return {state, increment}
}
}
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
使用 <script setup>
DOM 更新#
vue 会维护一个 DOM 更新序列,因此,数据的修改和 DOM 的更新并不严格同步。
若要保证更新完成,可以使用 nextTick()
深层响应性#
Vue 中所有状态默认为深层响应式,无论多深层次的对象或数组,改动都能被检测到。
代理#
使用 reactive()
处理后的对象,返回的是其原始对象的代理。
改变原始对象并不会出发响应性。
对同一个对象多次调用 reactive()
函数,会返回同一个代理
对代理调用 reactive()
函数,则会返回代理本身
⚠️注意
-
reactive()
只对 json 对象生效,对原始类型无效。 -
对响应式对象(代理)不应随意替换(赋新值),或赋值 / 解构到本地变量(该变量不会有响应性),或作为参数传入函数(不会具有响应性,等价于解构)
使用 ref()
#
ref(1)
创建了一个对象的响应式代理,该对象具有一个value
属性,即为被引用的原始类型变量。
ref()
也可以接受一个对象,此时,对该引用的value
属性访问,会自动获取到 reactive()
的结果。
ref()
在模版中作为顶层属性访问时,会自动解包,无需.value
<script setup>
import {ref} from 'vue'
const count = ref(0)
function increment(){
count.value++
}
const obj = { foo: ref(1) }
</script>
<template>
<button @click="increment">
{{ count }}
</button>
<button>
{{ obj.foo + 1 }} <!--[object:object]1 -->
</button>
</template>
响应性计算属性#
- 示例 和使用说明
<script setup>
import { reactive, d } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2' - 'Advanced Guide',
'Vue 3' - 'Advanced Guide',
'Vue 4' - 'Advanced Guide',
]
})
// 响应式计算属性
const publishedBooksMessage = computed(()=>{
return author.books.length > 0 ? 'Yes':'No'
})
</script>
<template>
<p>
Has Publiched books:
</p>
<span>{{ author.books.length > 0? 'Yes':'No'}}</span>
<span>{{ publishedBooksMessage }}</span>
</template>
comupted()
期望接受一个 getter 函数,返回一个 计算属性 ref,Vue 会自动追踪表达式依赖,任何变化都会实时更新。
-
计算属性 Vs 函数
计算属性会根据响应式依赖被缓存,而函数在组件重新渲染时被执行。
-
可写计算属性
<script setup> import {ref, computed} from 'vue' const firstName = ref('John') const lastName = ref('Doe') const fullName = computed({ get(){ return firstName.value + ' ' + lastName.value }, set(newValue){ [firstName.value, lastName.value] = newValue.split(' ') } }) </script>
-
⚠️注意
- Getter 不应有任何副作用,即异步请求或更改 DOM
- 计算属性返回的是一个 “临时快照”, 当源发生变化时,新的快照将会生成。更改计算结果时没有意义的,计算属性应该视为只读。
Class 和 Style 绑定#
绑定 Class#
<script setup>
import { reactive, ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
const classObject = reactive({
active: true,
'text-danger': false
})
const classComput = computed(()=>({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type ==='fatal'
}))
</script>
<template>
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"> 绑定ref元素 </div>
<div :class="classObject"> 绑定对象 </div>
<div :class="classComput"> 绑定计算属性 </div>
<div :class="[isActive ? activeClass : '', errorClass]"> 绑定对象数组 </div>
</template>
绑定 Style 样式#
<script setup>
import { reactive, ref } from 'vue'
const activeColor = ref('red')
const fontSize = ref(30)
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px'}"> 驼峰命名的style元素 </div>
<div :style="{ 'font-size': fontSize + 'px' }"> 短横风格的实际style-key名称 </div>
<div :style="styleObject"> 绑定对象 </div>
<div :style="[baseStyles, overridingStyles]"> 绑定数组 </div>
</template>
Vue 会根据浏览器支持情况,自动添加样式前缀,在一个属性提供多个值时,会自动选择适合当前环境的值。
条件渲染#
<script setup>
import { ref } from 'vue'
const awesome = ref(true)
</script>
<template>
<button @click="awesome = !awesome">toggle</button>
<template v-if="awesome">
<h1>Vue is awesome!</h1>
<h1>Oh no 😢</h1>
</template>
</template>
条件渲染 v-if 、v-else-if、v-else,支持在 template 标签上使用。
v-show 会有类似效果,但是不支持在 template 标签上使用。
v-show 只会改变 display 属性,元素依然会存在 DOM 中。
v-if 有更高的切换开销,v-show 有更高的初始渲染开销。
⚠️ v-if 和 v-for 同时存在时,优先执行 v-if。不推荐。
循环渲染#
<script setup>
import { reactive, ref } from 'vue'
const items = ref([{message: 'foo'}, {message: 'bar'}])
const object = reactive({
title: "how to do lists in Vue",
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
</script>
<!-- v-for 在模板中可用 -->
<template v-for="it in 1">
<li v-for="n in 10">{{n}}</li>
<!-- 重复元素中,可以完整访问父作用域的属性和变量,item则只在内部 -->
<li v-for="item in items">{{item.message}}</li>
<!-- 遍历对象,返回对象所有属性,顺序基于 Object.keys -->
<li v-for="value in object">{{value}}</li>
<!-- 遍历对象,返回两/三个参数 -->
<li v-for="(value, key, index) in object">{{key}}:{{value}}</li>
</template>
⚠️ Vue 默认 就地更新 以提高性能。此时,列表的顺序的改变不会改变渲染顺序。
通过为循环元素添加 :key
属性,可以改变此行为。
⚠️ Vue 会侦测操作数组函数,来响应数组的变化。但一些函数不会改变原数组,而是返回新数组,此时 Vue 不会侦测到变化。
事件处理#
-
內联事件处理器
事件被触发时,执行的內联 Javascript 语句
-
方法事件处理器
一个指向组件上定义的方法的属性名或路径
** 內联事件处理 **
<script setup>
// ...
const count = ref(0)
</script>
<template>
<button @click="count++">
Add 1
</button>
<p>
Count is: {{ count }}
</p>
</template>
** 方法事件处理 **
<script setup>
const name = ref('world!')
function greet(event){
alert('Hello ${name.value}')
// event是DOM原生事件
if(even){
// 使用 event.target 获取原生事件对象
alert(event.target.tagName)
}
}
</script>
<template>
<!-- greet -->
<button @click="greet">
Greet
</button>
</template>
⚠️ 搞不清楚內联和方法事件的区别。
可以使用 $event
传递原生 DOM 事件
- 事件修饰符
.stop
.prevent
.self
.capture
.once
.passive
- 按键修饰符
- 系统按键修饰符
.exact
控制按键组合,多余按键组合不会触发
- 鼠标按键修饰符
表单处理#
** 输入组件的数据绑定很麻烦 **
<input :value='text'
@input="event => text=event.target.value">
使用v-model
简化#
<input v-model="text">
⚠️ v-model 会自动处理不同 html 标签的键入属性。
⚠️ v-model 会忽略拼字阶段的键入,如果要取消忽略行为,请使用 input 事件处理方法
复选框的单一绑定和数组绑定#
<!-- ☑️ true 🔲 false -->
<!-- :true-value :false-value -->
<input type='checkbox' v-model="checked"/>
<label for="chekcbox">{{ checked }}</label>
<!-- 选中的项目的value会被添加到数组中 -->
<script setup>
const checkNames = ref([])
</script>
<template>
<div>
Checked Names: {{ checkNames }}
</div>
<input type="checkbox" value="Jack" v-model="checkNames">
<label for="jack">Jack</label>
<input type="checkbox" value="Tom" v-model="checkNames">
<label for="jack">Jack</label>
<input type="checkbox" value="Mike" v-model="checkNames">
<label for="jack">Jack</label>
</template>
单选按钮组#
<div>
Picked: {{picked}}
</div>
<input type='radio' value="One" v-model="picked"/>
<input type='radio' value="Two" v-model="picked"/>
<!-- vue使用Value值来区别不同按钮 -->
选择器#
<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>
⚠️ 绑定到一个数组,则为多选
💡 使用 v-for 循环渲染选项更方便
- 上述三种组件,可以使用
v-bind
绑定动态数据 - 修饰符
.lazy
键入触发改为 change 触发.number
自动转换数字,当type='number'
时会自动启用.trim
自动去除两端空格
组件的生命周期钩子函数#
在组件的生命周期各个阶段,用户可以通过定义钩子函数做一些特定行为。
侦听器 Watch#
基本用法#
<script setup>
import {ref, watch} from 'vue'
const question = ref('')
const answer = ref('Questions usually contain ...')
const myobj = reactive({ count: 0 })
watch(question, async(newvalue, oldvalue)=>{...})
// 注意,对于一个响应式对象,不可以监听其成员,因为得到的是一个值
// 应该使用 Getter 函数
watch(()=>myobj.count,(count)=>{ ... })
</script>
深层侦听#
监听一个响应式对象时(watch 函数第一参数为对象),自动启用深层监听,对象的任何成员更新,都会触发动作。但由于监听对象依旧是同一个对象,因此回调函数的 newva, oldval
依旧没变。
监听一个对象的 getter,则只有对象改变,才会触发。
<script setup>
import { reactive } from 'vue'
const obj = reactive({ count:0 })
watch(obj,()=>{
// 自动启用深层监听
})
watch(()=>obj,(newv,oldv)=>{
// 只有返回不同的obj对象,才会触发。
},{deep:true/* 强制转为深层监听 */})
</script>
即时执行#
侦听器默认只有数据源发生变化时,才会触发。
若想在创建完成后立刻触发一次,可以传入 immediate: true
参数
watchEffect()#
使用此函数可以无需提供响应式数据源,而是由 vue 自动根据回调函数的依赖情况,进行响应式触发,此外,该函数默认带有immediate:true
属性。
<script setup>
const todoId = ref(1)
const data = ref(null)
watch(todoId, async()=>{
const response = await fetch(
'https://xxx.xxx.com/api/todos/${todoId.value}'
)
data.value = await response.json()
},{immediate:true})
// 等价于
watchEffect(async()=>{
// ...
})
</script>
😄 当回调依赖数据众多时,watchEffect 的作用就非常明显了。此外,该函数对于对象属性的侦听,是表层的,非深层的。
⚠️ 在响应式更新中,用户监听回调默认在 DOM 更新之前,因此在回调期间访问的 DOM 并不是最新的。若要改变此行为,附加 flush:'post'
属性。或使用后置刷新监听函数watchPostEffect()
⚠️ 通常在 <script setup>
中创建的侦听器,会随着组件的创建自动绑定到组件,并随着组件的卸载自动停止。但当使用异步语句创建的侦听器,需要手动停止,以防止内存泄漏。使用watch/watchEffect的返回值停止一个侦听器。
模板引用#
模板引用可以帮助你获取到底层 DOM 元素
访问模板引用#
<script setup>
import {ref, onMounted} from 'vue'
// 声明一个引用 ref 来存放模板引用
const input = ref(null)
onMounted(()=>{
input.value.focus()
})
</script>
<template>
<!-- ref 名称必须和 脚本中的变量名相同。 -->
<input ref="input"/>
</template>
⚠️ 组件挂载后,模板引用才会有值。初次渲染时,引用时 null。若要侦听一个模板引用,必须考虑其为 null 的情况。
v-for 模板引用#
请声明一个数组的引用类型,以存放整个列表的元素。
⚠️ ref 数组不能保证于原数组相同顺序。
函数模版引用#
<input :ref="(el)=>{ ... }"/>
函数模板会在组件更新以及销毁时触发。销毁时el===null
组件#
定义组件#
-
使用构建步骤时,Vue 组件定义在一个
.vue
文件中,称为单文件组件 SFC<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++"> You clicked me {{ count }} tiems. </button> </template>
-
不使用构建步骤,一个 Vue 组件用一个特定的 Javascript 对象表示。
<script> import {ref} from 'vue' export default { setup() { const count = ref(0) return { count } }, template: ' <button @click="count++"> You clicked me {{count}} times. </button>' } </script>
使用组件#
通过<script setup>
,导入的组件可以在模板中直接使用。
全局组册组件可以使组件在整个 VueApp 中可用
使用组件只需创建组件名同名的标签即可。
⚠️ 在 vue 文件中,组件的标签可以区分大小写,但若直接在 html 文件中便携模板,则遵从浏览器解析规则。
Props#
prop 相当于组件的 “参数”。在调用组件的时候,可以根据传入的 prop,显示不同的内容。
使用defineProps
宏来定义组件的 Props
组合式
<script setup>
defineProps(['title'])
</script>
<template>
<h4>
{{ title }}
</h4>
</template>
选项式
export default {
props: ['title'],
// 必须将props作为参数传入setup
setup(props) {
console.log(props.title)
}
}
监听事件#
要想子组件触发事件,被父组件接收,需要:
- 使用宏
defineEmits(['enlarge-text'])
声明事件列表 - 在组件中书写同名的事件触发
@click="$emit('enlarge-text')"
- 在父组件中书写事件触发回调
@enlarge-text=" ... "
与 props 类似,使用选项式声明事件列表,且setup
函数第二个参数 ctx 上下文可以访问到ctx.emit
事件列表
插槽#
插槽可以使我们的自定义组件,像普通 html 组件一样,传递内容。
<script setup>
import AlertBox from '/alertbox.vue'
</script>
<template>
<AlertBox>
The Slot Message.
</AlertBox>
</template>
<!-- 这里是 alertbox.vue 子组件 -->
<template>
<slot/>
</template>
动态组件 - 实现组件切换#
#DOM 解析⚠️#
Vue 高级#
组件注册#
全局注册#
<!-- App.vue -->
<script>
import { createApp } from 'vue'
import MyComponent from './mycomponent.vue'
const app = createApp({})
// 1. 直接注册
app.component('componentName', {/* 组件实现 */})
// 2. 单文件注册
app.component('componentName', MyComponent)
// 3. 可链式调用
app
.component('componentName', MyComponent)
.component('componentName', MyComponent)
.component('componentName', MyComponent)
</script>
局部注册#
- 在
<script setup>
标签中导入的组件可以直接使用 - 使用选项式语法,需要显式在
components:{ nameA, nameB }
中注册
组件名格式#
- PascalCase 是建议的组件名格式,在单文件组件中,此名称也可以很好的于普通 html 标签区别。
- 在 DOM 模板中,请使用对应的 kebab-case 名称。
- vue 会自动处理两种格式和使用场景的转化。
Prop#
声明 Prop#
- 在
<script setup>
中使用defineProps(['prop1,prop2'])
方式 - 在选项式中使用
props: ['prop1','prop2]
方式 - 以 javascript 对象的方式带上类型名
<script setup>
defineProps({
title: String,
likes: Number
})
export default {
props: {
title: String,
likes: Number
}
}
</script>
书写格式#
- 使用 camelCase 命名 Prop 可以直接在模板中不带引号的使用属性。
- 在模板中,请使用 kebab-case 格式,与 DOM 书写风格形成统一。
- vue 会自动处理不同的写法的对应。
Vuex#
** 一种存储全局状态的仓库 Store。而且其中的状态都是响应式的。 **
** 其中存储的所有状态 state 都不能直接改变,只能通过 提交 commit 改变 **
示例#
import { createApp } from 'vue'
import { createStore } from 'vuex'
const store = createStore({
state(){
return {
count: 0
}
},
mutations: {
increment(state){
state.count++
}
}
})
const app = createApp({})
app.use(store)
// 使用 store.commit('name') 触发状态变更
// 使用 store.state 获取对象
// 在所有VUE组件中,使用 this.$store 访问store实例 (选项式)
// 在setup中导入useStore,并使用其访问store。 (组合式)
概念#
-
localStorage
- 是 H5 支持的本地存储的特性,解决了 cookie 空间不足的问题。
- 只能存储字符串的数据。
.getItem('key')
.removeItem('key')
.clear()
.key()
-
State
- 相当于一个集中的,全局的响应式数据库,状态 State 是这些响应式变量的名字。
- 在组件中调用状态时,为了保持响应性,应该使用
箭头函数
的方式访问。 - 使用
mapState()
可以返回多个 State,同时也可以以函数的方式将 State 计算后返回。在函数内部,可以使用this
访问到局部组件的变量 - 使用 ** 对象展开运算符
...mapState
可以将函数返回的对象展开混入到上一级属性中。
-
Getter
-
Mutation
-
是更改 state 的唯一途径。通过
commit
mutation 方法,改变 state -
那么 Mutation 中是一个包含多个函数的对象,每个函数中有 state 的不同的更改操作。
-
Mutation 函数的第一个参数是 state。
-
⚠️mutation 中定义的函数只能通过 commit 方法触发,commit 方法第一个参数是 mutation 函数名,之后的参数可以作为参数传递给 mutation 函数。在函数定义时,第一个参数通常是 State,之后的参数就是其他参数,与 commit 的第二之后的参数对应。
-
提交方式可以是对象。将第二参数写成 json 对象的形式,那么整个对象都将作为载荷提交给 mutation 函数。使用对象风格时,可以将函数名作为 type 属性加入到 json 对象中,整体提交。
-
Mutation 必须时同步函数
-
⚠️ 使用常量代替 Mutation 事件名是一种开发模式
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import { createStore } from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = createStore({ state:{...}, mutations: { [SOME_MUTATION](state){...} } })
-
⚠️
mapMutation
是改进代码阅读性的方法,与直接调用无区别import { mapMutations } from 'vuex' export default { methods: { ...mapMutations([ 'increment', // => 'increment'=$store.commit('increment') add: 'increment', ]) } }
-
-
Action
- 可以处理异步函数的 Mutation
-
Module
- 将 store 分割成不同的模块,在使用
createStore({modules:{}})
创建 store
- 将 store 分割成不同的模块,在使用
Vue Router#
** 简单案例 **
<p>
<router-link to="/">GO to home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<router-view>路由出口</router-view>
使用 router 标签代替 html 的超链接标签,将跳转交给 router。
使用 router-view 标签接收路由处理结果。可以显示组件或其他动作。
// 1. 示例组件
const Home = {template: '<div>Home</div>'}
const About = {template: '<div>About</div>'}
// 2. 定义路由对象
// 每一个路由对象都包含一个路径,以及组件
const routes = [
{path:'/', component: Home},
{path:'/about', component: About},
]
// 3. 创建路由实例,设置路由配置。
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes, // => routes: routes
})
// 将路由挂载到 vueApp
const app = Vue.createApp({})
app.use(router)
app.mount("#app")
router 已经启用。在组件内可以this.$router
访问当前路由。
在setup
函数中,使用useRouter() useRoute()
函数获取 router 实例。
动态路由匹配#
场景:当显示用户主页时,需要动态字段传递用户 ID
- 使用
:id
冒号 + 路径参数 - 此时路径参数将保存在
this.$route.params
中 - 此外,
$route.query
会保存 URL 中的参数(?参数)
路由参数响应#
场景:当从/users/a
-> /users/b
时,相同的组件将会被复用,提高性能,但组件将不会重新构建,相关函数也不会重新调用。
此时页面需要对路由参数的变化做出响应。
- 使用
watch
侦听this.$route.params
- 使用
async beforeRouteUpdate(to, from)
导航守卫
正则表达式#
常规的路由参数只会匹配 /
之间的字符。
如果要跳出分隔符的限制,可以使用自定义参数正则
const routes = [
{path:'/:pathMatch(.*)*'}
]
路由参数后的(...)
是正则表达式,符合表达式的字符串被赋给参数pathMatch
,后面的*
表示可选可重复
高级匹配#
...
路由匹配语法#
通常,使用静态路由 /about
以及一个参数来结尾的动态路由 /users/:userId
即可满足业务需求。
Vue-router 额外的支持
参数自定义正则#
场景: 下面两个路由会匹配相同的 URL /:orderId
/:productName
为了做区分,有以下方法
-
前面增加静态部分
const routes = [ {path: '/o/:orderId'}, {path: '/p/:productName'} ]
-
使用正则表达式修改参数匹配规则
const routes = [ // 注意转义字符 纯数字的结尾被匹配到 orderid {path: '/:orderId(\\d+)'}, {path: '/:productName'}, ]
可重复参数#
*
表示可匹配 0 个或多个路由(/ 包围的段)+
表示可匹配 1 个或多个路由?
表示可匹配 0 个或 1 个路由- 此时参数接收到的不再是字符串,而是一个数组
- 可以与正则表达式一起使用
配置大小写敏感,以及严格模式#
默认情况下,/users, /users/, /Users/
是同一路由。
严格模式将排除情况 2,敏感模式将排除 3
const router = createRouter({
// ...
routes: [/* ... */],
strict: true,
sensitive: true,
})
命名路由#
允许为路由对象 增加 name
属性,来使用 自动构建 URL 的特性
命名路由可以防止手动输入 URL 出错。
<template>
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
User
</router-link>
</template>
<script>
router.push({ name: 'user', params: { username: 'erina' } })
</script>
嵌套路由#
- 当子组件中有
<router-view>
标签时,可以设置嵌套路由。 - 即该路由设置一个
children:
属性,包含路由数组。 - 可为嵌套路由设置 空路径 路由对象,使其依然可以为父路由路径渲染合适的内容。
编程路由#
除了在 模板 中使用 <router-link>
标签,还可以在 script
中通过访问 this.$router
实例,来实现导航。
导航#
使用方法 router.push
为 history 栈添加一个新的记录。
<router-link :to:"...">
等价于 router.push()
-
push 方法的几种用法
// 字符串路径 router.push('/users/eduardo') // 带有路径的对象 router.push({ path: '/users/eduardo' }) // 命名的路由,并加上参数,让路由建立 url router.push({ name: 'user', params: { username: 'eduardo' } }) // 带查询参数,结果是 /register?plan=private router.push({ path: '/register', query: { plan: 'private' } }) // 带 hash,结果是 /about#team router.push({ path: '/about', hash: '#team' })
-
⚠️ path 参数 会使 params 被忽略,所以请使用 name 命名路由。
const username = 'eduardo' // 我们可以手动建立 url,但我们必须自己处理编码 router.push(`/user/${username}`) // -> /user/eduardo // 同样 router.push({ path: `/user/${username}` }) // -> /user/eduardo // 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益 router.push({ name: 'user', params: { username } }) // -> /user/eduardo // `params` 不能与 `path` 一起使用 router.push({ path: '/user', params: { username } }) // -> /user
-
⚠️ params 接收 string 或 number(可重复参数接收数组),undefined false 等将被自动序列化。可选参数可用 “” 跳过
-
router.push
返回一个Promise
替换当前位置#
动作类似于 push,只是不会向 history 中添加记录。
<router-link :to="..." replace>
等价 router.replace()
等价 router.push({path:'...', replace: true})
横跨历史#
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与 router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
命名视图#
场景:目前为止,单个路由的 Component 只有一个。若我们的路由目标需要将多个组件放置到不同位置,则需要多个路由出口,以及多个组件。
使用命名视图,并在路由对象中为不同的视图指定不同的组件。
<template>
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
</template>
<script></script>
SCSS#
- 需要编译器编译成 CSS 文件才能被浏览器识别。弥补了 CSS 文件重复性的问题,允许程序员定义变量、函数等。
变量#
- 变量以
$
开头,且下划线和短横线不作区分。 - 变量根据定义位置不同,有不同作用域。
- 多次定义同一个变量,相当于赋值操作,会覆盖。
选择器#
-
嵌套选择器
-
父级选择器
&
-
嵌套组合选择器
, > + ~
.container ul{ border: 1px solid #aaa; list-style: none; li{ float:left; >a{ display: inline-block; padding: 6px 12px; } } &:after { display: block; content: ""; clear: both; } }
-
嵌套属性
li{ border: 1px solid #aaa { left: 0; right: 0; } }
导入文件#
@import App2.scss
导入 sass 文件- 使用
!default
变量后缀,声明默认值,即不会替换同名变量 - 导入行为相当于将文件插入 import 位置。
- 可以局部导入 sass 内容。
@import 'app2.css'
使用 css 原生导入。
注释#
/* */
会被保留到 css 文件中/ /
不会保留
混合器(函数)#
-
使用
@mixin
声明一个混合器@mixin get-border-radius($border-radius, $color:red){ -moz-border-radius: $border-radius; -webkit-border-radius: $border-radius; border-radius: $border-radius; color: $color; }
-
使用
@include
使用混合器 -
混合器可以使用一切 scss 代码
继承#
-
%
开头定义一个被继承样式 -
使用
@extend
完成继承 -
继承于混合器的不同在于,可以继承已有的 css 规则
.container { @extend %border-style; color: red; } .container1 { @extend .container; }
计算#
- scss 可以使用标准算数运算
+ - * / %
Axios#
一个 Promise 风格的 http 请求包。可以在 服务端 node.js 环境中使用,也可以在浏览器中使用。
简易实用#
最简单的使用 Axios 的方法是在 javascript 中调用方法:
axios({method:'post',url:'xxx',data{...}}).then(function)
axios.request(config)
axios.get(urlp[,config])
axios.delete(url[,config])
- ...
Axios 实例#
使用 axios.create({config})
创建一个实例。
实例方法包括:
- Axios#request(config)
- Axios#get(url[,config])
- axios#delete(url[,config])
- axios#head(url[,config])
- Axios#options(url[,config])
- Axios#post(url[,config])
- Axios#put(url[,data[,config]])
- Axios#patch(url[,data[,config]])
- Axios#getUri([config])
请求配置#
只有 URL 是必须的,method 默认为 get
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // 默认值
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
// 你可以修改请求头。
transformRequest: [function (data, headers) {
// 对发送的 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对接收的 data 进行任意转换处理
return data;
}],
// 自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是与请求一起发送的 URL 参数
// 必须是一个简单对象或 URLSearchParams 对象
params: {
ID: 12345
},
// `paramsSerializer`是可选方法,主要用于序列化`params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求体被发送的数据
// 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
// 在没有设置 `transformRequest` 时,则必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属: FormData, File, Blob
// - Node 专属: Stream, Buffer
data: {
firstName: 'Fred'
},
// 发送请求体数据的可选语法
// 请求方式 post
// 只有 value 会被发送,key 则不会
data: 'Country=Brasil&City=Belo Horizonte',
// `timeout` 指定请求超时的毫秒数。
// 如果请求时间超过 `timeout` 的值,则请求会被中断
timeout: 1000, // 默认值是 `0` (永不超时)
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,这使测试更加容易。
// 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
adapter: function (config) {
/* ... */
},
// `auth` HTTP Basic Auth
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示浏览器将要响应的数据类型
// 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 浏览器专属:'blob'
responseType: 'json', // 默认值
// `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
// 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // 默认值
// `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
xsrfCookieName: 'XSRF-TOKEN', // 默认值
// `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
// `onUploadProgress` 允许为上传处理进度事件
// 浏览器专属
onUploadProgress: function (progressEvent) {
// 处理原生进度事件
},
// `onDownloadProgress` 允许为下载处理进度事件
// 浏览器专属
onDownloadProgress: function (progressEvent) {
// 处理原生进度事件
},
// `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
maxContentLength: 2000,
// `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
maxBodyLength: 2000,
// `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
// 则promise 将会 resolved,否则是 rejected。
validateStatus: function (status) {
return status >= 200 && status < 300; // 默认值
},
// `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
// 如果设置为0,则不会进行重定向
maxRedirects: 5, // 默认值
// `socketPath` 定义了在node.js中使用的UNIX套接字。
// e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
// 只能指定 `socketPath` 或 `proxy` 。
// 若都指定,这使用 `socketPath` 。
socketPath: null, // default
// `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// `proxy` 定义了代理服务器的主机名,端口和协议。
// 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
// 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
// `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
// 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
// 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// see https://axios-http.com/zh/docs/cancellation
cancelToken: new CancelToken(function (cancel) {
}),
// `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
decompress: true // 默认值
}
响应结构#
{
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 是服务器响应头
// 所有的 header 名称都是小写,而且可以使用方括号语法访问
// 例如: `response.headers['content-type']`
headers: {},
// `config` 是 `axios` 请求的配置信息
config: {},
// `request` 是生成此响应的请求
// 在node.js中它是最后一个ClientRequest实例 (in redirects),
// 在浏览器中则是 XMLHttpRequest 实例
request: {}
}
配置#
-
全局配置
axios.defaults.baseURL = ''; axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; axios.defaults.headers.post['Content-TYpe'] = 'application/x-www-form-urlencoded';
-
自定义默认值
// 创建实例后配置该实例默认值 const instance = axios.create({ baseURL: '' }); // 也可以修改默认值 instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
拦截器‼️#
拦截器可以在请求或响应在被 then 和 catch 处理前拦截。
// 请求拦截 在发送请求前进行处理
axios.interceptors.request.use(function(config){
// 发送前处理
return config;
}, function(error){
// 请求错误处理
return Promise.reject(error);
});
// 响应拦截
axios.interceptors.response.use(function(response){
// 2xx 状态码时
return response;
}, function(error){
// 超出 2xx
return Promise.reject(error);
})
拦截器可以移除
const myInterceptor = axios.interceptors.request.use(...)
axios.interceptors.request.eject(myInterceptor)
实例也可以添加拦截器
const instance = axios.create()
instance.interceptors.request.use()
错误处理#
当使用 catch 处理返回值时,返回对象会存储到 error.response...
中
设置 validateStates
函数可以配置错误状态码
使用 error.toJSON()
可以获取更多信息
取消请求#
-
AbortController
const controller = new AbortController(); axios.get(); // 取消请求 controller.abort();