programing

vue 컴포넌트에 다른 입력을 입력할 때 입력 파일 값이 누락되는 이유는 무엇입니까?

firstcheck 2022. 8. 8. 15:24
반응형

vue 컴포넌트에 다른 입력을 입력할 때 입력 파일 값이 누락되는 이유는 무엇입니까?

2개의 컴포넌트가 있습니다.

첫 번째 컴포넌트(부모 컴포넌트)는 다음과 같습니다.

<template>
    <div>
        ...
            <form-input id="name" name="name" v-model="name">Name</form-input>
            <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
            <form-input id="avatar" name="avatar" type="file" v-on:triggerChange="onFileChange($event)">Avatar</form-input>
            <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
        ...
    </div>
</template>
<script>
    export default {
        data() {
            return {
                name: null,
                birthDate:  null,
                mobileNumber: null
            }
        },
        methods: {
            onFileChange(e) {
                let self = this
                this.validate(e.target.files[0])
                .then(function(res) {
                    let files = e.target.files,
                    reader = new FileReader()
                    // if any values
                    if (files.length) {
                        self.removeErrorMessageUpload()
                        self.files = files[0]
                        reader.onload = (e) => {
                            self.updateProfileAvatar(e.target.result)
                        }
                        reader.readAsDataURL(files[0])
                    }
                })
                .catch(function() {
                    // do something in the case where the image is not valid
                    self.displayErrorMessageUpload()
                })
            },
            validate(image) {
                let self = this
                return new Promise(function(resolve, reject) {
                    // validation file type
                    if (!self.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
                        reject()
                    }
                    // validation file size
                    if (image.size > self.maximumSize) {
                        reject()
                    }
                    // validation image resolution
                    let img = new Image()
                    img.src = window.URL.createObjectURL(image)
                    img.onload = function() {
                        let width = img.naturalWidth,
                            height = img.naturalHeight

                        window.URL.revokeObjectURL(img.src)

                        if (width != 100 && height != 100) {
                            reject()
                        } 
                        else {
                            resolve()
                        }
                    }
                })         
            },
        }
    }
</script>

상위 구성 요소에서 하위 구성 요소(폼 입력 구성 요소)를 호출합니다.

자녀 구성요소는 입력 유형 텍스트, 입력 유형 날짜, 입력 유형 파일 및 입력 유형 번호입니다.나는 그것들을 모두 하나의 컴포넌트로 결합한다.

자구성요소는 다음과 같습니다.

<template>
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
            <input :type="type" :name="name" :id="id" class="form-control" :value="value" v-on:change="applySelected($event)" @input="$emit('input', $event.target.value)">
        </div>
    </div>
</template>

<script>
    export default {
        name: "form-input",
        props: {
            'id': String,
            'name': String,
            'isRequired': {
                type: Boolean,
                default: true
            },
            'type': {
                type: String,
                default() {
                    if(this.type == 'number')
                        return 'number'
                    return 'text'
                }
            },
            'value': {
                type: [String, Number]
            }
        },
        methods: {
            applySelected(e) {
                this.$emit('triggerChange', e)
            }
        }
    }
</script>

1개의 컴포넌트로 결합하기 때문에 새로운 문제가 발생합니다.

입력 유형 파일을 입력하면 파일 값이 입력 유형 파일에 표시됩니다.

그러나 입력 유형 텍스트에 입력하면 입력 유형 파일의 값이 누락됩니다.

입력 형식 파일의 값이 누락된 이유는 무엇입니까?

데모:

Vue.component('form-input', {
  template: "#form-input-tpl",
  name: "form-input",
  props: {
    'id': String,
    'name': String,
    'isRequired': {type: Boolean, default: true},
    'type': { type: String, default () {if (this.type == 'number') {return 'number'} else {return 'text'}}},
    'value': { type: [String, Number] }
  },
  methods: {
    applySelected(e) { this.$emit('triggerChange', e) }
  }
});

new Vue({
  el: '#app',
  data: {
    name: null,
    birthDate: null,
    mobileNumber: null
  },
  methods: {
    onFileChange(e) {
      // ...
    }
  }
})
<script src="https://unpkg.com/vue"></script>

<template id="form-input-tpl">
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
            <input :type="type" :name="name" :id="id" class="form-control" :value="value" v-on:change="applySelected($event)" @input="$emit('input', $event.target.value)">
        </div>
    </div>
</template>

<div id="app">
  <h3>Select a file, then type a name. The file will be reset.</h3>
  <div>
    <form-input id="name" name="name" v-model="name">Name</form-input>
    <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
    <form-input id="avatar" name="avatar" type="file" v-on:triggerChange="onFileChange($event)">Avatar</form-input>
    <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
  </div>
</div>

문제는 다음과 같습니다.

에서 파일을 선택한 후<form-input type="file">, 에 무언가를 입력했을 경우<form-input type="type">,그<form-input type="file">소거합니다.왜 그런 것일까요?

이 문제가 발생하는 이유는 편집 시<form-input type="text">Vue는 컴포넌트를 "보수"합니다.

그리고 그것이 후회할 때<form-input type="file">새로운 버전이기 때문에 "Nothing selected"로 돌아갑니다.<input type="file">.


솔루션:파일 값 유지

