【Vue】数据绑定方式

199 阅读4分钟

前言

  • 单向绑定:就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。因此,我们不需要进行额外的DOM操作,只需要进行Modal的操作就可以实现试图的联动更新
  • 双向绑定:在单向绑定的基础上,用户更新了ViewModel的数据自动被更新了,这种情况就是双向绑定。所以,当我们用JavaScript代码更新Model时,View就会自动更新,反之,如果用户更新了View,Model的数据也自动被更新了。

一、单向绑定

实现思路:

  1. 所有数据只有一份
  2. 一旦数据变化,就去更新页面(只有data->DOM,没有DOM->data)
  3. 若用户在页面上做了更新,就需要用户手动收集

1.1 插值语法:v-text

  • 官网:【Vue】【指令】v-text

  • 参数类型:String

  • 示例:

    <span v-text="msg"></span>
    <!-- 等同于 -->
    <span>{{msg}}</span>
    

1.2 属性绑定:v-bind

  • 参考:

  • 参数类型:any - 不限制数据类型,基础类型或复杂类型都可以

  • 作用:动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

  • 修饰符:

    • .prop

      v-bind 默认绑定到 DOM 节点的 attribute 上,使用 .prop 修饰符后,会绑定到 property

      • 作用/用途:

        1)通过自定义属性存储变量,避免暴露数据

        2)防止污染 HTML 结构

    • .camel

      • 支持版本:2.1.0+

      • 作用/用途:

        1)将命名变为驼峰命名法

    • .sync

      • 支持版本:2.3.0+

      • 描述:Vue的组语法

      • 作用/用途:

        1)子组件直接更新父组件的值

      • 示例:

        <!-- 父组件 -->
        <template>
            <div>
                <ChildCompontent :groupNames.sync="groupNames" />
            <div>
        </template><!-- 子组件 -->
        <template>
            <div>
                <button @click="updated">点击</button>
            <div>
        </template>
        export default {
            props: {
                groupNames: String,
            },
            methods: {
                updated() {
                    this.$emit("update:groupNames", "张三,李四,王麻子")
                },
            },
        }
        
  • 示例:

    <!-- 绑定一个 attribute -->
    <img v-bind:src="imageSrc">
    <!-- 等同于 -->
    <img :src="imageSrc">
    

二、双向绑定

2.1 双向绑定的原理

我们都知道Vue是数据双向绑定的框架,双向绑定由三个重要部分构成:

  • 数据层(Model):应用的数据及业务逻辑
  • 视图层(View):应用的展示效果,各类UI组件
  • 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来

而上面的这个分层的架构方案,可以用一个专业术语进行称呼MVVM

这里的控制层的核心功能便是“数据双向绑定”。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理。

2.2 理解ViewModel

  • 主要职责
    • 数据变化后更新视图
    • 视图变化后更新数据
  • 主要组成
    • 监听器(Observer):对所有数据的属性进行监听
    • 解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

2.3 双向绑定的流程

1)new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在 监听器(Observer) 中;

2)同时对模板执行编译(Compile),找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在 解析器(Compiler) 中;

3)同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数;

4)由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个Watcher

5)将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数。

流程图如下:

image.png

2.4 双向绑定的方法:v-model

