Xi-Yuer

Xi-Yuer

github

Vue

Vue3 常見 API#

reactive# Vue Guide#

數據屬性 Data#

組件的 data 選項是一個函數。Vue 在創建新組件實例的過程中調用此函數。它應該返回一個對象,然後 Vue 會通過響應性系統將其包裹起來,並以 $data 的形式存儲在組件實例中。為方便起見,該對象的任何頂級 property 也直接通過組件實例暴露出來:

  1. 本質上,組件的 data 選項只是一個函數
  2. 這個函數會在 Vue 創建組件實例時被調用
  3. 這個函數會返回一個對象
  4. 返回的這個對象會被 Vue 包裝成響應式數據
  5. 包裝後的響應式數據,以 $data 的形式存儲在組件實例中
  6. 為方便,data 返回的對象的任何頂級屬性可以直接通過組件實例直接拿到,前提是需要確保它們都在 data 函數返回的對象中
  7. 如果直接在組件實例上添加新屬性,雖然可以添加成功,但是由於它沒有在響應式對象 $data 中,所以該屬性不具有響應式
  8. data 返回的對象的屬性,應避免使用 $_ 開頭

原理分析#

為什麼 data 屬性是一個函數而不是一個對象?

  • 結論data 定義可是函數也可以是對象,但是根組件實例化時可以是對象或函數,組件實例化只能是函數
  • 原理:根實例是單例,不會產生數據污染;組件實例不是單例,為防止多個組件實例之間共用一個 data,產生數據污染,採用函數形式,初始化 data 時會將其作為工廠函數都會返回全新 data 對象

計算屬性 Computed#

應用場景:

  1. 模版中,較長的表達式會讓模版變得複雜和難以維護,為了解決這個問題 computed 孕育而生
  2. 具有緩存效果,提示了性能,當依賴數據未發生變化,調用的是緩存的數據

對於任何包含響應式數據的 複雜邏輯,都應該使用 計算屬性

計算屬性和函數方法#

計算屬性是基於它們的反應依賴滾系緩存的。計算屬性只在相關響應式依賴發生改變時它們才會重新求值。

相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。

我們為什麼需要緩存?

假設我們有一個性能開銷比較大的計算屬性 list,它需要遍歷一個巨大的數組並做大量的計算。然後我們可能有其他的計算屬性依賴於 list。如果沒有緩存,我們將不可避免的多次執行 listgetter!如果你不希望有緩存,請用 method 來替代。

監聽器 Watch#

Options API 實現 Watch#

普通的 watch#

export default {
    props:['dataList'],
    data(){
        return {
            sourceData:[],
            total:0
        }
    },
    // 當props的dataList發生變化時,data中的值也會重新被計算並賦值
    watch:{
        dataList(newVal,oldVal){
            this.total = newVal.length
            this.sourceData = newVal
        }
    }
}

對象的 watch#

export default {
    data(){
        return {
            firstName:'Li',
            lasteName:'Huan'
        }
    },
    // 當props的dataList發生變化時,data中的值也會重新被計算並賦值
    watch:{
        dataList:{
           // getter
            get:function(){
                return this.firstName + this.lasteName
            },
            set:function(val){
                // this.dataList = 'Wang Feng'
                const [ firstName,lasteName ] = val.spilt(' ')
                this.firstName = firstName
                this.lasteName = lasteName
            },
            {
            // 對象和數組都是引用類型,引用類型變量存的是地址,地址沒有變
            // 所以不會觸發 watch。這時我們需要進行深度監聽,需要加上屬性 deep: true
            deep:true,
            // watch 有一個特點,當值第一次綁定的時候,不會執行監聽函數,
            // 只有值發生變化時才會執行,如果想要最初綁定值的時候也執行函數,需要加 immediate 屬性
            immediate:true
        }
        }
    }
}

Reactive API 實現 watch#

watch 函數用來監聽特定的數據源,並在回調函數中執行副作用。默認情況下是 懶性 的,也就是說僅在監聽的源數據變更時才執行回調。

watch(source, callback, [options]);

參數說明:

  • source:可以支持 String、Object、Function 和 Array 用於指定要監聽的響應式變量
  • callback:執行的回調函數
  • options:支持 deepimmediateflush 選項

監聽 reactive#

