Home > Net >  Can i refactoring from repeating code this vue custom modifiers in multi v-model?
Can i refactoring from repeating code this vue custom modifiers in multi v-model?

Time:11-27

I made multiple v-model binding in vue 3. but got some probel in code quality when i must code emit value function repeating with 90% the function code is same. Is this possible to refactoring this code

<script lang="ts" setup>
const props = defineProps({
  firstName: String,
  lastName: String,
  firstNameModifiers: {
    default: {
      default: () => ({}),
      capitalize: () => ({}),
    },
  },
  lastNameModifiers: {
    default: {
      default: () => ({}),
      capitalize: () => ({}),
    },
  },
});
const emit = defineEmits(["update:firstName", "update:lastName"]);

// ! TODO: Make Function to handle modifiers - its repeating below
function firstNameEmitValue(e: Event) {
  const target = e.target as HTMLInputElement;
  let value = target.value;
  if (props.firstNameModifiers["capitalize"]) {
    value = value.charAt(0).toUpperCase()   value.slice(1);
  }
  emit(`update:firstName`, value);
}

function lastNameEmitValue(e: Event) {
  const target = e.target as HTMLInputElement;
  let value = target.value;
  if (props.firstNameModifiers["capitalize"]) {
    value = value.charAt(0).toUpperCase()   value.slice(1);
  }
  emit(`update:lastName`, value);
}
</script>

<template>
  <input type="text" :value="firstName" @input="firstNameEmitValue" />
  <input type="text" :value="lastName" @input="lastNameEmitValue" />
</template>

Its ok when just using 1 modifiers. But its will become annoying if i want to add other modifiers. for example toUppercase, toLowerCase etch. Maybe the solution is just separate the component for firstname input and lastname input and make it as single v-model and emit.

But i just want to try this approach bcause vue put it in, in their documentation.

Events Documentation

CodePudding user response:

You can try to factorise the two functions firstNameEmitValue and lastNameEmitValue into one function that takes the event and another argument (String or Literal) to evaluate the two cases of last and first. And on your v-on pass event and the other argument. This will look like this:

<script lang="ts" setup>
const props = defineProps({
  firstName: String,
  lastName: String,
  firstNameModifiers: {
    default: {
      default: () => ({}),
      capitalize: () => ({}),
    },
  },
  lastNameModifiers: {
    default: {
      default: () => ({}),
      capitalize: () => ({}),
    },
  },
});
const emit = defineEmits(["update:firstName", "update:lastName"]);

// ! TODO: Make Function to handle modifiers - its repeating below
function EmitValue(e: Event, type: String) {
    const target = e.target as HTMLInputElement;
    let value = target.value;
    if (type === "first") {
        if (props.firstNameModifiers["capitalize"]) {
            value = value.charAt(0).toUpperCase()   value.slice(1);
        }
        emit(`update:firstName`, value);
    } else {
        if (props.lastNameModifiers["capitalize"]) {
            value = value.charAt(0).toUpperCase()   value.slice(1);
        }
        emit(`update:lastName`, value);
    }
}

</script>

<template>
  <input type="text" :value="firstName" @input="EmitValue($event, 'first')" />
  <input type="text" :value="lastName" @input="EmitValue($event, 'last')" />
</template>

CodePudding user response:

This should do it:

<script lang="ts" setup>
  import { reactive, computed } from "vue"

  const names: string[] = ["first", "last"]
  const modifiers = {
    default: {
      default: () => ({}),
      capitalize: () => ({})
    }
  }

  const props = defineProps(
    Object.assign(
      {},
      ...names.map((name) => ({
        [name   "Name"]: String,
        [name   "NameModifiers"]: modifiers
      }))
    )
  )
  const emit = defineEmits(names.map((name) => `update:${name}Name`))
  const emitValue = ({ target }: Event, name: string) => {
    if (target instanceof HTMLInputElement) {
      let { value } = target
      if (props[`${name}NameModifiers`]["capitalize"]) {
        value = value.charAt(0).toUpperCase()   value.slice(1)
      }
      emit(`update:${name}Name`, value)
    }
  }
  const state = reactive(
    Object.assign(
      {},
      ...names.map((name) => ({ [name]: computed(() => props[name]) }))
    )
  )
</script>

<template>
  <input
    type="text"
    v-for="name in names"
    :key="name"
    :value="state[name]"
    @input="emitValue($event, name)"
  />
</template>

If you add 'middle' to names, it should work out of the box.

Note: haven't tested it, since you haven't provided a sandbox. If it doesn't work, I'll make one and test it.


I tend to avoid @input :value (although it works), in favor of v-model with computed setter. Has the advantage of not having to cast the input type and also allows assigning to model programmatically, should you ever need it. I also find the resulting syntax cleaner, hence more readable:

<script lang="ts" setup>
  import { reactive, computed } from "vue"

  const names: string[] = ["first", "last"]
  const modifiers = {
    default: {
      default: () => ({}),
      capitalize: () => ({})
    }
  }

  const props = defineProps(
    Object.assign(
      {},
      ...names.map((name) => ({
        [name   "Name"]: String,
        [name   "NameModifiers"]: modifiers
      }))
    )
  )
  const emit = defineEmits(names.map((name) => `update:${name}Name`))
  const state = reactive(
    Object.assign(
      {},
      ...names.map((name) => ({
        [name]: computed({
          get: () => props[name],
          set: (val) =>
            emit(
              `update:${name}Name`,
              props[`${name}NameModifiers`]["capitalize"]
                ? val.charAt(0).toUpperCase()   val.slice(1)
                : val
            )
        })
      }))
    )
  )
</script>

<template>
  <input type="text" v-for="name in names" :key="name" v-model="state[name]" />
</template>
  • Related