第4节:v-model和组件化开发

136 阅读9分钟

附:「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战

1. v-model 双向数据绑定

1.1 介绍

表单提交是开发中非常常见的功能,也是和用户交互的重要手段:

  • 比如用户在登录、注册时需要提交账号密码;
  • 比如用户在检索、创建、更新信息时,需要提交一些数据;

这些都要求我们可以在代码逻辑中获取到用户提交的数据,我们通常会使用v-model指令来完成:v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定;它会根据控件类型自动选取正确的方法来更新元素;尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理;

1.2 基本使用

当我在输入框输入新的内容,对应的数据也会发生变化。这个就是v-model(双向数据绑定)

<template id="my-app">
  <input type="text" v-model="message">
  <h2>{{message}}</h2>
</template>

输出结果:

1.3 v-model原理

官方有说到,v-model的原理其实是背后有两个操作:

  • v-bind绑定value属性的值;
  • v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
<input type="text" v-model="message">

相当于

<input type="text" :value="message" @input="message = $event.target.value">

1.4 v-model绑定表单

1.4.1 textarea 文本控件
<template id="my-app">
   <textarea cols="30" rows="10" v-model="message"></textarea>
   <h2>{{message}}</h2>
</template>

输出结果:

1.4.2 checkbox 复选框

(1)单个勾选框:v-model即为布尔值,true为选中状态、false未选中状态

<label for="agreement">
     <input type="checkbox" v-model="isAgree">同意协议
</label>
<h2>isAgree当前的值是: {{isAgree}}</h2>

输出结果:

(2)多个复选框

  • 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
  • 当选中某一个时,就会将input的value添加到数组中。
<span>你的爱好: </span>
<label for="basketball">
   <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
</label>
<label for="football">
   <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
</label>
<label for="tennis">
   <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
</label>
<h2>hobbies: {{hobbies}}</h2>

输出结果:

1.4.3 radio 单选框
<template id="my-app">
    <label for="male">
       <input type="radio" id="male" v-model="gender" value="male">男
    </label>
    <label for="female">
        <input type="radio" id="female" v-model="gender" value="female">女
     </label>
     <h2>gender当前的值是:{{gender}}</h2>
</template>

输出结果:

1.4.4 select 单选/多选菜单

select和checkbox一样,select也分单选和多选两种情况。

(1)单选:只能选中一个值

  • v-model绑定的是一个值,例如 -> fruit: "orange"
  • 当我们选中option中的一个时,会将它对应的value赋值到fruit中;
 <div>
    <span>喜欢的水果: </span>
    <select v-model="fruit">
      <option value="apple">苹果</option>
      <option value="orange">橘子</option>
      <option value="banana">香蕉</option>
    </select>
    <h2>fruit: {{fruit}}</h2>
</div>

输出结果:


(2)多选:可以选中多个值。加上 multiple属性后,就开启了多选内容(按住shift来多选)

  • v-model绑定的是一个数组,例如 -> fruit: []
  • 当选中多个值时,就会将选中的option对应的value添加到数组fruit中;
<div>
    <span>喜欢的水果: </span>
    <select v-model="fruit" multiple>
      <option value="apple">苹果</option>
      <option value="orange">橘子</option>
      <option value="banana">香蕉</option>
    </select>
    <h2>fruit: {{fruit}}</h2>
</div>

输出结果:

1.5 v-model的值绑定

(1)目前我们在前面的案例中大部分的值都是在template中固定好的: 比如gender的两个输入框值male、female;比如hobbies的三个输入框值basketball、football、tennis。

在真实开发中,我们的数据可能是来自服务器的,那么我们就可以先将值请求下来,绑定到data返回的对象中,再通过v-bind来进行值的绑定,这个过程就是值绑定。示例代码如下:

<template id="my-app">
   <span>喜欢的水果: </span>
   <select v-model="SelectedFruit">
     <option v-for="item in fruits" :value="item.value">{{item.name}}</option>
   </select>
   <h2>SelectedFruit: {{SelectedFruit}}</h2>
</template>

 <script>
        const App = {
            template: '#my-app',
            data() {
                return {
                    SelectedFruit: [],
                    fruits: [{
                        name: "香蕉",
                        value: "banana"
                    }, {
                        name: "苹果",
                        value: "apple"
                    }, {
                        name: "橘子",
                        value: "orange"
                    }]
                }
            },
        }

  </script>

输出结果:

1.6 v-model的修饰符

1.6.1 .lazy

默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步; 如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发;

<template id="my-app">
    <input type="text" v-model.lazy="message">
    <h2>{{message}}</h2>
</template>

输出结果:只有在按下回车键后才会更新 message 的内容

1.6.2 .number

(1)我们先来看一下 v-model绑定message属性后值的类型是什么?

<template id="my-app">
    <input type="text" v-model="message">
    <h2>{{message}}</h2>
    <button @click="checkType">查看类型</button>
</template>

<script>
        const App = {
            template: '#my-app',
            data() {
                return {
                    message: 0,
                }
            },
            methods: {
                checkType() {
                    console.log("messageType:", typeof this.message)
                }
            }
        }
 </script>

输出结果:

  1. 当我们第一次点击查看类型按钮,会显示类型是 number,这个没有问题,因为我们本身定义属性的值是number。但是我们在输入框输入新的数字后,再次点击按钮查看类型,显示的结果是 messageType: string。

如果我们希望转换为数字类型,可以使用 .number 修饰符:

<input type="text" v-model.number="message">

输出结果:

补充知识点:我们进行逻辑判断时,如果是一个string类型,在可以转化的情况下会进行隐式转换的: 下面的score在进行判断的过程中会进行隐式转化的;

const score = "100";
if(score > 90){
 console.log("优秀")
}

console.log(typeof score)

输出结果:

1.6.3 .trim

如果要自动过滤用户输入的首尾空白字符,可以给v-model添加 trim 修饰符:

<template id="my-app">
 <!-- 去除首尾空格 -->
 <input type="text" v-model.trim="message">
</template>

输出结果:

2. 组件化开发

2.1 人处理问题的方式

人面对复杂问题的处理方式:

  • 任何一个人处理信息的逻辑能力都是有限的
  • 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
  • 但是,我们人有一种天生的能力,就是将问题进行拆解。
  • 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。

2.2 认识组件化开发

(1)组件化也是类似的思想:

  • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展;
  • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了;
  • 如果我们将一个个功能块拆分后,就可以像搭建积木一下来搭建我们的项目;

如图:

(2)组件化核心思想:我们需要通过组件化的思想来思考整个应用程序:我们将一个完整的页面分成很多个组件;每个组件都用于实现页面的一个功能块;而每一个组件又可以进行细分;而组件本身又可以在多个地方进行复用;

2.3 Vue组件化

前面我们的createApp函数传入了一个对象App,这个对象其实本质上就是一个组件,也是我们应用程序的根组件;

<script>
        const App = {
            template: '#my-app',
            data() {
                return {
                    message: "Hello Vue3",
                }
            },
            methods: {}
        }
  Vue.createApp(App).mount('#app');
</script>

组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用; 任何的应用都会被抽象成一颗组件树;

2.4 注册组件

如果我们现在有一部分内容(模板、逻辑等) ,我们希望将这部分内容抽取到一个独立的组件中去维护,这个时候如何注册一个组件呢?我们先从简单的开始谈起,比如下面的模板希望抽离到一个单独的组件:

<h2>{{ title }}</h2>
<p>{{ message }}</p>

注册组件分成两种:

  • 全局组件:在任何其他的组件中都可以使用的组件;
  • 局部组件:只有在注册的组件中才能使用的组件;
2.4.1 注册全局组件