import { defineComponent,reactive,watch } from 'vue'
export default defineComponent({
    setup(){
        const state = reactive({
            firstName:'Li',
            lastName:'Wang'
        })
        
        // 當修改 firstName時會自動觸發 watch 的回調該函數
        watch(
            ()=>state.firstName,
            (newVal,oldVal)=>{
            	console.log(`新值:${newVal}`,`舊值:${oldVal}`)
        	},
        	{deep:true})
    }
})

監聽多個數據#

const stop = watch([() => state.firstName, () => state.lastName], ([curAge, newVal], [preAge, oldVal]) => {
  console.log('新值:', curAge, '老值:', preAge);
  console.log('新值:', newVal, '老值:', oldVal);
});

unmounted(()=>{
    // 停止監聽
    stop()
})

watchEffect#

watchEffect 不需要像 watch 那樣需要先傳入依賴,watchEffect 會自動收集依賴,只要指定一個回調函數。在組件初始化時,會先執行一次來收集依賴,然後當收集到的依賴中數據發生變化時,就會再次執行回調函數。

watchEffect(()=>{
    // 自動搜集依賴,初始化執行一次
    console.log(firstName);
    console.log(lastName)
})

樣式設置#

對象語法#

<template>
    <div class='static' :class="{active:isActive,danger:hasError}"></div>
</template>
<script>
	export default {
        data(){
            return {
                isActive:true,
                hasError:true
            }
        }
    }
</script>
<!-- 渲染後 -->
<div class="static active danger"></div>

直接傳入對象#

<template>
    <div class='static' :class="classObj"></div>
</template>
<script>
	export default {
        data(){
            return {
               classObj:{
                   active:true,
                   hasError:true
               }
            }
        }
    }
</script>

computed 傳入#

<template>
	<div class="static" :class="clsObject"></div>
</template>
<script>
	export default {
        data(){
            return {
                isActive:true,
                hasError:true
            }
        },
        computed:{
            classObj(){
                return {
                    active:this.active,
                    hasError:this.hasError
                }
            }
        }
    }
</script>

條件渲染#

<template>
	<div v-if='show'></div>
	 <div v-else></div>
	<div v-show='show'></div>
</template>

<script>
	export default {
        data(){
            return {
                show:true
            }
        }
    }
</script>

v-if vs v-show:

  • v-show只是單純的修改元素的css屬性,它將元素的disable設置為了none
  • v-if是將元素從組件樹中移除,當元素是一個組件時,修改show值,會觸發組件元素的生命週期函數

內置組件#

component#

動態組件

// 動態組件
<template>
	<!-- currentTab 改變時組件也改變 -->
	<component :is="tabs[currentTab]"></component>
</template>

在上面的例子中,被傳給 :is 的值可以是以下幾種:

  • 被註冊的組件名
  • 導入的組件對象

transition#

動畫組件

<!-- 模板 -->
<template>
	<Transition name="silde-fade">

	</Transition>
</template>
/*
  進入和離開動畫可以使用不同
  持續時間和速度曲線。
*/
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

image-20230726165423050

  • 過渡鉤子函數
<template>
	<Transition
      @before-enter="onBeforeEnter"
      @enter="onEnter"
      @after-enter="onAfterEnter"
      @enter-cancelled="onEnterCancelled"
      @before-leave="onBeforeLeave"
      @leave="onLeave"
      @after-leave="onAfterLeave"
      @leave-cancelled="onLeaveCancelled"
  >
  </Transition>
</template>
// 在元素被插入到 DOM 之前被調用
// 用這個來設置元素的 "enter-from" 狀態
function onBeforeEnter(el) {}

// 在元素被插入到 DOM 之後的下一幀被調用
// 用這個來開始進入動畫
function onEnter(el, done) {
  // 調用回調函數 done 表示過渡結束
  // 如果與 CSS 結合使用,則這個回調是可選參數
  done()
}

// 當進入過渡完成時調用。
function onAfterEnter(el) {}
function onEnterCancelled(el) {}

// 在 leave 鉤子之前調用
// 大多數時候,你應該只會用到 leave 鉤子
function onBeforeLeave(el) {}

// 在離開過渡開始時調用
// 用這個來開始離開動畫
function onLeave(el, done) {
  // 調用回調函數 done 表示過渡結束
  // 如果與 CSS 結合使用,則這個回調是可選參數
  done()
}

