目錄
基礎素材
vue3的優化
使用CompositionAPI理由
1. reactive() 函數
2. ref() 函數
2.1. ref的使用
2.2. 在 reactive 對象中訪問 ref 創建的響應式數據
3. isRef() 函數
4. toRefs() 函數
5. computed()
5.1. 通過 set()、get()方法創建一個可讀可寫的計算屬性
6. watch() 函數
6.1. 監聽用 reactive 聲明的數據源
6.2. 監聽用 ref 聲明的數據源
6.3. 同時監聽多個值
6.4. stop 停止監聽
7. LifeCycle Hooks(新的生命周期)
8. Template refs
9. vue 的全局配置
10. Suspense組件
11. vuex, router, vue 初始化寫法的變化
11.1. vue
11.2. router
11.2.1. 特點:
11.2.2. 使用
12. vuex
12.1. 使用
13. 組件注入
13.1. 父組件
13.2. 子組件
14. vue 3.x 完整組件模版結構
基礎素材
印記中文|印記中文 - 深入挖掘國外前端新領域,為中國 Web 前端開發人員提供優質文檔!
vue3官網 | 簡介 | Vue.js
pina|介紹 | Pinia 中文文檔
vue3的優化
- 對虛擬 DOM 進行了重寫、模板編譯速度的提升,對靜態數據的跳過處理
- 對數組的監控
- 對ts有了很好的支持
- 對2.x版本的完全兼容
- 可以有多個根節點(也有bug,比如外層開了display:flex 那么里面會收到影響,也就是說布局更靈活但也更要小心,總之請對自己與他人的代碼負責) 6. 支持Source map,雖然沒演示但是這點真的重要 7. vue2 大量的 API 掛載在 Vue 對象的原型上,難以實現 TreeShaking
使用CompositionAPI理由
- 更好的Typescript支持
- 在復雜功能組件中可以實現根據特性組織代碼 - 代碼內聚性。比如:排序和搜索邏輯內聚
- 組件間代碼復用
經過了漫長的迭代,Vue 3.0 終于在上 2020-09-18 發布了,帶了翻天覆地的變化,使用了 Typescript 進行了大規模的重構,帶來了 Composition API RFC 版本,類似 React Hook 一樣的寫 Vue,可以自定義自己的 hook ,讓使用者更加的靈活。
- setup()
- ref()
- reactive()
- isRef()
- toRefs()
- computed()
- watch()
- LifeCycle Hooks(新的生命周期)
- Template refs
- globalProperties
- Suspense
1. reactive() 函數
reactive() 函數接收一個普通對象,返回一個響應式的數據對象,想要使用創建的響應式數據也很簡單,創建出來之后,在 setup 中 return 出去,在 template 中調用即可
<template>{{state.name}} // test
<template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({setup(props, context) {let state = reactive({name: 'test'});return state}
});
</script>
2. ref() 函數
2.1. ref的使用
ref() 函數用來根據給定的值創建一個響應式的數據對象,ref() 函數調用的返回值是一個對象,這個對象上只包含一個 value 屬性,只在 setup 函數內部訪問 ref 函數需要加 .value
<template><div class="mine">{{count}} // 10</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({setup() {const count = ref(10)// 在js 中獲取ref 中定義的值, 需要通過value屬性console.log(count.value);return {count}}
});
</script>
2.2. 在 reactive 對象中訪問 ref 創建的響應式數據
通過reactive 來獲取ref 的值時,不需要使用.value屬性
<template><div class="mine">{{count}} -{{t}} // 10 -100</div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({setup() {const count = ref<number>(10)const obj = reactive({t: 100,count})// 通過reactive 來獲取ref 的值時,不需要使用.value屬性console.log(obj.count);return {...toRefs(obj)}}
});
</script>
3. isRef() 函數
isRef() 用來判斷某個值是否為 ref() 創建出來的對象
<script>
import { defineComponent, isRef, ref } from 'vue';
export default defineComponent({setup(props, context) {const name = 'vue'const age = ref(18)console.log(isRef(age)); // trueconsole.log(isRef(name)); // falsereturn {age,name}}
});
</script>
4. toRefs() 函數
toRefs() 函數可以將 reactive() 創建出來的響應式對象,轉換為普通的對象,只不過,這個對象上的每個屬性節點,都是 ref() 類型的響應式數據
<template><div class="mine">{{name}} // test{{age}} // 18</div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({setup(props, context) {let state = reactive({name: 'test'});const age = ref(18)return {...toRefs(state),age}}
});
</script>
5. computed()
該函數用來創造計算屬性,和過去一樣,它返回的值是一個 ref 對象。
里面可以傳方法,或者一個對象,對象中包含 set()、get()方法。
<script>
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({setup(props, context) {const age = ref(18)// 根據 age 的值,創建一個響應式的計算屬性 readOnlyAge,它會根據依賴的 ref 自動計算并返回一個新的 refconst readOnlyAge = computed(() => age.value++) // 19return {age,readOnlyAge}}
});
</script>// 組合式
<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>
5.1. 通過 set()、get()方法創建一個可讀可寫的計算屬性
<script>
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({setup(props, context) {const age = ref(18)const computedAge = computed({get: () => age.value + 1,set: value => age.value + value})// 為計算屬性賦值的操作,會觸發 set 函數, 觸發 set 函數后,age 的值會被更新age.value = 100return {age,computedAge}}
});
</script>
6. watch() 函數
watch 函數用來偵聽特定的數據源,并在回調函數中執行副作用。
默認情況是懶執行的,也就是說僅在偵聽的源數據變更時才執行回調。
6.1. 監聽用 reactive 聲明的數據源
<script>
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';export default defineComponent({setup(props, context) {const state = reactive({ name: 'vue', age: 10 })watch(() => state.age,(age, preAge) => {console.log(age); // 100console.log(preAge); // 10})// 修改age 時會觸發 watch 的回調, 打印變更前后的值state.age = 100return {...toRefs(state)}}
});
</script>
6.2. 監聽用 ref 聲明的數據源
<script>
import { defineComponent, ref, watch } from 'vue';
export default defineComponent({setup(props, context) {const age = ref(10);watch(age, () => console.log(age.value)); // 100// 修改age 時會觸發watch 的回調, 打印變更后的值age.value = 100return {age}}
});
</script>
6.3. 同時監聽多個值
<script>
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';export default defineComponent({setup(props, context) {const state = reactive({ name: 'vue', age: 10 })watch([() => state.age, () => state.name],([newName, newAge], [oldName, oldAge]) => {console.log(newName);console.log(newAge);console.log(oldName);console.log(oldAge);})// 修改age 時會觸發watch 的回調, 打印變更前后的值, 此時需要注意, 更改其中一個值, 都會執行watch的回調state.age = 100state.name = 'vue3'return {...toRefs(state)}}
});
</script>
6.4. stop 停止監聽
在 setup() 函數內創建的 watch 監視,會在當前組件被銷毀的時候自動停止。
如果想要明確地停止某個監視,可以調用 watch() 函數的返回值即可,語法如下:
<script>
import { set } from 'lodash';
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
export default defineComponent({setup(props, context) {const state = reactive({ name: 'vue', age: 10 })const stop = watch([() => state.age, () => state.name],([newName, newAge], [oldName, oldAge]) => {console.log(newName);console.log(newAge);console.log(oldName);console.log(oldAge);})// 修改age 時會觸發 watch 的回調, 打印變更前后的值, 此時需要注意, 更改其中一個值, 都會執行watch的回調state.age = 100state.name = 'vue3'setTimeout(()=> {stop()// 此時修改時, 不會觸發watch 回調state.age = 1000state.name = 'vue3-'}, 1000) // 1秒之后將取消watch的監聽return {...toRefs(state)}}
});
</script>
7. LifeCycle Hooks(新的生命周期)
新版的生命周期函數,可以按需導入到組件中,且只能在 setup() 函數中使用,但是也可以在 setup 外定義,在 setup 中使用\
<script>
import { set } from 'lodash';
import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onErrorCaptured, onMounted, onUnmounted, onUpdated } from 'vue';
export default defineComponent({setup(props, context) {onBeforeMount(()=> {console.log('beformounted!')})onMounted(() => {console.log('mounted!')})onBeforeUpdate(()=> {console.log('beforupdated!')})onUpdated(() => {console.log('updated!')})onBeforeUnmount(()=> {console.log('beforunmounted!')})onUnmounted(() => {console.log('unmounted!')})onErrorCaptured(()=> {console.log('errorCaptured!')})return {}}
});
</script>
8. Template refs
通過 refs 來獲取真實 dom 元素,這個和 react 的用法一樣,為了獲得對模板內元素或組件實例的引用,我們可以像往常一樣在 setup()中聲明一個 ref 并返回它
<div><div ref="content">第一步, 在dom上面定義, 他會有一個回調</div></div><ul><li>v-for 出來的ref</li><li>可以寫為表達式的形式, 可以推導出vue是如何實現的</li><li>vue2.x的時候v-for不用這么麻煩, 直接寫上去會被組裝成數組</li><li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li></ul>
9. vue 的全局配置
通過 vue 實例上 config 來配置,包含 Vue 應用程序全局配置的對象。您可以在掛載應用程序之前修改下面列出的屬性:
您可以在掛載應用程序之前修改下面列出的屬性:
const app = Vue.createApp({})
app.config = {...}//為組件渲染功能和觀察程序期間的未捕獲錯誤分配處理程序。//錯誤和應用程序實例將調用處理程序:
app.config.errorHandler = (err, vm, info) => {}
可以在應用程序內的任何組件實例中訪問的全局屬性,組件的屬性將具有優先權。
這可以代替 Vue 2.xVue.prototype 擴展:
const app = Vue.createApp({})
app.config.globalProperties.$http = 'xxxxxxxxs'
可以在組件用通過 getCurrentInstance() 來獲取全局 globalProperties 中配置的信息,getCurrentInstance() 方法獲取當前組件的實例,然后通過 ctx 屬性獲得當前上下文,這樣我們就能在 setup 中使用 router 和 vuex,通過這個屬性我們就可以操作變量、全局屬性、組件屬性等等。
setup( ) { const { ctx } = getCurrentInstance(); ctx.$http }
10. Suspense組件
在開始介紹 Vue 的 Suspense 組件之前,我們有必要先了解一下 React 的 Suspense 組件,因為他們的功能類似。
React.lazy 接受一個函數,這個函數需要動態調用 import()。
它必須返回一個 Promise,該 Promise 需要 resolve 一個 default export 的 React 組件。
import React, { Suspense } from 'react';
const myComponent = React.lazy(() => import('./Component'));
function MyComponent() {return (<div><Suspense fallback={<div>Loading...</div>}><myComponent /></Suspense></div>);
}
Vue3 也新增了 React.lazy 類似功能的 defineAsyncComponent 函數,處理動態引入(的組件)。
defineAsyncComponent 可以接受返回承諾的工廠函數。
當您從服務器檢索到組件定義時,應該調用 Promise 的解析回調。
您還可以調用 reject(reason) 來指示負載已經失敗。
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>import('./components/AsyncComponent.vue')
)
app.component('async-component', AsyncComp)
Vue3 也新增了 Suspense 組件:
- 異步組件加載狀態管理:可以通過 Suspense 組件來指定一個 loading 插槽,在異步組件加載過程中
- 錯誤處理:可以在 Suspense 組件中使用 error 插槽來處理異步組件加載過程中可能發生的錯誤,并展示相關信息
- 多個異步組件加載狀態管理:能夠同時管理多個異步組件的加載狀態,在任意一個異步組件加載時展示 loading 狀態,而其他未加載的組件仍然保持正常
<template><Suspense><template #default><my-component /></template><template #fallback>Loading ...</template></Suspense>
</template>
<script>import { defineComponent, defineAsyncComponent } from "vue";const MyComponent = defineAsyncComponent(() => import('./Component'));
export default defineComponent({components: {MyComponent},setup() {return {}}
})
</script>
11. vuex, router, vue 初始化寫法的變化
11.1. vue
import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
import store from './store'// 方法一. 創建實例變成了鏈式, 直接寫下去感覺語義與結構有點模糊, 但是我們要理解vue這樣做的良苦用心, 前端趨近于函數化。
// createApp(App).use(router).use(store).mount('#app')// 方法二.
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');
11.2. router
history 關鍵字:createWebHistory
history模式直接指向history對象,它表示當前窗口的瀏覽歷史,history對象保存了當前窗口訪問過的所有頁面網址。URL中沒有#,它使用的是傳統的路由分發模式,即用戶在輸入一個URL時,服務器會接收這個請求,并解析這個URL,然后做出相應的邏輯處理。
11.2.1. 特點:
當使用history模式時,URL就像這樣:hhh.com/user/id。相比hash模式更加好看。
雖然history模式不需要#。但是,它也有自己的缺點,就是在刷新頁面的時候,如果沒有相應的路由或資源,就會刷出404來。
history api可以分為兩大部分,切換歷史狀態 和 修改歷史狀態:
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue'const routes = [{path: '/',name: 'Home',component: Home}
]const router = createRouter({// 專門創建history的函數history: createWebHistory(process.env.BASE_URL),routes
})export default router
11.2.2. 使用
<template><div>{{id}}</div>
</template><script>
import { getCurrentInstance, ref } from 'vue';
export default {setup(){const { ctx } = getCurrentInstance()// 1. 這樣也是為了去掉this// 2. 方便類型推導console.log(ctx.$router); // push等方法console.log(ctx.$router.currentRoute.value); // 路由實例// 這個其實沒有必要變成ref因為這個值沒必要動態// 但是他太長了, 這個真的不能忍const id = ref(ctx.$router.currentRoute.value.query.id)// 4: 頁面攔截器ctx.$router.beforeEach((to, from,next)=>{console.log('路由的生命周期')next()})return {id}}
}
</script>
12. vuex
import { createStore } from 'vuex'
// 專門創建實例的一個方法
export default createStore({state: {},mutations: {},actions: {},modules: {}
});
12.1. 使用
import { createStore } from 'vuex'// 難道前端趨勢只有函數這一種嗎
export default createStore({state: {name:'牛逼, 你拿到我了',age: 24,a:'白',b:'黑'},mutations: {updateName(state, n){state.name += n}},actions: {deferName(store) {setTimeout(()=>{// 必須只有commit可以修改值, 這個設定我比較反對, 可以討論// vuex本身結構就很拖沓, 定義域使用個人都不喜歡store.state.name = '牛逼, 你改回來了'},1000)}},getters: {fullName(state){ return `${state.a} - + -${state.b}` }},modules: {}
});
<template><div><p>{{name}}</p><button @click="updateName('+')">點擊改變名字</button><button @click="deferName('+')">改回來</button><p>{{fullName}}</p></div>
</template><script>
import { useStore } from "vuex";
import { computed } from "vue";
export default {setup() {const store = useStore();// 1: 單個引入const name = computed(() => store.state.name);// 2: 引入整個stateconst state = computed(() => store.state);console.log("vuex的實例", state.value); // 別忘了.value// 3: 方法其實就直接從本體上取下來了const updateName = newName => store.commit("updateName", newName);// 4: action一個意思const deferName = () => store.dispatch("deferName");// 5: getter 沒變化const fullName = computed(() => store.getters.fullName);return {name,fullName,deferName,updateName,};}
};
</script>
13. 組件注入
13.1. 父組件
<template><div>組件:<zj :type="type" @ok="wancheng"></zj></div>
</template><script>
import zj from "../components/子組件.vue";
import { ref } from 'vue';
import { provide } from 'vue'export default {components: { zj},setup() {provide('name','向下傳值'); // 基礎值provide('name2', ref('向下傳值')); // 監控值const type = ref('大多數');function wancheng(msg){console.log('子組件-->',msg)setTimeout(()=>{type.value = 'xxxxxxx'},2000)}return {type,wancheng}}
};
</script>
13.2. 子組件
<template><div>props的屬性不用setup去return --- {{type}}</div>
</template><script>
import { inject, ref } from 'vue'
export default {props: {type: String},// 1: props也是不可以解構的, 會失去響應式// 2: context是上下文, 我們可以獲取到slots emit 等方法// 3: props, context 分開也是為了ts更明確的類型推導// setup({type}){setup(props, context) {// 1: propsconsole.log("props", props.type);console.log("上下文", context);context.emit('ok','傳遞完成');// 2: 注入console.log('inject',inject('name'));console.log('inject',inject('xxxx','我是默認值'))inject('name1', ref('默認值')) // 接收方也可以這樣}
};
</script>
14. vue 3.x 完整組件模版結構
一個完成的 vue 3.x 完整組件模版結構包含了:組件名稱、 props、components、setup(hooks、computed、watch、methods 等)
<template><div class="mine" ref="elmRefs"><span>{{name}}</span><br><span>{{count}}</span><div><button @click="handleClick">測試按鈕</button></div><ul><li v-for="item in list" :key="item.id">{{item.name}}</li></ul></div>
</template>
<script lang="ts">
import { computed, defineComponent, getCurrentInstance, onMounted, PropType, reactive, ref, toRefs } from 'vue';
interface IState {count: 0,name: string,list: Array<object>
}
export default defineComponent({name: 'demo',// 父組件傳子組件參數props: {name: {type: String as PropType<null | ''>,default: 'vue3.x'},list: {type: Array as PropType<object[]>,default: () => []}},components: {/// TODO 組件注冊},emits: ["emits-name"], // 為了提示作用setup (props, context) {console.log(props.name)console.log(props.list)const state = reactive<IState>({name: 'vue 3.0 組件',count: 0,list: [{name: 'vue',id: 1},{name: 'vuex',id: 2}]})const a = computed(() => state.name)onMounted(() => {})function handleClick () {state.count ++// 調用父組件的方法context.emit('emits-name', state.count)}return {...toRefs(state),handleClick}}
});
</script>
在通往幸福道路上,并沒有什么捷徑可走,唯有付出努力和拼搏