如何让自动调整大小的文本区域作为道具传递?单独工作,但不能作为道具

How to get auto-resizing textarea passed as props? Works on it's own, but not as a prop

我正在为 textareas 开发一个新组件,并希望赋予它们做三件事的能力; autofocusdebounce(仍在努力)和 autogrow/resize 基于值。

我得到了一个可以使用的基础组件,并且得到了一些我不太熟悉的计算属性。不过据我了解,我不应该需要它们才能使这些新道具发挥作用。

我的自动对焦作为一个合格的道具工作得很好,但是 autogrow/resize 一直给我带来一些麻烦。在一个孤立的实例中,我可以让它工作,但是作为这个基础组件的道具传递,我无法让它工作。

当我尝试添加 :value="value" 以使 v 模型工作时,出现错误:

:value="value" conflicts with v-model on the same element because the latter already expands to a value binding internally

我想这是因为它与上面的 v-model="model" 冲突,但我不确定如何处理它。

我在底部附上了有效的版本。

我的问题是,我在 CcInput-App.vue 关系中做错了什么导致它无法像下面的独立版本那样工作?

Here's a link to the github repo as well!

如果您需要更多信息,请随时询问!

CcInput

<template>
  <div class="cc-input">
    <input
      v-if="propsToPass.type !== 'textarea'"
      v-bind="propsToPass"
      v-model="model"
      ref="input"
    />
    <textarea
      v-if="propsToPass.type === 'textarea'"
      v-bind="propsToPass"
      v-model="model"
      ref="textarea"
    />
  </div>
</template>


<script>

export default {
  name: 'CcInput',
  props: {
    /** v-model */
    value: { type: [String, Number], default: '' },
    valueType: { type: [String, undefined], default: undefined },
    autofocus: { type: Boolean, default: false },
    autogrow: { type: Boolean, default: false },
  },
  mounted() {
    if (this.autofocus) {
      if (this.propsToPass.type !== 'textarea') {
        this.focusInput()
      } else {
        this.focusTextArea()
      }
    }
  },
  data() {
    return {
      innerValue: this.value,
    }
  },
  methods: {
    focusInput() {
      this.$refs.input.focus()
    },
    focusTextArea() {
      this.$refs.textArea.focus()
    },
  },
  computed: {
    listenersWithoutInput() {
      return { ...this.$listeners, input: undefined }
    },
    model: {
      get() {
        return this.value
      },
      set(val) {
        const { propsToPass } = this
        if (propsToPass.type === 'number') {
          const valAsNumberIfNotNaN = !isNaN(Number(val)) ? Number(val) : val
          this.$emit('input', valAsNumberIfNotNaN)
          return
        }
        this.$emit('input', val)
      },
    },
    propsToPass() {
      const { $attrs, valueType } = this
      const type = $attrs.type ? $attrs.type : valueType === 'number' ? 'number' : undefined
      return { ...$attrs, type }
    },
  },
}
</script>

<style lang="sass">
.cc-input
  min-width: 0
  max-width: 100%
  display: flex
  flex-wrap: nowrap
  align-items: center
  position: relative
  textarea, input
    padding: 10px
    min-width: 0
    width: 100%
    min-height: 24px
    line-height: 24px
    outline: none
    box-shadow: none
    -webkit-appearance: none
    border: none
    z-index: 2
    position: relative
.cc-input
  min-width: 0
  max-width: 100%
  display: flex
  flex-wrap: nowrap
  align-items: center
  position: relative
  textarea, input
    padding: 10px
    min-width: 0
    width: 100%
    min-height: 24px
    line-height: 24px
    outline: none
    box-shadow: none
    -webkit-appearance: none
    border: none
    z-index: 2
    position: relative
    background: none
  textarea
    box-sizing: border-box
    min-height: 100px !important

  &::after, &::before
    border-radius: 4px
    position: absolute
    top: 0
    bottom: 0
    left: 0
    right: 0
    content: ''
    border-style: solid
    transition: border-color 300ms ease
  &::after
    border-width: 1px
    border-color: rgba(0, 0, 0, 0.24)
  &::before
    border-width: 2px
    border-color: transparent
  &:focus-within::after
    border-color: transparent
  &:focus-within::before
    border-color: purple
</style>

App.vue

<template>
  <div class="home">
    <div class="textarea">
        autofocus
        <CcInput valueType="textarea" :autofocus="true" />
      </div>
      <div class="textarea">
        debounce
        <CcInput valueType="textarea" :debounce="300" />
      </div>
      <div class="textarea">
        autogrow
        <CcInput valueType="textarea" :autogrow="true" />
      </div>
    </div>
  </div>
</template>

<script>
import CcInput from '../components/CcInput.vue'
export default {
  name: 'App',
  components: {
    CcInput,
  },
}
</script>

<style lang="sass" scoped>
*
  width: 50vw
  left: 50%
.textarea
  text-align: center
</style>

Autogrow.vue

<template>
  <div>
    <textarea class="textarea" name="body" id="body" @input="autogrow($event)"></textarea>
    <textarea class="textarea" name="anotherbody" id="anotherbody"></textarea>
  </div>
</template>
<script>
export default {
  name: 'Autogrow',
  },
  methods: {
    autogrow(e) {
      e.target.style.height = 'auto'
      e.target.style.height = `${e.target.scrollHeight}px`
    },
  },
}
</script>
<style lang="sass" scoped>
.textarea
  box-sizing: border-box
  width: 100%
  padding: 20px
  border-radius: 8px
</style>

错误在 propsToPass 计算 属性 中,它 defaults type to undefined when valueType is not "number":

// Ccinput.vue
export default {
  computed: {
    propsToPass() {
      const { $attrs, valueType } = this
      const type = $attrs.type ? $attrs.type : valueType === 'number' ? 'number' : undefined
      return { ...$attrs, type }
    },
  }
}

给定 "textarea"valueTypepropsToPass.typeundefined,不满足 the condition required to render the textarea 自动增长行为:

<textarea v-if="propsToPass.type === 'textarea'" ... />

解决此问题的一种方法是更新条件以检查 valueType 而不是 propsToPass.type:

<textarea v-if="valueType === 'textarea'" ... />

并更新 propsToPass.type reference 以使用 valueType:

// if (this.propsToPass.type !== 'textarea') {

if (this.valueType !== 'textarea') {

顺便说一句,你有一个 typo in this.$refs.textArea:

// this.$refs.textArea.focus()  ❌
                  ^

this.$refs.textarea.focus() ✅