preface
On 6 February 2019, React version 16.8.0 added an Hooks feature. Vue announced the most important RFC of Vue3.0 in JSConf 2019, namely the function-based API. Vue3.0 will discard the Class API proposal in favor of the Function API. Currently, Vue officially offers a new version of Vue3.0 feature called Vue-function-API, which has been renamed comcomposition API.
One, the Composition API
First, what is Composition API designed for?
- Logical composition and reuse
- Type derivation: one of the core points of Vue3.0 is the use of TS refactoring to achieve silky support for TS. Function-based apis, on the other hand, are naturally type derivation friendly.
- Packaging size: Each function can be introduced separately as named ES export, which is tree-shaking friendly; Secondly, all function names and variables inside the setup function can be compressed, so it has better compression efficiency.
Let’s take a closer look at the logical composition and reuse section.
Before we begin, let’s review what vue2.x currently has for logic reuse. As shown in figure
In Mixins and HOC, the source of template data may not be clear.
Injecting properties, method names, and HOC props into mixins can also cause namespace conflicts.
Finally, since both HOC and Renderless Components require additional component instances for logical encapsulation, there is (3) unnecessary performance overhead.
1, basic usage
Now that you have a sense of what the Composition API is designed for, let’s look at its basic usage.
The installation
npm i @vue/composition-api -S
Copy the code
use
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
Copy the code
If your project uses TS, define the component using createComponent so that you can use type inference
import { createComponent } from '@vue/composition-api'
const Component = createComponent({
// ...
})
Copy the code
Since MY own project uses TS, I won’t go over some of the uses of JS here, except for the last particularly large example
import { value, computed, watch, onMounted } from 'vue'
const App = {
template: `
count is {{ count }}
plusOne is {{ plusOne }}
`.setup() {
// reactive state
const count = value(0)
// computed state
const plusOne = computed(() = > count.value + 1)
// method
const increment = () = > { count.value++ }
// watch
watch(() = > count.value * 2.val= > {
console.log(`count * 2 is ${val}`)})// lifecycle
onMounted(() = > {
console.log(`mounted`)})// expose bindings on render context
return {
count,
plusOne,
increment
}
}
}
Copy the code
OK, let’s go back to TS, and let’s look at the basic usage, but it’s pretty much the same.
<template>
<div class="hooks-one">
<h2>{{ msg }}</h2>
<p>count is {{ count }}</p>
<p>plusOne is {{ plusOne }}</p>
<button @click="increment">count++</button>
</div>
</template>
<script lang="ts">
import { ref, computed, watch, onMounted, Ref, createComponent } from '@vue/composition-api'
export default createComponent({
props: {
name: String
},
setup (props) {
const count: Ref<number> = ref(0)
// computed
const plusOne = computed(() = > count.value + 1)
// method
const increment = () = > { count.value++ }
// watch
watch(() = > count.value * 2.val= > {
console.log(`count * 2 is ${val}`)})// lifecycle
onMounted(() = > {
console.log('onMounted')})// expose bindings on render context
return {
count,
plusOne,
increment,
msg: `hello ${props.name}`}}})</script>
Copy the code
2. Combinatorial functions
We already know that one of the original purposes of the Composition API is to do logical Composition, which gives rise to what are called Composition functions.
The Vue function-based API RFC provides an example of mouse position interception. Here is an example with a business scenario.
Scenario: I need to change the page title for some specific pages, and I don’t want to make it global.
Traditionally, we would throw logic directly into mixins, as follows
import { Vue, Component } from 'vue-property-decorator'
declare module 'vue/types/vue' {
interface Vue {
setTitle (title: string): void}}function setTitle (title: string) {
document.title = title
}
@Component
export default class SetTitle extends Vue {
setTitle (title: string) {
setTitle.call(this, title)
}
}
Copy the code
Then refer to it on the page
import SetTitle from '@/mixins/title'
@Component({
mixins: [ SetTitle ]
})
export default class Home extends Vue {
mounted () {
this.setTitle('home')}}Copy the code
So, let’s use the Composition API to do the processing and see how it works
export function setTitle (title: string) {
document.title = title
}
Copy the code
Then refer to it on the page
import { setTitle } from '@/hooks/title'
import { onMounted, createComponent } from '@vue/composition-api'
export default createComponent({
setup () {
onMounted(() = > {
setTitle('home')})}})Copy the code
As you can see, we just need to pull out the logic that needs to be reused and use it directly in setup(), which is very convenient.
Of course, if you insist on making it global, it is not impossible. In this case, it will be made global, as follows
import Vue, { VNodeDirective } from 'vue'
Vue.directive('title', {
inserted (el: any, binding: VNodeDirective) {
document.title = el.dataset.title
}
})
Copy the code
The page uses the following
<template>
<div class="home" v-title data-title="Home page">
home
</div>
</template>
Copy the code
Some of you may have seen this scene and thought, I’m obviously using global commands in a more convenient way. What’s the advantage of Combining functions in Vue3.0?
Don’t worry, the above example is really just to show you how to modify your current Mixins using composite functions.
There are a lot of real scenes after the actual scene, if you can’t wait, you can skip to chapter 2.
Setup () function
Setup () is a new component option introduced in Vue3.0, where setup component logic is located.
I. Initialization time
When does setup() initialize? Let’s look at a picture
Setup is called when the component instance is created, after the props is initialized.
At this point we can accept the initial props as an argument.
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
setup (props) {
console.log('setup', props.test)
return{}}})export default class Hooks extends Vue {
@Prop({ default: 'hello' })
test: string
beforeCreate () {
console.log('beforeCreate')
}
created () {
console.log('created')}}Copy the code
The console print order is as follows
Second, as we can see from all of the examples above, setup(), like data(), returns an object whose properties are directly exposed to the template rendering context:
<template>
<div class="hooks">
{{ msg }}
</div>
</template>
<script lang="ts">
import { createComponent } from '@vue/composition-api'
export default createComponent({
props: {
name: String
},
setup (props) {
return {
msg: `hello ${props.name}`}}})</script>
Copy the code
ii. reactivity api
Unlike React Hooks, Vue Hooks go the value Hooks route, which is how to derive normal values and views from a reactive value.
Inside setup(), Vue provides us with a series of responsive apis, such as ref, which returns a ref wrapper and is automatically expanded when referenced by the View layer
<template>
<div class="hooks">
<button @click="count++">{{ count }}</button>
</div>
</template>
<script lang="ts">
import { ref, Ref, createComponent } from '@vue/composition-api'
export default createComponent({
setup (props) {
const count: Ref<number> = ref(0)
console.log(count.value)
return {
count
}
}
})
</script>
Copy the code
Then there’s computed and watch
import { ref, computed, Ref, createComponent } from '@vue/composition-api'
export default createComponent({
setup (props) {
const count: Ref<number> = ref(0)
const plusOne = computed(() = > count.value + 1)
watch(() = > count.value * 2.val= > {
console.log(`count * 2 is ${val}`)})return {
count,
plusOne
}
}
})
Copy the code
And the value that we get from the calculation, even if we don’t declare the type, we can just take it and derive its type, because it depends on the Ref
I won’t go over the rest of the internal apis and lifecycle functions in setup(), but check out the original article for more information
4. Derivation of Props type
As I said at the beginning, if you want to use type derivation in TS, you must define the component in the createComponent function
import { createComponent } from '@vue/composition-api'
const MyComponent = createComponent({
props: {
msg: String
},
setup(props) {
props.msg // string | undefined
return{}}})Copy the code
Of course, the props option is not required. If you don’t need runtime props checking, you can declare it directly at the TS level
import { createComponent } from '@vue/composition-api'
interface Props {
msg: string
}
export default createComponent({
props: ['msg'],
setup (props: Props, { root }) {
const { $createElement: h } = root
return () = > h('div', props.msg)
}
})
Copy the code
For complex Props, you can use the PropType provided by Vue to declare Props of any complexity, but as far as the type declaration is concerned, we need to do a cast with any
export type Prop<T> = { (): T } | { new(... args: any[]): T & object } | {new(... args: string[]):Function }
export type PropType<T> = Prop<T> | Prop<T>[]
Copy the code
import { createComponent } from '@vue/composition-api'
import { PropType } from 'vue'
export default createComponent({
props: {
options: (null as any) as PropType<{ msg: string }>
},
setup (props) {
props.options // { msg: string } | undefined
return{}}})Copy the code
Ii. Business practice
So far, we’ve had a good look at Vue3.0’s Composition API and some real-world business scenarios where it can be used.
What did I try to do in specific businesses? Now, let’s get into the real fight
1. Query lists in pages
Scenario: I need to perform paging query for the list in the service, including the two general query conditions, page number and page number size, and some specific query conditions, such as keywords and status.
In vue2.x, we do this in two ways, as shown in the figure
- The simplest way is to directly store the general query in one place, where the query needs to be directly introduced, and then in the page to do a series of repeated operations, this time the most test
Ctrl + C
,Ctrl + V
I can do it. - To extract its common variables and methods to
mixins
In the middle, then the page can be used directly, can avoid a lot of repetitive work. The problem comes when we have more than one paging list on our page, and my variables get washed out, causing the query to go wrong.
So for now, let’s try using the Vue3.0 feature to pull out its repetitive logic and put it in @/hooks/paging-query.ts
import { ref, Ref, reactive } from '@vue/composition-api'
import { UnwrapRef } from '@vue/composition-api/dist/reactivity'
export function usePaging () {
const conditions: UnwrapRef<{
page: Ref<number>,
pageSize: Ref<number>,
totalCount: Ref<number>
}> = reactive({
page: ref(1),
pageSize: ref(10),
totalCount: ref(1000)})const handleSizeChange = (val: number) = > {
conditions.pageSize = val
}
const handleCurrentChange = (val: number) = > {
conditions.page = val
}
return {
conditions,
handleSizeChange,
handleCurrentChange
}
}
Copy the code
We then combine them to use on specific pages
<template>
<div class="paging-demo">
<el-input v-model="query"></el-input>
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="cons.page"
:page-sizes="[10, 20, 30, 50]"
:page-size.sync="cons.pageSize"
layout="prev, pager, next, sizes"
:total="cons.totalCount">
</el-pagination>
</div>
</template>
<script lang="ts">
import { usePaging } from '@/hooks/paging-query'
import { ref, Ref, watch } from '@vue/composition-api'
export default createComponent({
setup () {
const { conditions: cons, handleSizeChange, handleCurrentChange } = usePaging()
const query: Ref<string> = ref(' ')
watch([
() = > cons.page,
() = > cons.pageSize,
() = > query.value
], ([val1, val2, val3]) = > {
console.log('Conditions changed, do search', val1, val2, val3)
})
return {
cons,
query,
handleSizeChange,
handleCurrentChange
}
}
})
</script>
Copy the code
As you can see from this example, the source of the attributes exposed to the template is very clear, returning directly from usePaging(); It can also be renamed at will, so there are no namespace conflicts. There is no performance cost associated with additional component instances.
What do you think? You got a little real smell.
2. User-select component
Scenario: In my business, there is a common business component that I call user-select, which is a people selection component. As shown in figure
About the transformation before and after the comparison we first look at a picture, so as to have a general understanding
In VUe2.x, its common business logic and data are not well handled for much the same reason as in the previous case.
Then I need to do the following every time I want to use it, which gives me a good Ctrl + C and Ctrl + V workout
<template>
<div class="demo">
<user-select
:options="users"
:user.sync="user"
@search="adminSearch" />
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { Action } from 'vuex-class'
import UserSelect from '@/views/service/components/user-select.vue'
@Component({
components: { UserSelect }
})
export default class Demo extends Vue {
user = []
users: User[] = []
@Prop()
visible: boolean
@Action('userSearch') userSearch: Function
adminSearch (query: string) {
this.userSearch({ search: query, pageSize: 200 }).then((res: Ajax.AjaxResponse) = > {
this.users = res.data.items
})
}
}
</script>
Copy the code
Can this be avoided by using the Composition API? The answer must be that it can be avoided.
Let’s first look at how the logic in the setup can be modified using Vue3.0
import { ref, computed, Ref, watch, createComponent } from '@vue/composition-api'
import { userSearch, IOption } from '@/hooks/user-search'
export default createComponent({
setup (props, { emit, root }) {
let isFirstFoucs: Ref<boolean> = ref(false)
let showCheckbox: Ref<boolean> = ref(true)
// computed
// The option is currently selected
const chooseItems: Ref<string | string[]> = ref(computed(() = > props.user))
// Option deduplicate (containing objects)
const uniqueOptions = computed(() = > {
const originArr: IOption[] | any = props.customSearch ? props.options : items.value
const newArr: IOption[] = []
const strArr: string[] = []
originArr.forEach((item: IOption) = > {
if(! strArr.includes(JSON.stringify(item))) {
strArr.push(JSON.stringify(item))
newArr.push(item)
}
})
return newArr
})
// watch
watch(() = > chooseItems.value, (val) = > {
emit('update:user', val)
emit('change', val)
})
// methods
const remoteMethod = (query: string) = > {
// You can throw out custom or use an internally integrated method to handle remote
if (props.customSearch) {
emit('search', query)
} else {
handleUserSearch(query)
}
}
const handleFoucs = (event) = > {
if (isFirstFoucs.value) {
return false
}
remoteMethod(event.target.value)
isFirstFoucs.value = true
}
const handleOptionClick = (item) = > {
emit('option-click', item)
}
// Displays the checkbox status. If the checkbox is selected, no checkbox is displayed
const isChecked = (value: string) = > {
let checked: boolean = false
if (typeof chooseItems.value === 'string') {
showCheckbox.value = false
return false
}
chooseItems.value.forEach((item: string) = > {
if (item === value) {
checked = true}})return checked
}
return {
isFirstFoucs, showCheckbox, // ref
uniqueOptions, chooseItems, // computed
handleUserSearch, remoteMethod, handleFoucs, handleOptionClick, isChecked // methods}}})Copy the code
We then pull out the reusable logic and data into hooks/user-search.ts
import { ref, Ref } from '@vue/composition-api'
export interface IOption {
[key: string]: string
}
export function userSearch ({ root }) {
const items: Ref<IOption[]> = ref([])
const handleUserSearch = (query: string) = > {
root.$store.dispatch('userSearch', { search: query, pageSize: 25 }).then(res= > {
items.value = res.data.items
})
}
return { items, handleUserSearch }
}
Copy the code
You can then use it directly in your component (you can rename it if you want)
import { userSearch, IOption } from '@/hooks/user-search'
export default createComponent({
setup (props, { emit, root }) {
const { items, handleUserSearch } = userSearch({ root })
}
})
Copy the code
Finally, to avoid naming conflicts, I now use the
component after business integration
<user-select :user.sync="user" />
Copy the code
Wow, it’s so refreshing.
conclusion
It’s time to say goodbye again.
During my first taste of Vue3.0, my overall feeling was pretty good. If you want to try some of Vue3.0’s new features in your business, you should start now.
So by the time Vue3.0 does come out, you’ll probably be familiar with how this works.
Finally, if the article is helpful to you, please click a “like”
Front-end AC group: 731175396
Reference article: Vue function-based API RFC