如何让自动调整大小的文本区域作为道具传递?单独工作,但不能作为道具
How to get auto-resizing textarea passed as props? Works on it's own, but not as a prop
我正在为 textareas 开发一个新组件,并希望赋予它们做三件事的能力; autofocus
、debounce
(仍在努力)和 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"
的 valueType
,propsToPass.type
是 undefined
,不满足 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() ✅
我正在为 textareas 开发一个新组件,并希望赋予它们做三件事的能力; autofocus
、debounce
(仍在努力)和 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"
的 valueType
,propsToPass.type
是 undefined
,不满足 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() ✅