【问题标题】:Extend vuetify VTextField扩展 vuetify VTextField
【发布时间】:2020-08-12 05:54:18
【问题描述】:

我正在尝试扩展 vuetify VTextField 组件以创建可重用的 password-field。有许多 props 可以控制我们需要改变的组件。 Vuejs 认为 prop 突变是一种“反模式”并警告它。

我已经尝试过声明一个计算属性来覆盖有效的道具,但它会在网络控制台中抛出有关冲突的警告。

这是一个简单的例子:

import Vue from 'vue'
import { VTextField } from 'vuetify/lib'

export default Vue.extend({
    name: 'password-field',
    mixins: [VTextField],
    data: () => ({
        reveal: false
    }),
    computed: {
        function type () {
            return this.reveal ? 'text' : 'password'
        }
    }
})

感觉应该不用使用 mixins 来扩展 VTextField 并有选择地删除我们想要用计算属性替换的道具。最后,我们需要该值是响应式的,并且在 password-field 组件的控制之下 -- 由父组件控制。

我是不是走错方向了?

更新

借助Yom S () 提供的专家建议,我能够创建VTextField 的自定义扩展。我们采纳了他的建议 #2,一个 SFC 模板化组件。

对于任何偶然发现这个主题的人,这里是 Typescript 兼容的实现:

<!-- put this in components/password-field.vue -->
<template>
  <v-text-field
    v-bind="computedProps"
    v-on:click:append="reveal = !reveal"
    v-on="listeners$"
  ></v-text-field>
</template>

<script lang="ts">
import Vue from 'vue'
import { VTextField } from 'vuetify/lib'

export default {
  name: 'PasswordField',
  extends: VTextField,

  props: {
    label: {
      type: String,
      default: 'Password'
    },
    rules: {
      type: Array,
      default: () => [(v: string) => {
        return /((?=.*\d)(?=.*[a-z])(?=.*[!@#$%^&*()?.]).{8,})/i.test(v) ||
          'At least 8 char; upper and lowercase, a number and a special char'
      }]
    }
  },

  data: () => ({
    reveal: false
  }),

  computed: {
    computedProps: function () {
      return {
        ...this.$props,
        type: this.reveal ? 'text' : 'password',
        appendIcon: this.reveal ? 'mdi-eye' : 'mdi-eye-off'
      }
    }
  }

} as Vue.ComponentOptions<Vue>
</script>

这里有一个如何使用这个组件的简单例子

<template>
  <v-form v-model="formValid">
    <password-field v-model="newPassword/>
    <v-btn :disabled="!formValid">Change</v-btn>
  </v-form>
</template>

<script lang="ts">
import Vue from 'vue'
import PasswordField from '@/components/password-field.vue'

export default Vue.extend({
  name: 'ChangePasswordForm',
  data: () => ({
    formValid: false,
    newPassword: ''
  })
})
</script>

【问题讨论】:

  • 感谢您的链接...但这还不是答案。我们可以轻松地覆盖扩展中的道具。问题是我们需要它具有反应性并受子组件的控制。这需要我们改变 prop,这是不能/不应该做的。
  • 我为不同的目的定制了几个 Vuetify 组件,但与您想要实现的目标不同。对于我的案例,我使用了计算的 props,但也使用了作为函数传递的 props,允许自定义组件根据它获得的 props 函数列表做出不同的响应和行为

标签: typescript vue.js vuetify.js


【解决方案1】:

如果这个特定的 type 属性是 sync-able 会很有帮助;但既然不是这样,您可以通过重新渲染VTextField 来解决这个问题,同时也可以从它扩展。

现在,我不能说这是最好的解决方案,因为它有一些缺点,使其成为有缺陷的包装器。但它确实根据您的要求为您提供所需的东西。

常见缺点:

  • 作用域槽(例如appendappend-outer)不会输出预期的元素。

因此,为此,我们将这个组件称为“PasswordField”,我们可以这样使用它:

<PasswordField 
  label="Enter your password"
  :revealed="revealed" 
  append-outer-icon="mdi-eye" 
  @click:append-outer="togglePassword" />

append-outer-icon 和图标切换机制可能应该封装在组件本身中。

下面是实现:

PasswordField.js

  • 优点:
    • 稍微简单一些(因为不需要模板)。
    • 更快的编译时间,因为它只是一个 JS 文件,不需要经过 Vue 模板编译器和 Vue Loader。 (您可以更进一步,将其设为functional component)。
  • 缺点:
    • 事件侦听器似乎不起作用。
import { VTextField } from 'vuetify/lib';

export default {
  name: 'PasswordField',
  extends: VTextField,

  props: {
    revealed: {
      type: Boolean,
      default: false
    }
  },

  render(h) {
    const { revealed, ...innerProps } = this.$options.propsData;

    return h(VTextField, {
      // For some reason this isn't effective
      listeners: this.$listeners,

      props: {
        ...innerProps,
        type: revealed ? 'text' : 'password'
      }
    })
  } 
}

请注意基本组件 (VTextField) 中的 extends 并“覆盖”原始 render 函数,返回自定义虚拟节点,也称为 VNode

但是,如前所述,这有一些缺点,它无法侦听已发出的事件。 (我很想知道是否有人对此有解决方案)。

所以,作为最后的手段,让我们实际使用模板和计算道具,字面意思(我们希望props 部分是唯一要绑定的属性,减去data)。

PasswordField.vue

  • 优点:
    • 更可靠,功能更明智。
    • 事件侦听器将正常工作。
    • 当然,SFC 以这种方式效果最好。
  • 缺点:
    • 有些重复,因为您必须手动(重新)绑定道具和注册事件。
    • 编译时间较慢(无论如何应该几乎看不到)。
<template>
  <v-text-field
    v-bind="computedProps"
    v-on="$listeners">
  </v-text-field>
</template>

<script>
  import { VTextField } from 'vuetify/lib';

  export default {
    name: 'PasswordField',
    extends: VTextField,

    props: {
      revealed: {
        type: Boolean,
        default: false
      }
    },

    computed: {
      computedProps() {
        return {
          ...this.$props,
          type: this.revealed ? 'text' : 'password'
        }
      }
    }
  }
</script>

希望在某些方面有所帮助!

【讨论】:

  • 这正是我所需要的!谢谢
  • 这是一个糟糕的主意,仅供参考,您最终会将所有原始观察者和计算属性留在原地,这可能会以有趣和意想不到的方式破坏事物。只需将原始组件 (stackoverflow.com/a/70064606/2074736) 包装起来,您添加的任何额外内容都不会干扰原始道具。
  • @KaelWatts-Deuchar 这到底是怎么发生的?有什么可验证的例子吗?
  • 一个不同的组件,但 github.com/vuetifyjs/vuetify/issues/14411
  • @KaelWatts-Deuchar 你能帮助我们让它变得不那么糟糕,并在这个official example 中添加一些注释吗:) 另外,你能解释一下为什么它从Vue.options.components["VAutocomplete"] 扩展时有效(如在例子)?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-12
  • 1970-01-01
  • 2019-03-18
  • 1970-01-01
  • 1970-01-01
  • 2020-10-04
  • 1970-01-01
相关资源
最近更新 更多