2.4.1 v-model的介绍和用法

  • 作用/用途:实现表单元素的数据双向绑定
  • 用法:
    • v-model在原生元素上的用法:

      <input v-model="searchText" />
      

      在代码背后,模板编译器会对 v-model 进行更冗长的等价展开。因此上面的代码其实等价于下面这段代码:

      <input
        :value="searchText"
        @input="searchText = $event.target.value"
      />
      
    • 自定义组件多个参数的双向绑定

      • vue2:

        在vue2中,一个标签有且只有一个v-model,因此只能实现一个数据的双向绑定,而vue2.3+中新增了一个修饰符.sync,可以让我们在自定义组件中对多个参数进行双向绑定。

        // FatherComponent.vue
        <template>
          <CustomInput v-model:firstName="firstName" :lastName.sync="lastName" />
        </template>
        
        <script>
        import CustomInput from "./customInput";
        
        export default {
          name: 'FatherComponent',
          components: {
            CustomInput,
          },
          watch: {
            firstName(val) {
              console.log("firstName:::", val);
            },
            lastName(val) {
              console.log("lastName:::", val);
            },
          },
          data() {
            return {
              firstName: "张三",
              lastName: "李四"
            }
          },
        }
        </script>
        
        // customInput.vue
        <template>
          <div>
            <input placeholder="Basic usage" v-model="inputValue1" @input="handleInput1" />
            <input placeholder="Basic usage" v-model="inputValue2" @input="handleInput2" />
          </div>
        </template>
        
        <script>
        export default {
          name: "customInput",
          props: {
            firstName: String,
            lastName: String,
          },
          model: {
            prop: 'firstName',
            event: 'input',
          },
          data() {
            return {
              inputValue1: this.firstName,
              inputValue2: this.lastName,
            };
          },
          methods: {
            handleInput1(e) {
              this.$emit("input", e.target.value);
            },
            handleInput2(e) {
              this.$emit('update:lastName', e.target.value);
            }
          },
        }
        </script>
        
      • vue3:多个 v-model 绑定 - vue3

      vue3中的v-model像是提取vue2中的.sync和v-model的优势结合,使用起来更加便捷。

      我们可以在单个组件实例上创建多个 v-model 双向绑定

      组件上的每一个v-model都会同步不同的prop,而无需额外的选项:

      <UserName
        v-model:first-name="first"
        v-model:last-name="last"
      />
      
      <template>
        <input
          type="text"
          :value="firstName"
          @input="$emit('update:firstName', $event.target.value)"
        />
        <input
          type="text"
          :value="lastName"
          @input="$emit('update:lastName', $event.target.value)"
        />
      </template>
      
      <script>
          export default {
              data() {
                  return {
                      childValue: "",
                  }
              },
              props: {
                  firstName: String,
                  lastName: String
              },
          }
      </script>
      

2.4.2 v-model 修饰符