// 在離開過渡完成、
// 且元素已從 DOM 中移除時調用
function onAfterLeave(el) {}

// 僅在 v-show 過渡中可用
function onLeaveCancelled(el) {}

keep-alive#

用於緩存組件狀態,避免組件重複渲染

keep-alive有以下三個屬性

  • include:字符串或正則表達式,只有名稱匹配的組件會被緩存,組件的 name;
  • exclude:字符串或正則表達式,任何名稱匹配的組件都不會被緩存;
  • max:數字,最多可以緩存多少組件實例。

slot#

默認插槽

<template>
	<!-- 父組件 -->
    <Child>
      <div>默認插槽</div>
    </Child>

    <!-- 子組件 -->
    <template>
      <slot>
        <p>插槽後備的內容</p>
      </slot>
    </template>
</template>

具名插槽#

<!-- 組件內部 -->
<div class="container">
  <header>
    <!-- 我們希望把頁頭放這裡 -->
  </header>
  <main>
    <!-- 我們希望把主要內容放這裡 -->
  </main>
  <footer>
    <!-- 我們希望把頁腳放這裡 -->
  </footer>
</div>
<!-- 組件內部 -->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

作用域插槽#

父組件在自定義子組件中的插槽內容時,需要獲取到子組件中的組件數據

<template>
	<!-- Child -->
    <ul>
      <li v-for="(item, index) in items">
        <slot :item="item"></slot>
      </li>
    </ul>

    <!-- Parent -->
    <todo-list>
      <template #default="{slotProps}">
        <span class="green">{{ slotProps.item }}</span>
      </template>
    </todo-list>
</template>

Teleport#

teleport 是 Vue3.x 新推出的功能,提供了一種乾淨的方法,允許我們控制在 DOM 中哪個父節點下渲染了 HTML

<template>
	<Teleport to="body">
      <div> teleport to body </div>
	</Teleport>
</template>
import { reactive } from 'vue';
export default {
  // `setup` 是一個專門用於組合式API 的特殊鉤子
  setup() {
    const state = reactive({ count: 0 }); // 暴露 state到模板

    return { state };
  },
};

ref#

import { ref } from 'vue';

const count = ref(0);

console.log(count); // { value: 0 }
console.log(count.value); // 0

count.value++;
console.log(count.value); // 1

computed#

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一個計算屬性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

watch#

<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// 可以直接監聽一個 ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>

watchEffect#

watchEffect () 會立即執行一遍回調函數,如果這時函數產生了副作用,
Vue 會自動追蹤副作用的依賴關係,自動分析出響應源。

watchEffect(async () => {
  const response = await fetch(url.value);
  data.value = await response.json();
});

watch vs. watchEffect#

watchwatchEffect 都能響應式地執行有副作用的回調。它們之間的主要區別是追蹤響應式依賴的方式:watch 只追蹤明確監聽的源。它不會追蹤任何在回調中訪問到的東西。另外,僅在響應源確實改變時才會觸發回調。watch 會避免在發生副作用時追蹤依賴,因此,我們能更加精確地控制回調函數的觸發時機。
watchEffect,則會在副作用發生期間追蹤依賴。它會在同步執行過程中,自動追蹤所有能訪問到的響應式 property。這更方便,而且代碼往往更簡潔,但其響應性依賴關係不那麼明確。

狀態管理 pinia#

store#

// stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 };
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++;
    },
  },
  getters(){
      finishedTodos(store){
          return store.count++
      }
  }
});

action#

import { useCounterStore } from '@/stores/counter';

export default {
  setup() {
    const counter = useCounterStore();

    counter.count++;
    // with autocompletion ✨
    counter.$patch({ count: counter.count + 1 });
    // or using an action instead
    counter.increment();
  },
};

vue-router 路由#

router.js

const Home = { template: '<div>Home</div>' };
const About = { template: '<div>About</div>' };

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
];
const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes,
});

const app = Vue.createApp({});

app.use(router);

app.mount('#app');

渲染

<template>
  <router-view />
</template>

setup 語法#

defineProps defineEmits#

Ts 語法支持

const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

defineExpose#

setup 組件是默認關閉

<script setup>
  import {ref} from 'vue' const a = 1 const b = ref(2) defineExpose({(a, b)})
</script>
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。