banner
Chenh

Chenh

Vue

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 - 基础#

模板语法#

  1. 标签中插入响应式文本,使用{{ msg }}
  2. 属性设定响应式数据,使用 v-bind:id="" 或使用简写 :
  3. 2中绑定的数据是布尔类型,则 vue 会处理该属性存在或不存在。
  4. 使用不带参数的v-bind=""语法,可以将带有多个属性的 json 对象绑定在标签上。
  5. Vue 的所有数据绑定都支持 js 表达式
  6. 可以使用一个组件暴露的方法,该方法会在组件每次更新时调用,因此不应产生任何副作用,例如改变数据和异步操作。
  7. ** 模版中的表达式只能访问到有限的全局对象列表,该列表可以在 app.config.globalProperties中显示添加。
## 指令
- 是指带有·v-·前缀的特殊属性,
- 指令的期望值是 js表达式,用来在表达式发生变化时响应更新DOM
- `v-bind:href="xxx.api"`后可以带 指令参数
- `:[attributeObj]="xxx.api"` 参数可以时动态的,但表达式需要用方括号包围
- ** HTML 中的字符会自动处理为小写,且不可以为空格 **
- ** 推荐使用 计算属性**

directive.69c37117

响应式基础#

** 使用组合式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() 函数,则会返回代理本身

⚠️注意

  1. reactive()只对 json 对象生效,对原始类型无效。

  2. 对响应式对象(代理)不应随意替换(赋新值),或赋值 / 解构到本地变量(该变量不会有响应性),或作为参数传入函数(不会具有响应性,等价于解构)

使用 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>
    
  • ⚠️注意

    1. Getter 不应有任何副作用,即异步请求或更改 DOM
    2. 计算属性返回的是一个 “临时快照”, 当源发生变化时,新的快照将会生成。更改计算结果时没有意义的,计算属性应该视为只读。

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 事件

  • 事件修饰符
    1. .stop
    2. .prevent
    3. .self
    4. .capture
    5. .once
    6. .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 绑定动态数据
  • 修饰符
    1. .lazy 键入触发改为 change 触发
    2. .number 自动转换数字,当type='number'时会自动启用
    3. .trim 自动去除两端空格

组件的生命周期钩子函数#

在组件的生命周期各个阶段,用户可以通过定义钩子函数做一些特定行为。

lifecycle.16e4c08e

侦听器 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)
  }
}

监听事件#

要想子组件触发事件,被父组件接收,需要:

  1. 使用宏 defineEmits(['enlarge-text']) 声明事件列表
  2. 在组件中书写同名的事件触发 @click="$emit('enlarge-text')"
  3. 在父组件中书写事件触发回调 @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>

局部注册#

  1. <script setup> 标签中导入的组件可以直接使用
  2. 使用选项式语法,需要显式在 components:{ nameA, nameB } 中注册

组件名格式#

  1. PascalCase 是建议的组件名格式,在单文件组件中,此名称也可以很好的于普通 html 标签区别。
  2. 在 DOM 模板中,请使用对应的 kebab-case 名称。
  3. vue 会自动处理两种格式和使用场景的转化。

Prop#

声明 Prop#

  1. <script setup> 中使用 defineProps(['prop1,prop2']) 方式
  2. 在选项式中使用 props: ['prop1','prop2] 方式
  3. 以 javascript 对象的方式带上类型名
<script setup>
	defineProps({
    title: String,
    likes: Number
  })
  export default {
    props: {
      title: String,
      likes: Number
    }
  }
</script>

书写格式#

  1. 使用 camelCase 命名 Prop 可以直接在模板中不带引号的使用属性。
  2. 在模板中,请使用 kebab-case 格式,与 DOM 书写风格形成统一。
  3. 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

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}) 创建一个实例。

实例方法包括:

  1. Axios#request(config)
  2. Axios#get(url[,config])
  3. axios#delete(url[,config])
  4. axios#head(url[,config])
  5. Axios#options(url[,config])
  6. Axios#post(url[,config])
  7. Axios#put(url[,data[,config]])
  8. Axios#patch(url[,data[,config]])
  9. 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() 可以获取更多信息

取消请求#

  1. AbortController

    const controller = new AbortController();
    axios.get();
    // 取消请求
    controller.abort();
    
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。