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.