首页 > 解决方案 > 扩展 vuetify VTextField

问题描述

我正在尝试扩展一个 vuetifyVTextField组件以创建一个可重用的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>

标签: typescriptvue.jsvuetify.js

解决方案


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

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

常见的缺点:

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

因此,为了这个目的,我们称这个组件为“PasswordField”,我们会像这样使用它:

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

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

这里是实现:

密码字段.js

  • 优点:
    • 稍微简单一些(因为不需要模板)。
    • 更快的编译时间,因为它只是一个 JS 文件,不需要经过 Vue 模板编译器和 Vue Loader。(您可以更进一步,使其成为功能组件)。
  • 缺点:
    • 事件侦听器似乎不起作用。
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'
      }
    })
  } 
}

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

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

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

密码字段.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>

希望在某种程度上有所帮助!


推荐阅读