参考element源码用vue写一个input的受控组件

2022-10-28 15:24:07 浏览数 (3)

在react当中,表单元素 input 中设置了 value ,则为受控组件,通过 onChange 事件中 setState() 改变 value 值来更新 state 值和DOM中渲染的值。但在vue中,表单元素设置 value 值,即使 value 值改变了,dom中 value 的表现也和data中的 value 不一致

vue和react中受控组件的不同

在 HTML 中,表单元素(如 <input><textarea><select> )通常自己维护 state ,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState() 来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生

代码语言:javascript复制
import React, { useState } from "react";

export default function App() {
  const [vale, setValue] = useState(0);
  const changeEvent = function(e) {
    console.log(e);
    setValue(123);//value和渲染的值都为123
  }
  return (
    <div className="App">
      <input type="text" value={value} onChange={changeEvent} /> 
    </div>
  );
}
复制代码

而在vue中,改变 value 的值,只有 data 中的状态改变了,而原生DOM中的 value 值并没有被改变,最终渲染出来的仍然为用户输入的值

代码语言:javascript复制
<template>
  <input :value="value" @input="setValue" />
</template>

<script>
export default {
  name: "App",
  components: {},
  data: function () {
    return {
      value: '',
    };
  },
  methods: {
    setValue(e) {
      this.value = 123//仅仅data中value的值改变了,DOM中渲染的value值仍为输入的值
    }
  }
};
</script>
复制代码

用vue写一个input受控组件

在日常业务中,受控组件的需求经常被用到,用来给input框输入的限制,例如一个仅可以输入数字的 input 框。在使用elementUI的时候,发现其 <el-input> 为受控组件,于是去 elementUI-github 上看了这种操作是如何实现的。

核心原理就是在更新自己的 data 值的同时,一起更新原生input DOM上的 value

代码如下:

首先写一个完全像一个普通的 <input> 元素一样使用的 <CtrlInput> 组件

代码语言:javascript复制
<template>
  <label>
    <input v-bind="$attrs" :value="value" v-on="inputListeners" />
  </label>
</template>

<script>
export default {
  name: "CtrlInput",
  props: {
    value: {
      type: String
    }
  },
  computed: {
    inputListeners() {
      return {
          //从父级添加所有的监听器
        ...this.$listeners,
        // 然后我们添加自定义监听器
        // 这里确保组件配合 `v-model` 的工作
        input: e => {
          this.$emit("input", e.target.value);
        }
      };
    }
  }
};
</script>

<style scoped></style>
复制代码

所有跟原生 input 相同的 attribute 和监听器都可以正常工作,并且确保组件配合 v-model 也可以工作

然后在 input 监听器中,设置 nativeInputValue (原生DOM的 value 值)和 data 中的 value 一样即可。

代码语言:javascript复制
<template>
  <label>
    <input ref="input" v-bind="$attrs" :value="value" v-on="inputListeners" />
  </label>
</template>

<script>
export default {
  name: "CtrlInput",
  props: {
    value: {
      type: String
    }
  },
  computed: {
    inputListeners() {
      return {
        //从父级添加所有的监听器
        ...this.$listeners,
        // 然后我们添加自定义监听器
        // 这里确保组件配合 `v-model` 的工作
        input: e => {
          this.$emit("input", e.target.value);
          // 保证原生的input value 是可控的
          // ensure native input value is controlled
          this.$nextTick(this.setNativeInputValue);
        }
      };
    },
    nativeInputValue() {
      //将传入的值转为String,防止出错
      return this.value === null || this.value === undefined
        ? ""
        : String(this.value);
    }
  },
  methods: {
    setNativeInputValue() {
      // 将展示的原生的input value 和this中的input value保持一致
      const input = this.$refs.input;
      if (!input) return;
      if (input.value === this.nativeInputValue) return;
      input.value = this.nativeInputValue;
    }
  },
  watch: {
    //将展示的原生的input value 和this中的input value保持一致
    nativeInputValue() {
      this.setNativeInputValue();
    }
  }
};
</script>

<style scoped></style>

复制代码

this.nextTick(this.setNativeInputValue); 这行代码意思就是在 data 中的 value 改变完,并且渲染完成后(使用 nextTick )再改变 nativeInputValue 的值,即可让原生DOM和自身的state保持一致

使用

需求:仅可输入数字的input框,输入其他字符就不显示

代码语言:javascript复制
<template>
  <div>
    仅可以输入数字的受控组件
    <CtrlInput
      type="text"
      placeholder="请输入数字"
      :value="value"
      @input="handleInput"
    ></CtrlInput>
  </div>
</template>

<script>
import CtrlInput from "./CtrlInput";
export default {
  name: "Demo",
  components: {
    CtrlInput
  },
  data() {
    return {
      value: ""
    };
  },
  methods: {
    handleInput(v) {
      if (/^[0-9]*$/.test(v)) {
        this.value = v;
      }
    }
  }
};
</script>
复制代码

1 人点赞