扩展 vuetify VTextField [英] Extend vuetify VTextField

查看:21
本文介绍了扩展 vuetify VTextField的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to extend a vuetify VTextField component to create a reusable password-field. There are a number of props that control the component that we need to mutate. Vuejs considers prop mutation an "anti-pattern" and warnings against it.

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

I've experimented with declaring a computed-property that overrides the prop which works, but it tosses warnings in the web-console about the conflict.

这是一个简单的例子:

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'
        }
    }
})

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

It feels like there should be away to use mixins to extend the VTextField and selectively drop the props we want to replace with computed-properties. In the end, we need the value to be reactive and under the control of the password-field component -- not controlled by the parent.

我是不是走错方向了?

更新

根据 Yom S () 提供的专家建议,我能够创建<代码>VTextField.我们采纳了他的建议 #2,一个 SFC 模板化组件.

With the expert advice provided by Yom S (), I was able to create a custom extension of VTextField. We went with his suggestion #2, an SFC templated component.

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

For anyone else who stumbles across this topic, here is the Typescript compatible implementation:

<!-- 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>

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

Here's a simple example of how to use this component

<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>

推荐答案

如果这个特定的 type 道具是 sync-able;但由于它不是,你可以通过重新渲染 VTextField 来解决这个问题,同时也从它扩展.

It would've been helpful if this particular type prop was sync-able; but since it's not, you could work around this by sort of re-rendering the VTextField, while also extending from it.

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

Now, I can't say this is the best solution as it has some drawbacks that make it a flawed wrapper. But it does get you what you need, as per your requirement in question.

共同缺点:

  • 作用域插槽(例如 appendappend-outer)不会输出预期的元素.
  • Scoped slots (e.g. append, append-outer) will not output the expected element(s).

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

So for this purpose, let's call this component "PasswordField", and we would use it like:

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

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

The append-outer-icon and the icon-toggling mechanism should probably be encapsulated within the component itself.

这里是实现:

  • 优点:
    • 稍微简单一些(因为不需要模板).
    • 更快的编译时间,因为它只是一个 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 函数,返回自定义的虚拟节点又名 VNode.

    Notice this extends from the base component (VTextField) and sort of "overrides" the original render function, returning customized virtual node a.k.a. VNode.

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

    However, as mentioned earlier, this has some drawback where it fails to listen to emitted events. (I would love to know if someone has a solution for this).

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

    So, as a last resort, let's actually use a template and computed props, literally (we want the props part being the only properties to bind to, minus the data).

    • 优点:
      • 更可靠,功能更强大.
      • 事件侦听器将正常工作.
      • 当然,证监会以这种方式工作得最好.
      • 有点重复,因为您必须手动(重新)绑定道具和注册事件.
      • 较慢的编译时间(无论如何应该不会引起注意).
      <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>
      

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

      Hope that helps in some way!

      这篇关于扩展 vuetify VTextField的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