Vue3 has been out for more than a year now and the @vue/ composition-API is stable. Recently, I want to use @vue/ composition-API to transform the project components.
This article focuses on the process of refactoring a widget. For details about the Composition API, visit the website.
Of course, this component is relatively simple and does not necessarily require refactoring.
The first is how to write the Options API
<template>
<div>
<span v-if="tags && tags.length" class="tags">
<el-tag
v-for="(item, index) in tags"
type="success"
:key="item"
:closable="true"
:close-transition="false"
@close="handleCloseTag(index)"
>
{{ item }}
</el-tag>
</span>
<el-input
v-if="tagVisible"
v-model="tag"
class="tag-input"
:placeholder="placeholder"
@blur="handleAddTag()"
@keyup.enter.native="handleAddTag()"
></el-input>
<el-button
v-else
size="small"
icon="el-icon-plus"
@click="tagVisible = true"
>addition</el-button>
</div>
</template>
<script>
export default {
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},watch: {
value(newValue, oldValue) {
if(newValue ! == oldValue) {this.tags = newValue
}
}
},
data() {
return { tags: this.value, tag: ' '.tagVisible: false}},methods: {
handleAddTag() {
const { tag, tags } = this
if(tag && ! tags.includes(tag)) { tags.push(tag) }this.tagVisible = false
this.tag = ' '
},
handleCloseTag(index) {
this.tags.splice(index, 1)}}}</script>
<style lang="scss" scoped>
.tags {
.el-tag {
margin-right: 10px; }}.tag-input {
width: 120px;
}
</style>
Copy the code
After a preliminary reconstruction
<script>
import { defineComponent, ref, watch } from '@vue/composition-api'
export default defineComponent({
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},setup(props) {
const tags = ref(props.value)
const tag = ref(' ')
const tagVisible = ref(false)
watch(
() = > props.value,
(newValue, oldValue) = > {
if(newValue ! == oldValue) { tags.value = newValue } } )function handleAddTag() {
if(tag.value && ! tags.value.includes(tag.value)) { tags.value.push(tag.value) } tagVisible.value =false
tag.value = ' '
}
function handleCloseTag(index) {
tags.value.splice(index, 1)}return {
tags,
tagVisible,
tag,
handleCloseTag,
handleAddTag
}
}
})
</script>
Copy the code
Too much.value is not friendly. Optimize a little
function handleAddTag() {
if(tag.value && ! tags.value.includes(tag.value)) { tags.value.push(tag.value) } tagVisible.value =false
tag.value = ' '} insteadfunction handleAddTag() {
const tagValue = tag.value
const tagsValue = tags.value
if(tagValue && ! tagsValue.includes(tagValue)) { tagsValue.push(tagValue) } tagVisible.value =false
tag.value = ' '
}
Copy the code
Then organize the code into logical functions
<script>
import { defineComponent, ref, watch } from '@vue/composition-api'
export default defineComponent({
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},setup(props) {
const tags = ref(props.value)
watch(
() = > props.value,
(newValue, oldValue) = > {
if(newValue ! == oldValue) { tags.value = newValue } } )function handleCloseTag(index) {
tags.value.splice(index, 1)}const tag = ref(' ')
const tagVisible = ref(false)
function handleAddTag() {
const tagValue = tag.value
const tagsValue = tags.value
if(tagValue && ! tagsValue.includes(tagValue)) { tagsValue.push(tagValue) } tagVisible.value =false
tag.value = ' '
}
return {
tags,
handleCloseTag,
//
tagVisible,
tag,
handleAddTag
}
}
})
</script>
Copy the code
Refining function
function handleAddTag() {
const tagValue = tag.value
const tagsValue = tags.value
if(tagValue && ! tagsValue.includes(tagValue)) { tagsValue.push(tagValue) } tagVisible.value =false
tag.value = ' '} insteadfunction addTag(tag) {
if(! tag) {return
}
consttagsValue = tags.value ! tagsValue.includes(tag) && tagsValue.push(tag) }function handleAddTag() {
addTag(tag.value)
tag.value = ' '
tagVisible.value = false
}
Copy the code
Encapsulation variable
const tags = ref(props.value)
watch(
() = > props.value,
(newValue, oldValue) = > {
if(newValue ! == oldValue) {tags.value = newValue}}) insteadconst tags = ref(getModelValue())
watch(getModelValue, (newValue, oldValue) = > {
if(newValue ! == oldValue) { tags.value = newValue } })function getModelValue() {
return props.value
}
Copy the code
It looks a little bit better now
<script>
import { defineComponent, ref, watch } from '@vue/composition-api'
export default defineComponent({
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},setup(props) {
const tags = ref(getModelValue())
watch(getModelValue, (newValue, oldValue) = > {
if(newValue ! == oldValue) { tags.value = newValue } })function getModelValue() {
return props.value
}
function addTag(tag) {
if(! tag) {return
}
consttagsValue = tags.value ! tagsValue.includes(tag) && tagsValue.push(tag) }function handleCloseTag(index) {
tags.value.splice(index, 1)}const tag = ref(' ')
const tagVisible = ref(false)
function handleAddTag() {
addTag(tag.value)
tag.value = ' '
tagVisible.value = false
}
return {
tags,
handleCloseTag,
//
tagVisible,
tag,
handleAddTag
}
}
})
</script>
Copy the code
I don’t know if I can go any further (.value is really uncomfortable).
Encapsulates a dataRef
function dataRef(value) {
return {
ref: ref(value),
get() {
return this.ref.value
},
set(value) {
this.ref.value = value
}
}
}
Copy the code
constTags = ref(getModelValue()const Tags = dataRef(getModelValue())
Copy the code
Uppercase is used to make finding.get () look like a static method, and the value variable does not need to be renamed
const tags = Tags.get()
Copy the code
Now it looks like this
<script>
import { defineComponent, ref, watch } from '@vue/composition-api'
function dataRef(value) {
return {
ref: ref(value),
get() {
return this.ref.value
},
set(value) {
this.ref.value = value
}
}
}
export default defineComponent({
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},setup(props) {
const Tags = dataRef(getModelValue())
watch(getModelValue, (newValue, oldValue) = > {
if(newValue ! == oldValue) { Tags.set(newValue) } })function getModelValue() {
return props.value
}
function addTag(tag) {
if(! tag) {return
}
consttags = Tags.get() ! tags.includes(tag) && tags.push(tag) }function handleCloseTag(index) {
Tags.get().splice(index, 1)}const Tag = dataRef(' ')
const TagVisible = dataRef(false)
function handleAddTag() {
addTag(Tag.get())
Tag.set(' ')
TagVisible.set(false)}return {
tags: Tags.ref,
handleCloseTag,
//
tagVisible: TagVisible.ref,
tag: Tag.ref,
handleAddTag
}
}
})
</script>
Copy the code
Can you go further?
Watch has an if judgment before assignment
The handleAddTag function has a reset operation after executing the addTag function
function dataRef(value) {
return {
ref: ref(value),
get() {
return this.ref.value
},
set(value) {
this.ref.value = value}}import clone from 'rfdc/default'
function dataRef(value, { isSkipSameValue } = { isSkipSameValue: false }) {
const _defaultValue = clone(value)
return {
ref: ref(value),
get() {
return this.ref.value
},
set(value) {
if (isSkipSameValue && this.get() === value) {
return
}
this.ref.value = value
},
reset() {
this.set(clone(_defaultValue))
}
}
}
Copy the code
The refactoring is complete, and the final result is as follows
<template>
<div>
<span v-if="tags && tags.length" class="tags">
<el-tag
v-for="(item, index) in tags"
type="success"
:key="item"
:closable="true"
:close-transition="false"
@close="handleCloseTag(index)"
>
{{ item }}
</el-tag>
</span>
<el-input
v-if="tagVisible"
v-model="tag"
class="tag-input"
:placeholder="placeholder"
@blur="handleAddTag()"
@keyup.enter.native="handleAddTag()"
></el-input>
<el-button
v-else
size="small"
icon="el-icon-plus"
@click="tagVisible = true"
>addition</el-button>
</div>
</template>
<script>
import { defineComponent, ref, watch } from '@vue/composition-api'
import clone from 'rfdc/default'
function dataRef(value, { isSkipSameValue } = { isSkipSameValue: false }) {
const _defaultValue = clone(value)
return {
ref: ref(value),
get() {
return this.ref.value
},
set(value) {
if (isSkipSameValue && this.get() === value) {
return
}
this.ref.value = value
},
reset() {
this.set(clone(_defaultValue))
}
}
}
export default defineComponent({
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},setup(props) {
const Tags = dataRef(getModelValue() || [], { isSkipSameValue: true })
watch(getModelValue, Tags.set.bind(Tags))
function getModelValue() {
return props.value
}
function addTag(tag) {
if(! tag) {return
}
consttags = Tags.get() ! tags.includes(tag) && tags.push(tag) }function handleCloseTag(index) {
Tags.get().splice(index, 1)}const Tag = dataRef(' ')
const TagVisible = dataRef(false)
function handleAddTag() {
addTag(Tag.get())
Tag.reset()
TagVisible.reset()
}
return {
tags: Tags.ref,
handleCloseTag,
//
tagVisible: TagVisible.ref,
tag: Tag.ref,
handleAddTag
}
}
})
</script>
<style lang="scss" scoped>
.tags {
.el-tag {
margin-right: 10px; }}.tag-input {
width: 120px;
}
</style>
Copy the code
If setup is too long, you can continue refining the functions, leaving only the trunk
import { defineComponent, ref, watch } from '@vue/composition-api'
import clone from 'rfdc/default'
function dataRef(value, { isSkipSameValue } = { isSkipSameValue: false }) {
const _defaultValue = clone(value)
return {
ref: ref(value),
get() {
return this.ref.value
},
set(value) {
if (isSkipSameValue && this.get() === value) {
return
}
this.ref.value = value
},
reset() {
this.set(clone(_defaultValue))
}
}
}
function useTags(props) {
const Tags = dataRef(getModelValue() || [], { isSkipSameValue: true })
watch(getModelValue, Tags.set.bind(Tags))
function getModelValue() {
return props.value
}
function addTag(tag) {
if(! tag) {return
}
consttags = Tags.get() ! tags.includes(tag) && tags.push(tag) }function handleCloseTag(index) {
Tags.get().splice(index, 1)}return { addTag, tags: Tags.ref, handleCloseTag }
}
function useTag() {
const Tag = dataRef(' ')
const TagVisible = dataRef(false)
return {
tag: Tag.ref,
tagVisible: TagVisible.ref,
createHandleAddTag(callback) {
return function () {
callback(Tag.get())
Tag.reset()
TagVisible.reset()
}
}
}
}
export default defineComponent({
name: 'TagsContainer'.props: {
value: { type: Array.default: () = >[]},placeholder: { type: String.default: ' '}},setup(props) {
const { addTag, tags, handleCloseTag } = useTags(props)
const { createHandleAddTag, tag, tagVisible } = useTag()
return {
tags,
handleCloseTag,
//
tag,
tagVisible,
handleAddTag: createHandleAddTag(addTag)
}
}
})
</script>
Copy the code
Become code farmer almost 5 years, front-end do less add up to less than a year, JQuery Backbone Angular Vue React and other mainstream frameworks are used to develop projects, in nuggets read so many articles, the first post, please forgive me.