最近项目组重构一个大型项目,为了引领时尚潮流,作公司最靓的仔。项目前端组采用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>
复制代码
都看到这里了,点个赞,关注再走呗。