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();
    
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。