最近項目組重構一個大型項目,爲了引領時尚潮流,作公司最靚的仔。項目前端組採用Vue3.0
進行重構。javascript
固然,他們沒有強制要求使用Vue3.0
,不習慣的依然採用Vue2.x
的寫法慢慢過渡。我我的喜歡追求新知識,因此我就一步到位了。Vue3.0
的升級必然有許多亮點,以前也有大體瞭解:函數式API,Typescript支持等。最喜歡這種工做,能夠在工做嘗試和學習新的東西。今後開始Vue3.0
之旅。css
初看composition-api ,個人表情以下:前端
這不就是React
的Hook
嗎? 也關注了一下網上同行的評價,許多React
的開發者對此次的升級表示不屑,這不就是抄襲嗎?因爲Vue
入門簡單,擁有豐富的UI庫,龐大的使用人羣,良好的生態系統,成爲了當下火熱的前端框架。現在Vue
的star數162K
,React的star數爲147K
。Vue「抄襲」React
的Hook
也能理解,既然有巨人的肩膀,爲什麼Vue
不站上去呢。咱們不能作一個隨波逐流的人,獨立思考了一下,引用最近比較火的一句話:「存在即合理」。我的以爲Vue
這次升級的主要是爲了解決:vue
Typescript
是大趨勢,TypeScript安利指南。Vue2.x
對Typescript
支持度不高;Vue2.x
能夠經過mixins
(相似多繼承)和extends
(相似單繼承)來實現,存在命名衝突,隱藏複用API
等缺點;Vue3.0
採用函數編程便能很好的解決代碼複用問題。下面介紹的是對於咱們開發者比較容易感知的一些優化,至於重寫虛擬節點,提升運行時效率等優化暫時尚未深刻研究。java
衆所周知,Vue2.x
雙向綁定經過Object. defineproperty
重定義data
中的屬性的get
和set
方法,從而劫持了data
的get
和set
操做。Vue2.x
雙向綁定存在的弊端有:react
beforeCreate
和created
生命週期間完成。能夠經過$set
來解決後期添加監聽屬性的問題。defineproperty ()
沒法監聽數組變化,當直接用index
設置數組項是不會被檢測到的。如:this.showData[1] = {a:1}
。固然也能用$set解決。官方文檔指出,經過下面八種方法操做數組,Vue
能檢測到數據變化,分別爲:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue3.0
採用Proxy和Reflect實現雙向綁定, 它在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。咱們能夠這樣認爲,Proxy
是Object.defineProperty
的全方位增強版。git
Object.defineProperty
能作的Proxy
能作github
Proxy
有多達13種攔截方法,不限於apply、ownKeys、deleteProperty、has
等等是Object.defineProperty
不具有的。Object.defineProperty
不能作的Proxy還能作。面試
Proxy
做爲新標準,獲得了各大瀏覽器廠商的大力支持,性能持續優化。惟一的不足就是兼容性的問題,並且沒法經過polyfill
解決。vuex
更多詳細內容見:面試官: 實現雙向綁定Proxy比defineproperty優劣如何?
順便感慨一下:掘金個個都是人才,說話又好聽,噢喲超喜歡在裏面!像在外面開廂同樣,high到那種感受,飛起來那種感受 。不像某CSDN,帖子抄來抄去,還佔據了大量的搜索資源。
函數式API
主要是爲了解決代碼複用以及對Typescript的友善支持。這裏主要介紹代碼複用的升級。廢話很少說,直接擼代碼。下面介紹的場景相對簡單,應該也比較好理解,是咱們平時開發過程當中經常使用的搜索組件。
代碼結構以下:
初始化效果:
<template>
<div class="vue2">
<el-input type="text" @change="onSearch" v-model="searchValue" />
<div v-for="name in names" v-show="name.isFixSearch" :key="name.id">
{{ name.value }}
</div>
</div>
</template>
<script>
// vue2.vue
import searchMixin from "./searchMixin";
export default {
mixins: [searchMixin],
data() {
return {
names: [
{ id: 1, isFixSearch: true, value: "name1" },
{ id: 2, isFixSearch: true, value: "name2" },
{ id: 3, isFixSearch: true, value: "name3" },
{ id: 4, isFixSearch: true, value: "name4" },
{ id: 5, isFixSearch: true, value: "name5" },
],
};
},
};
</script>
<style lang="less">
.vue2 {
}
</style>
複製代碼
// searchMixin.js
export default {
data() {
return {
searchValue: ''
}
},
/** * 命名固定,外面另外命名不容易 * 應該能夠經過 searchMixin.methods.onNewNameSearch = searchMixin.methods.onSearch * 來進行重命名。可是data裏面的應該就重命名不了了。 */
methods: {
onSearch() {
this.names
.forEach(name =>
name.isFixSearch = name.value.includes(this.searchValue)
)
}
}
}
複製代碼
效果以下:
mixin
的頁面屬性;mixin
多的話容易出現不容易定位的問題。固然也能夠經過namespace
來解決。<template>
<div class="vue3">
<el-input type="text" @change="onSearch" v-model="searchValue" />
<div v-for="name in names" v-show="name.isFixSearch" :key="name.id">
{{ name.value }}
</div>
</div>
</template>
<script>
// vue3
import useSearch from "./useSearch";
export default {
setup() {
const originNames = [
{ id: 1, isFixSearch: true, value: "name1" },
{ id: 2, isFixSearch: true, value: "name2" },
{ id: 3, isFixSearch: true, value: "name3" },
{ id: 4, isFixSearch: true, value: "name4" },
{ id: 5, isFixSearch: true, value: "name5" },
];
// 能夠很容易重命名
const { onSearch, data: names, searchValue } = useSearch(originNames);
return {
onSearch,
names,
searchValue,
};
},
};
</script>
<style lang="less">
.vue3 {
}
</style>
複製代碼
// useSearch
import {
reactive,
toRefs
} from "@vue/composition-api";
export default function useSearch(names) {
const state = reactive({
data: names,
searchValue: ''
})
const onSearch = () => {
state.data.forEach(name =>
name.isFixSearch = name.value.includes(state.searchValue)
)
}
return {
...toRefs(state),
onSearch
}
}
複製代碼
效果以下:
一開始的vue2.x
是不支持Typescript
的,耐不住Typescript
的火熱,就出現了.vue
中class
寫法,經過vue-class-component強行支持Typescript
。且看下面代碼。
<template>
<div>
<input v-model="hello" />
<p>hello: {{ hello }}</p>
<p>computed: {{ computedMsg }}</p>
<button @click="greet({a:1})">Hello TS</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Component, { mixins } from "vue-class-component";
@Component
class MixinsDemo extends Vue {
typescript = "Typescript";
}
// 這裏即可以使用Typescript的類型檢驗了
function testTs(val: string) {
console.log("testTs");
}
// 編譯失敗
testTs('1')
@Component
export default class TS extends mixins(MixinsDemo) {
// 初始化數據
hello = "Hello";
// 聲明週期鉤子
mounted() {}
// 計算屬性
get computedMsg() {
return `computed ${this.hello} ${this.typescript}`;
}
// template 傳參校類型驗不了
greet(val: string) {
alert(`greeting ${this.hello} ${this.typescript}-${val}`);
}
}
</script>
複製代碼
Vue2.x
語法外還要從新學習一套語法效果以下:
Vue2.x1
引入Typescript
意猶未盡的能夠查看:在 Vue 中使用 TypeScript 的一些思考(實踐)
因爲Vue3.0
採用函數式API
開發,能很方便的引入Typescript
,這裏就不贅述了。
上面囉嗦了那麼多「廢話」,下面就開啓Vue3.0
之旅。首先簡單介紹一下Vue3.0
入口API setup(props,ctx)
的兩個參數:
template
傳遞的參數,不像vue2.x
能夠經過this.propsA
訪問到template
傳遞的參數,這裏要經過props.propsA
進行訪問setup
裏面this
再也不指向vue
實例,ctx
有幾個屬性:slots, root, parent, refs, attrs, listeners, isServer, ssrContext, emit
,其中root
指向`vue實例,其餘詳細介紹可見Vue Composition API下面內容是Vue2.x
經常使用的場景寫法映射到Vue3.0
,但願在你平常開發過程當中有所幫助。代碼目錄結構以下:
頁面效果以下:
export default {
data() {
return {
plusValue: 1,
stateValue: 1,
};
},
created(){
// 單向綁定
this.singleValue = 2
},
methods: {
onClickSingle() {
this.singleValue++;
console.log(this.singleValue);
},
onPlus() {
this.plusValue++;
},
onPlueState() {
this.stateValue++;
},
},
};
複製代碼
雙向綁定我的更喜歡經過reactive
統一包裹,訪問的時候能夠經過state.stateValue
進行訪問和賦值,經過ref生成的雙向綁定數據須要經過plusValue.value
的形式進行訪問和賦值。並且能夠經過...toRefs(state)
一次性解構爲雙向綁定的屬性。
import { reactive, ref, toRefs } from "@vue/composition-api";
export default {
setup() {
// 單向綁定
let singleValue = 2;
// 單個雙向綁定
const plusValue = ref(1);
// 對象包裹雙向綁定
const state = reactive({
stateValue: 1,
});
const methods = {
onClickSingle() {
singleValue++;
console.log(singleValue);
},
onPlus() {
plusValue.value++;
},
onPlueState() {
state.stateValue++;
},
};
return {
...toRefs(state),
plusValue,
singleValue,
...methods,
};
},
};
複製代碼
computed: {
plusValueAndStateValue() {
return this.plusValue + this.stateValue;
},
},
複製代碼
import { computed } from "@vue/composition-api";
// 計算屬性
const plusValueAndStateValue = computed(
() => plusValue.value + state.stateValue
);
複製代碼
watch: {
plusValueAndStateValue(val) {
console.log("vue2 watch plusValueAndStateValue change", val);
},
},
複製代碼
import { watch } from "@vue/composition-api";
watch(plusValueAndStateValue, (val) => {
console.log("vue3 watch plusValueAndStateValue change", val);
});
複製代碼
Vue2.x
能夠經過App.vue
實例來跨組件廣播事件,傳遞數據。
onClickSingle() {
this.singleValue++;
// 廣播事件
this.$root.$emit("vue2 eventBus", { a: 1 });
console.log(this.singleValue);
},
複製代碼
另外一個存活的vue
實例,接受事件
created() {
this.$root.$on("vue2 eventBus", (data) => {
console.log(data);
debugger;
});
},
複製代碼
固然也能夠經過監聽vuex
中的屬性值來實現eventBus
。參看狀態機Vuex的奇淫巧技-多彈框、多事件統一控制
發送事件(原理和vue2.x
同樣)
onClickSingle() {
singleValue++;
ctx.root.$root.$emit("vue3 eventBus", { a: 3 });
console.log(singleValue);
}
複製代碼
接受事件
ctx.root.$root.$on("vue3 eventBus", (data) => {
console.log(data);
debugger;
});
複製代碼
固然也能夠經過Vue3.0
中vuex
代替方案中監聽注入屬性來實現eventBus
,見下面2.6
Vue3.0
中再也不存在beforeCreate
和created
。composition-api
暴露其餘生命週期的API
,都是以on開頭的API
。下面就mounted
寫法進行舉例,其餘生命週期類比。
import {
onMounted,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onUnmounted,
onUpdated,
} from "@vue/composition-api";
export default {
setup(props, ctx) {
onMounted(()=>{
console.log('mounted')
})
},
};
複製代碼
Vue2.x vuex
的寫法可參看狀態機Vuex的奇淫巧技-多彈框、多事件統一控制。因爲Vue3.0
不能直接訪問到this,不能方便的調用this.$commit
;也不方便經過mapState
在computed
注入屬性。composition-api
提供了兩個API:provide、inject
。讓Vue
更加React
,經過這兩個API
能夠替代vuex
進行狀態管理。
代碼結構以下:
場景:統一控制彈框顯示隱藏,不用在vue
實例中設置isDialogShow
和修改值,不用在彈框關閉的時候修改$parent
中的isDialogShow
。
store/BooleanFlag.js
import {
provide,
inject,
reactive,
} from '@vue/composition-api'
const bfSymbol = Symbol('BooleanFlag')
export const useBooleanFlagProvider = () => {
// 統一控制彈框顯示隱藏
const BooleanFlag = reactive({
isDialogShow: false,
isDialog2Show: false,
isDialog3Show: false,
})
const setBooleanFlag = (keys) => {
keys.forEach(key => {
if (Object.keys(BooleanFlag).includes(key)) {
BooleanFlag[key] = !BooleanFlag[key]
}
})
}
provide(bfSymbol, {
BooleanFlag,
setBooleanFlag,
})
}
export const useBooleanFlagInject = () => {
return inject(bfSymbol);
};
複製代碼
store/index.js
// vue3 vuex 替代方案
import {
useBooleanFlagProvider,
useBooleanFlagInject
} from './BooleanFlag'
export {
useBooleanFlagInject
}
export const useProvider = () => {
useBooleanFlagProvider()
}
複製代碼
init/initVueComposition.js
import VueCompositionApi from '@vue/composition-api'
import {
useProvider
} from '@/store'
export default function initVueComposition(Vue) {
Vue.use(VueCompositionApi)
return function setup() {
useProvider()
return {}
}
}
複製代碼
init/index.js
import initElement from './initElement'
import initAppConst from './initAppConst'
import initI18n from './initI18n'
import initAPI from './initAPI'
import initRouter from './initRouter'
import initVueComposition from './initVueComposition'
// 往Vue原型上追加內容,簡化開發調用,原型上追加內容是不會影響性能的,由於原型在內存中只存在一份
export default function initVue(Vue) {
initElement(Vue)
initAppConst(Vue)
const i18n = initI18n(Vue)
const router = initRouter(Vue)
initAPI(Vue)
const setup = initVueComposition(Vue)
return {
i18n,
router,
setup
}
}
複製代碼
main.ts
import Vue from 'vue'
import App from './App.vue'
import initVue from './init'
import 'static/css/base.css'
import 'static/css/index.css'
const init = initVue(Vue)
new Vue({
...init,
render: h => h(App),
}).$mount('#app')
複製代碼
vue文件注入
<template>
<div>
<div>
<span>vuex:</span>
<span>{{ "isDialogShow : " + BooleanFlag.isDialogShow }}</span>
<el-button @click="onDialogShow">onDialogShow</el-button>
</div>
</div>
</template>
<script>
import { useBooleanFlagInject } from "@/store";
export default {
setup(props, ctx) {
const { BooleanFlag, setBooleanFlag } = useBooleanFlagInject();
const methods = {
onDialogShow() {
setBooleanFlag(["isDialogShow"]);
},
};
return {
BooleanFlag,
};
},
};
</script>
複製代碼
都看到這裏了,點個贊,關注再走唄。