全局组件需要使用我们全局创建的app来注册组件; 通过component方法传入组件名称、组件对象即可注册一个全局组件了;之后,我们可以在App组件的template中直接使用这个全局组件:

<template id="my-app">
   <!-- 2. 使用全局组件 -->
   <component-a></component-a>
</template>

<script>
   const app = Vue.createApp(App)

    // 1. 通过app.component注册全局组件
    app.component('component-a',{
        template: `<h2>{{ message }}</h2>`,
        data(){
            return {
                message: "我是component-a组件"
            }
        }
    })

    app.mount('#app');
</script>
2.4.2 组件的名称

在通过app.component注册一个组件的时候,第一个参数是组件的名称,定义组件名的方式有两种:

方式一:使用kebab-case(短横线分隔符)

  • 当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 ;
app.component('my-component-name', {
 /* ... */
})

方式二:使用PascalCase(驼峰标识符)

  • 当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 和 都是可接受的;
app.component('myComponentName', {
 /* ... */
})
2.4.3 注册局部组件

全局组件往往是在应用程序一开始就会全局组件完成,那么就意味着如果某些组件我们并没有用到也会一起被注册:

  • 比如我们注册了三个全局组件:ComponentA、ComponentB、ComponentC;
  • 在开发中我们只使用了ComponentA、ComponentB,如果ComponentC没有用到但是我们依然在全局进行了注册,那么就意味着类似于webpack这种打包工具在打包我们的项目时,我们依然会对其进行打包;
  • 这样最终打包出的JavaScript包就会有关于ComponentC的内容,用户在下载对应的JavaScript时也会增加包的大小;

所以在开发中我们通常使用组件的时候采用的都是局部注册:

  • 局部注册是在我们需要使用到的组件中,通过components属性选项来进行注册;
  • 比如之前的App组件中,我们有data、computed、methods等选项了,事实上还可以有一个components选项;
  • 该components选项对应的是一个对象,对象中的键值对是 组件的名称: 组件对象;
 <template id="my-app">
    <!-- 3. 使用ComponentA组件 -->
    <component-a></component-a>
 </template>

  <script>
   // 2. ComponentA组件
   const ComponentA = {
    template: `<h2>{{message}}</h2>`,
    data(){
        return {
            message: "ComponentA组件"
        }
    }
   }
   
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World",
        }
      },
      // 1. 注册局部组件
      components: {
          "component-a": ComponentA
      }
    }

    Vue.createApp(App).mount('#app');
  </script>

2.5 Vue的开发模式

  1. 目前我们使用vue的过程都是在html文件中,通过template编写自己的模板、脚本逻辑、样式等。
  2. 但是随着项目越来越复杂,我们会采用组件化的方式来进行开发:
  • 这就意味着每个组件都会有自己的模板、脚本逻辑、样式等;
  • 当然我们依然可以把它们抽离到单独的js、css文件中,但是它们还是会分离开来;
  • 也包括我们的script是在一个全局的作用域下,很容易出现命名冲突的问题;
  • 并且我们的代码为了适配一些浏览器,必须使用ES5的语法;
  • 在我们编写代码完成之后,依然需要通过工具对代码进行构建、代码;
  1. 所以在真实开发中,我们可以通过一个后缀名为 .vue 的single-file components (单文件组件) 来解决,并且可以使用webpack或者vite或者rollup等构建工具来对其进行处理。

如图:

  1. .vue文件会经过被构建工具(webpack/vite)打包成浏览器可以认识的JavaScript代码
  2. JavaScript放在某一台服务器上
  1. 最终用户通过浏览器访问这个网页

2.6 单文件的特点(了解)

在这个组件中我们可以获得非常多的特性:

  • 代码的高亮;
  • ES6、CommonJS的模块化能力;
  • 组件作用域的CSS;
  • 可以使用预处理器来构建更加丰富的组件,比如TypeScript、Babel、Less、Sass等;

如图: