扩展 vuetify VTextField

use*_*028 5 typescript vue.js vuetify.js

我正在尝试扩展 vuetifyVTextField组件来创建可重用的password-field. 有许多道具可以控制我们需要改变的组件。Vuejs 认为 prop 突变是一种“反模式”,并对其发出警告。

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

这是一个简单的例子:

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'
        }
    }
})
Run Code Online (Sandbox Code Playgroud)

感觉应该可以使用 mixins 来扩展 VTextField 并有选择地删除我们想要用计算属性替换的 props。最后,我们需要该值是反应性的并且受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>
Run Code Online (Sandbox Code Playgroud)

这是一个如何使用该组件的简单示例

<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>
Run Code Online (Sandbox Code Playgroud)

Yom*_* S. 4

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

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

常见缺点:

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

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

<PasswordField 
  label="Enter your password"
  :revealed="revealed" 
  append-outer-icon="mdi-eye" 
  @click:append-outer="togglePassword" />
Run Code Online (Sandbox Code Playgroud)

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

实现如下:

密码字段.js

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

extends从基本组件 ( ) 中注意到这一点VTextField,并且有点“覆盖”原始render函数,返回自定义的虚拟节点(又称为 )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>
Run Code Online (Sandbox Code Playgroud)

希望能以某种方式有所帮助!