v-model有一些内置的修饰符,例如.trim.number.lazy。当然,我们也是可以自定义修饰符的。

  • 内置修饰符

    • .trim():默认自动去除用户输入内容中两端的空格
      <input v-model.trim="msg" />
      
    • .number:将输入内容自动转换为数字。
      1. 如果该值无法被 parseFloat() 处理,那么将返回原始值。
      2. number 修饰符会在输入框有 type="number" 时自动启用。
      3. 是在输入框失去焦点时,将非数字删除
      <input v-model.number="age" />
      
    • .lazy:改变v-moel的语法糖,将更新数据时机放置于change事件后。默认情况是,v-model会在每次input事件后更新事件
      <!-- 在 "change" 事件后同步更新而不是 "input" -->
      <input v-model.lazy="msg" />
      
  • 自定义修饰符

    【特别注意】:自定义修饰符,Vue3中支持!!!Vue2不支持!!!

    <MyComponent v-model.capitalize="myText" />
    

    组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象:

    <script setup>
    const props = defineProps({
      modelValue: String,
      modelModifiers: { default: () => ({}) }
    })
    
    defineEmits(['update:modelValue'])
    
    console.log(props.modelModifiers) // { capitalize: true }
    </script>
    
    <template>
      <input
        type="text"
        :value="modelValue"
        @input="$emit('update:modelValue', $event.target.value)"
      />
    </template>
    

    注意这里组件的 modelModifiers prop 包含了 capitalize 且其值为 true,因为它在模板中的 v-model 绑定 v-model.capitalize="myText" 上被使用了。

    有了这个 prop,我们就可以检查 modelModifiers 对象的键,并编写一个处理函数来改变抛出的值。在下面的代码里,我们就是在每次 <input /> 元素触发 input 事件时将值的首字母大写:

    <script setup>
    const props = defineProps({
      modelValue: String,
      modelModifiers: { default: () => ({}) }
    })
    
    const emit = defineEmits(['update:modelValue'])
    
    function emitValue(e) {
      let value = e.target.value
      if (props.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      emit('update:modelValue', value)
    }
    </script>
    
    <template>
      <input type="text" :value="modelValue" @input="emitValue" />
    </template>
    

    参数的 v-model 修饰符: 对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + "Modifiers"。举例来说:

    <UserName
      v-model:first-name.capitalize="first"
      v-model:last-name.uppercase="last"
    />
    
    <script setup>
        const props = defineProps({
          firstName: String,
          lastName: String,
          firstNameModifiers: { default: () => ({}) },
          lastNameModifiers: { default: () => ({}) }
        })
        defineEmits(['update:firstName', 'update:lastName'])
    
        console.log(props.firstNameModifiers) // { capitalize: true }
        console.log(props.lastNameModifiers) // { uppercase: true}
    </script>
    

2.4.3 自定义组件的 v-model

  • Vue2:自定义组件的 v-model - Vue2

    image.png

    • 给自定义组件绑定一个 v-model:

      // faterComponent.vue
      <template>
          <div id="app">
              <CustomInput v-model:modelValue="modelValue" />
          </div>
      </template>
      
      <script>
          import CustomInput from './customInput.vue'
      
          export default {
              name: 'app',
              components: {
                  CustomInput
              },
              data() {
                  return {
                      modelValue: ""
                  }
              },
          }
      </script>
      
      // customInput.vue
      <template>
        <input placeholder="Basic usage" v-model="inputValue" @input="handleInput" />
      </template>
      
      <script>
      export default {
        props: {
          modelValue: String,
        },
        // 声明v-model的相关属性信息
        model: {
          prop: 'modelValue', // 声明哪个数据是v-model使用的
          event: 'input', // 声明哪个事件可以触发v-model这个语法糖
        },
        data() {
          return {
            inputValue: this.modelValue,
          };
        },
        methods: {
          handleInput(e) {
            this.$emit("input", e.target.value);
          }
        },
      }
      </script>
      
    • 给自定义组件绑定多个 v-model: 在vue2中,一个标签有且只有一个v-model,因此只能实现一个数据的双向绑定,而vue2.3+中新增了一个修饰符.sync,可以让我们在自定义组件中对多个参数进行双向绑定。

      // FatherComponent.vue
      <template>
        <CustomInput v-model:firstName="firstName" :lastName.sync="lastName" />
      </template>
      
      <script>
      import CustomInput from "./customInput";
      
      export default {
        name: 'FatherComponent',
        components: {
          CustomInput,
        },
        watch: {
          firstName(val) {
            console.log("firstName:::", val);
          },
          lastName(val) {
            console.log("lastName:::", val);
          },
        },
        data() {
          return {
            firstName: "张三",
            lastName: "李四"
          }
        },
      }
      </script>
      
      // customInput.vue
      <template>
        <div>
          <input placeholder="Basic usage" v-model="inputValue1" @input="handleInput1" />
          <input placeholder="Basic usage" v-model="inputValue2" @input="handleInput2" />
        </div>
      </template>
      
      <script>
      export default {
        name: "customInput",
        props: {
          firstName: String,
          lastName: String,
        },
        model: {
          prop: 'firstName',
          event: 'input',
        },
        data() {
          return {
            inputValue1: this.firstName,
            inputValue2: this.lastName,
          };
        },
        methods: {
          handleInput1(e) {
            this.$emit("input", e.target.value);
          },
          handleInput2(e) {
            this.$emit('update:lastName', e.target.value);
          }
        },
      }
      </script>
      
  • Vue3:多个 v-model 绑定 - vue3

    • 给自定义组件绑定一个 v-model:

      <CustomInput v-model="searchText" />
      
      <!-- CustomInput.vue -->
      <script setup>
          defineProps(['modelValue'])
          defineEmits(['update:modelValue'])
      </script>
      
      <template>
        <input
          :value="modelValue"
          @input="$emit('update:modelValue', $event.target.value)"
        />
      </template>
      
    • 给自定义组件绑定多个 v-model:

      vue3中的v-model像是提取vue2中的.sync和v-model的优势结合,使用起来更加便捷。

      我们可以在单个组件实例上创建多个 v-model 双向绑定

      组件上的每一个v-model都会同步不同的prop,而无需额外的选项:

      <UserName
        v-model:first-name="first"
        v-model:last-name="last"
      />
      
      <script setup>
      defineProps({
        firstName: String,
        lastName: String
      })
      
      defineEmits(['update:firstName', 'update:lastName'])
      </script>
      
      <template>
        <input
          type="text"
          :value="firstName"
          @input="$emit('update:firstName', $event.target.value)"
        />
        <input
          type="text"
          :value="lastName"
          @input="$emit('update:lastName', $event.target.value)"
        />
      </template>
      

参考