Kaiido코멘트에서 지적한 바와 같이 최신 버전의 브라우저에서는 파일 설정을 할 수 있습니다.<input type="file">표준적인 방법으로

아래 코드는 이렇게 동작합니다.감시하고 있습니다.value속성(부모가 를 사용하는 경우)v-model그 값을 로 설정합니다..files의 특성<input type="file">.

우리는 두 개를 사용해야 한다.<input>(와 함께)v-if/v-else)왜냐하면,<input type="file">,그:value속성을 설정할 수 있습니다.이벤트 핸들러는 달라야 합니다( )@change="$emit('input', $event.target.files)") 、 ) ) 、 、ref이 경우,files.

아래 전체 작업 데모입니다.

Vue.component('form-input', {
  template: "#form-input-tpl",
  name: "form-input",
  props: {
    'id': String,
    'name': String,
    'isRequired': {type: Boolean, default: true},
    'type': {type: String, default: 'text'},
    'value': {type: [String, Number, FileList, DataTransfer]}
  },
  mounted() {
    // set files upon creation or update if parent's value changes
    this.$watch('value', () => {
      if (this.type === "file") { this.$refs.inputFile.files = this.value; }
    }, { immediate: true });
  }
});

new Vue({
  el: '#app',
  data: {
    name: null,
    birthDate: null,
    mobileNumber: null,
    files: null
  }
})
<script src="https://unpkg.com/vue"></script>

<template id="form-input-tpl">
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
           <input v-if="type !== 'file'" :type="type" :name="name" :id="id" class="form-control" :value="value" @input="$emit('input', $event.target.value)">
            
           <input v-else :type="type" :name="name" :id="id" class="form-control" @change="$emit('input', $event.target.files)" ref="inputFile">
        </div>
    </div>
</template>

<div id="app">
  <div>
    <form-input id="name" name="name" v-model="name">Name</form-input>
    <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
    <form-input id="avatar" name="avatar" type="file" v-model="files">Avatar</form-input>
    <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
  </div>
</div>

를 사용하여file-change이벤트 및validate기능:

Vue.component('form-input', {
  template: "#form-input-tpl",
  name: "form-input",
  props: {
    'id': String,
    'name': String,
    'isRequired': {type: Boolean, default: true},
    'type': {type: String, default: 'text'},
    'value': {type: [String, Number, FileList, DataTransfer]}
  },
  mounted() {
    // set files upon creation or update if parent's value changes
    this.$watch('value', () => {
      if (this.type === "file") { this.$refs.inputFile.files = this.value; }
    }, { immediate: true });
  }
});

new Vue({
  el: '#app',
  data: {
    name: null,
    birthDate: null,
    mobileNumber: null,
    filesVModel: null,
    allowableTypes: ['jpg', 'jpeg', 'png'],
    maximumSize: 1000,
    files: null
  },
  methods: {
    onFileChange(e) {
      console.log('onfilechange!');
      let self = this
      this.validate(e.target.files[0])
        .then(function(res) {
          let files = e.target.files,
            reader = new FileReader()
          // if any values
          if (files.length) {
            self.removeErrorMessageUpload()
            self.files = files[0]
            reader.onload = (e) => {
              self.updateProfileAvatar(e.target.result)
            }
            reader.readAsDataURL(files[0])
          }
        })
        .catch(function(err) {
          // do something in the case where the image is not valid
          self.displayErrorMessageUpload(err)
        })
    },
    validate(image) {
      let self = this
      return new Promise(function(resolve, reject) {
        // validation file type
        if (!self.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
          reject("Type " + image.name.split(".").pop().toLowerCase() + " is not allowed.")
        }
        // validation file size
        if (image.size > self.maximumSize) {
          reject("Image size " + image.size + " is larger than allowed " + self.maximumSize + ".")
        }
        // validation image resolution
        let img = new Image()
        img.src = window.URL.createObjectURL(image)
        img.onload = function() {
          let width = img.naturalWidth,
            height = img.naturalHeight

          window.URL.revokeObjectURL(img.src)

          if (width != 100  && height != 100) {
            reject("Width and height are " + width + " and " + height + " and not both 100")
          } else {
            resolve()
          }
        }
      })
    },
    displayErrorMessageUpload(msg) {
      console.log('displayErrorMessageUpload', msg);
    },
    removeErrorMessageUpload() {
      console.log('removeErrorMessageUpload');
    },
    updateProfileAvatar(result) {
      console.log('updateProfileAvatar', result);
    }
  }
})
<script src="https://unpkg.com/vue"></script>

<template id="form-input-tpl">
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
           <input v-if="type !== 'file'" :type="type" :name="name" :id="id" class="form-control" :value="value" @input="$emit('input', $event.target.value)">
            
           <input v-else :type="type" :name="name" :id="id" class="form-control" @change="$emit('input', $event.target.files)" ref="inputFile" v-on:change="$emit('file-change', $event)">
        </div>
    </div>
</template>

<div id="app">
  <div>
    <form-input id="name" name="name" v-model="name">Name</form-input>
    <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
    <form-input id="avatar" name="avatar" type="file" v-model="filesVModel" @file-change="onFileChange">Avatar</form-input>
    <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
  </div>
</div>

언급URL : https://stackoverflow.com/questions/49337786/why-the-value-of-input-file-missing-when-i-input-the-another-input-on-the-vue-co

반응형