如何在 Vue 3 的自定义组件中同时使用 ref 和 v -for ?

问题描述 投票:0回答:1

我有字段验证,在提交期间我需要关注有错误的第一个输入字段

从我的父组件 CustomForm 我无法访问 CustomInput 中的子组件输入

<script setup lang="ts">
import CustomForm from '@/components/CustomForm.vue'

</script>

<template>
  <CustomForm />
</template>

<style scoped lang="scss">
</style>

<script setup lang="ts">
import CustomInput from '@/components/CustomInput.vue'
import { useForm } from '@/hooks/useForm'

const formData = [
  {
    name: 'title',
    label: 'title',
    required: true,
    value: '',
    isValid: false,
    errorMessages: []
  },
  {
    name: 'name',
    label: 'name',
    required: true,
    type: 'textarea',
    value: '',
    isValid: false,
    errorMessages: []
  }
]
const { isFormValid, fieldsForm, submit } = useForm(formData)

const submitForm = () => {
  submit()
  if (isFormValid.value) {
    console.log('submit')
    console.log(fieldsForm)
  }
}


</script>

<template>
  <form @submit.prevent="submitForm">
    <CustomInput v-for="data in fieldsForm"
                 :key="data.name"
                 ref="customInputRef"
                 :field-data="data"
                 v-model="data.value"
                 v-model:error="data.errorMessages"
                 v-model:isValid="data.isValid"
    />
    <button type="submit">Отправить</button>
  </form>
</template>

<style scoped lang="scss">

</style>

<script setup lang="ts">
import { computed, ref, watchEffect } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import type { IFieldProps } from '@/types/Field'
import { useInputValidator } from '@/hooks/useInputValidator'

const props = withDefaults(defineProps<IFieldProps>(), {
  placeholder: (props: IFieldProps) => `Input  ${props.fieldData.name.toUpperCase()} please`
})

const emit = defineEmits([
  'update:modelValue',
  'update:error',
  'update:isValid',
])
const r= ref(null)
const inputComponent = ref(props.fieldData.type !== 'textarea' ? 'input' : 'textarea')
const inputId = computed(() => `input-${uuidv4()}`)

const { isBlurred,field, blurAction,inputAction, errors, isValid } = useInputValidator(props.fieldData)

const inputHandler = (e: Event) => {
  const v = (e.target as HTMLInputElement).value
  emit('update:modelValue', v)
  inputAction()
  emit('update:error', errors)
}

const blurHandler = (e: Event) => {
  blurAction()
  emit('update:error', errors)
  emit('update:isValid', isValid)
}

</script>

<template>
  <div class="field">
    <label class="field__label"
           :for="inputId">
      {{ field.label }}
    </label>
    <div class="field__inner">
      <div class="field-icon" v-if="$slots.icon">
        <slot name="icon"></slot>
      </div>
      <component :is="inputComponent"
                 :name="field.name"
                 ref="r"
                 class="field__input"
                 :class="{
                   valid:field.isValid,
                   error:field.errorMessages.length,
                 }"
                 :id="inputId"
                 :value="field.value"
                 @input="inputHandler"
                 @blur="blurHandler"
                 :placeholder="props.placeholder" />
      <template v-if="field.errorMessages">
        <p class="field__error" v-for="(error,index) in field.errorMessages" :key="index">
          {{ error }}
        </p>
      </template>
    </div>
  </div>
</template>
import type { Field } from '@/types/Field'
import { computed, ref } from 'vue'
import { validateField } from '@/helpers/validateField'


export const useForm = (formFields: Field[]) => {
  const fieldsForm = ref(formFields)
  const isFormValid = computed(() =>
    fieldsForm.value.every(field => field.isValid)
  )


  const updateValidity = (fieldName: string, errors: string[]) => {
    const field = fieldsForm.value.find(data => data.name === fieldName)
    if (field) {
      field.errorMessages = errors
    }
  }

  const checkFields = () => {
    fieldsForm.value.forEach(field => {
      let err: string[] = []
      if (field.required) {
        if (!isFormValid.value && !field.errorMessages.length) {
          err = validateField(field.name, field.value)
          updateValidity(field.name, err)
        }
      }
    })
  }

  const submit = () => {
    checkFields()
  }

  return {
    submit,
    isFormValid,
    fieldsForm

  }
}

import { computed, ref, watchEffect } from 'vue'
import type { Field } from '@/types/Field'
import { validateField } from '@/helpers/validateField'

export const useInputValidator = (fieldForm: Field) => {
  const field = ref<Field>(fieldForm)
  const errors = ref(field.value.errorMessages)
  const isBlurred = ref(false)
  const isValid = computed(() => {
   return !errors.value.length
  })

  watchEffect(()=>{
    if(field.value.errorMessages.length){
      isBlurred.value= true
      errors.value= field.value.errorMessages
    }
  })

  const inputAction = () => {
    if (isBlurred.value) {
      errors.value = validateField(field.value.name, field.value.value)
    }
  }

  const blurAction = () => {
    if (isBlurred.value) return
    errors.value = validateField(field.value.name, field.value.value)
    isBlurred.value =true
  }

  return {
    field,
    isValid,
    errors,
    blurAction,
    inputAction,
isBlurred
  }
}

我验证了这些字段。但重点关注的功能仍然存在。我想通过钩子访问表单字段 如何更改主数组的数据,例如isValid? ...................................................... ...................................................... ..

vue.js vuejs3 ref refs
1个回答
0
投票

首先,要根据文档应用v-for内的引用,请使用数组或函数。

其次,在子组件中,使用

defineExpose()
绑定到所需元素组件引用

我创建了简单的示例

/* App.vue */
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

const formData = ref({
  firstName: '',
  lastName: '', 
})

const inputs = ref({});

const focus = (key) => {
  inputs.value[key].input.focus();
}

</script>

<template>
  <div class="inputs">
    <CustomInput
      v-for="(value, key) in formData"
      v-model="formData[key]"
      :ref="el => inputs[key] = el"
      :label="key"
    ></CustomInput>
  </div>
  <br>
  <div class="btns">
    <button
      v-for="(value, key) in inputs"
      @click="focus(key)"
    >
      Focus to {{ key }}
    </button>
  </div>
  <br>
  <pre>{{ formData }}</pre>
</template>

<style>
.inputs {
  display: grid;
  gap: 4px;
}

.btns {
  display: flex;
  gap: 8px;
}
</style>
/* CustomInput.vue */

<script setup>
import { ref } from 'vue';

defineProps({
  label: String,
})

const model = defineModel();
const input = ref();

defineExpose({ input });

</script>

<template>
  <label>
    {{ label }}:
    <br>
    <input
      ref="input"
      v-model="model"
    >
  </label>
</template>
© www.soinside.com 2019 - 2024. All rights reserved.