Vue.js破冰系列-2HTML模板

1概述

上一章我们说了Vue是用于构建用户界面的框架,它最终呈现给用户的是一个一个HTML标签。Vue可以使用HTML模板和render函数这2种方式来构建这些HTML标签。当开发者使用了HTML模板来构建界面时,Vue内部会将其编译成render函数,最后渲染到挂载点上。render函数生成的是虚拟DOM,虚拟DOM是基于js的,相比于DOM的操作,开销会小很多。

Vue是实例需要一个挂载点,这个挂载点是由Vue的el选项指定的,el的类型可以是string或HTMLElement类型,当为string时,使用css选择器。

<html>
  <body>
    <div id="app">
      <h1>Hello World!</h1>
    </div>
    <script>
    	var vm = new Vue({
        el:"#app"
      });
    </script>
  </body>
</html>

上面使用了el类型为string的方式查找挂载点,#app为css的id选择器。Vue实例会将自身生成的DOM替换掉挂载点的元素,这里我们没有定义如何生成DOM,所以,Vue会将挂载点的HTML提取出来当作模板生成DOM,替换掉原来的HTML标签。

##2 HTML模板

HTML模板有4种方式定义,分别是:

  • Vue实例不定义,由Vue将挂载点的元素提取出来生成模板,可以理解为将模板写在挂载点内,如上面的例子。

  • 将模板以字符串的形式写在实例的配置选项template中

    <div id="app"></div>
    <script>
    	var vm = new Vue({
    		el:"#app",
        template:"<h1>Hello World!</h1>"
    	});
    </script>
    

    实例挂载后,会将template中的字符串,转换为html标签,替换掉app的内容。

  • 使用x-template,在template中定义模板有些弊端,因为它是字符串,所以缺少高亮的语法支持,当需要多行显示是,需要用到\,体验不好。x-template是将模板的定义放在scirpt标签中,然后在template中使用#引用该模板。

    <div id="app"></div>
    <!--使用x-template定义模板-->
    <script type="text/x-template" id="tempHello">
    	<h1>Hello World!</h1>
    </script>
    <!--使用template选项引用x-template定义的模板-->
    <script>
      var vm = new Vue({
        el:"#app",
        template:"#tempHello",
      });
    </script>
    

    在定义x-template模板时,需要注意的是,x-template定义的模板在script标签中,type属性为text/x-template,需要配上id,后续的template中才能够引用。在template选项中要使用#引用x-template模板(类似使用css的id选择器)。

  • 使用单文件组件(single-file components)的方式定义模板(最常用),单文件组件是以.vue结尾的文件,一个文件就是一个组件,后续讲解组件时再详细说明。

3 内部指令(directive)

在HTML模板中,html元素仅仅是界面的呈现,我们还需要和vue实例进行交互,这就需要用到vue的指令。在vue中,指令是写在html元素中,以v-开头,后接表达式(有些不需要表达式),它是Vue实例数据与用户界面之间的纽带。如:

<div id="app">
  <p v-text="msg">text</p>
</div>
<script>
	var vm = new Vue({
    el:"#app",
    data:{
      msg:"Hello World!",
    }
  });
</script>

v-text指令后接表达式msg,msg是对实例data中Hello World!的引用。当实例运行时,v-text指令所在的标签内容会替换为Hello World!。有些指令可以在其后加上修饰符,从而达到特殊的效果。指令大致可以分为显示类、渲染类,事件类3种。

3.1 显示类指令

指令用法说明
v-text<div v-text="msg"></div>以文本的方式更新元素的内容,即使msg中是html标签,它只当做字符串的方式处理,不会解析标签。即,只改变元素的textContent,与插值表达式相同<div>{{msg}}</div>
v-html<div v-html=“html”></div>更新元素的 innerHTML 。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译
v-cloak<div v-cloak>{{msg}}</div>v-cloak不接表达式,当网速较慢时,vue实例还没有加载完成,这时界面上会显示{{msg}},在vue实例加载完成后,{{msg}}会替换为对应的内容,不过这使得页面会闪动。当使用v-cloak指令后,vue实例在编译完成后,会移除该指令。通过这一特征,使用v-cloak再配合css的属性选择器[v-cloak]{display:none;}使用,达到在网速慢时界面不闪动的效果。

3.2 渲染类指令

指令用法说明
v-once<div v-once>{{msg}}</div>v-once不接表达式,通常配合其他指令使用,表示只渲染一次。渲染后,当msg的内容发生变化时,也不会重新渲染。
v-show<div v-show=“show”></div>v-show后接表达式,表达式返回true或false,根据表达式的值,修改该元素的display的样式,从而达到显示隐藏的效果。注意:因为是使用的display样式,所以该元素始终都会被渲染,只是显示/隐藏而已。

v-if v-if-else v-else

v-ifv-if-elsev-else标签需要组合使用,后接表达式。与其它语言的if/else一样,表达式条件为true时进入该分支。

<div id="app">
  <p v-if="status==0">0</p>
  <p v-else-if="status==1">1</p>
  <p v-else="status==2">2</p>
</div>
<script>
	var vm = new Vue({
    el:"#app",
    data:{
      status:0,
    }
  });
</script>

v-show不同,使用这组指令时,当条件为true时才渲染该分支对应的元素/组件,当条件为false时,对应的元素/组件会被移除。

Vue会出于效率考虑,会复用已有的元素,而非重新渲染,这样做有时会出现不符合需求的情况。比如,当复用input的时候,不会改变input的value值。

<div id="app">
  <template v-if="showQQ">
    <label>QQ</label>
    <input placeholder="QQ">
  </template> 
  <template v-else>
    <label>Email</label>
    <input placeholder="Email">
  </template> 
</div>

当showQQ为true时,显示QQ输入框,用户在输入内容后,切换到Email,即改变了showQQ的值为false,这时第一个template的内容将会被移除,但是vue为了提高效率,会重新引用这个label和input,并设置了对应的值,不过,vue并没有修改input的value的值,所以Email输入框中的值还是我们之前输入的QQ内容。

要解决这个问题,只需要在input上加入key属性,告诉vue这两个input是完全独立的,不要复用它们,注意key的值必须是唯一的。

v-ifv-show的区别:

  • v-if是条件渲染,v-show是条件显示,v-if会根据条件销毁元素/组件以及其绑定事件,而v-show总是会渲染,根据条件切换css样式来控制显示/隐藏。
  • v-show不能使用在template标签上

v-for

v-for通过遍历数据源渲染指定内容。

<ul>
  <li v-for="person in persons">{{person.name}}</li>
</ul>

<script>
  var vm = new Vue({
    data:{
      persons:[{name:"张三"},{name:"李四"},]
    }
  });
</script>

将v-for加到要渲染的标签上,使用alias in expression语法为当前遍历的元素提供别名。v-for的数据源可以是数组,也可以是对象,还可以是数字。

  • 当数据源为数组时,别名指向的是数组元素,
  • 当数据源为对象时,别名指向的是对象的值集合中的值。
  • 当数据源为数字时,别名指向的是从1开始的数字,迭代到等于数据源的数字

v-for有多种可选参数形式:

<div v-for="item in array"></div>
<div v-for="(item,index) in array"></div>

<div v-for="value in object"></div>
<div v-for="(value,key) in object"></div>
<div v-for="(value,key,index) in object"></div>

<div v-for="n in number"></div>

###3.3 事件类指令

事件类指令包括v-onv-bindv-model3个指令,如果说显示类指令和渲染类指令负责界面的呈现,那么事件类指令就负责定义交互逻辑,这些指令非常常用。

3.3.1 v-on 监听事件

使用v-on来监听事件,这些事件包括HTML元素的DOM原生事件,也可能是组件的自定义事件,组件使用$emit()方法来触发事件。v-on缩写为@符号

<div id="app">
  <!-- v-on指令接收javascript代码 -->
  <button v-on:click="counter += 1">点击</button>
  <!-- v-on语法糖形式 -->
  <button @click="counter += 1">点击</button>
  <p>点击了按钮 {{ counter }} 次</p>
</div>
<script>
	var vm = new Vue({
  el: "#app",
  data: {
    counter: 0
  }
})
</script>

事件的处理有3种方式,第1种为直接将js代码加在v-on指令中,如上所示。当代码量较多时,这种方式不可行,此时可用第2种方式,将事件的处理逻辑封装到方法中,把方法与事件绑定起来。

<div id="app">
  <!-- v-on指令接收方法名 -->
  <button @click="increment">点击</button>
  <p>点击了按钮 {{ counter }} 次</p>
</div>
<script>
	var vm = new Vue({
  el: "#app",
  data: {
    counter: 0
  },
  methods:{
    increment(){
      this.counter++;
    }
  }
})
</script>

第3种是使用内敛语句调用方法,这种方式和第1种基本一样,只是第1种是执行语句,这里是执行方法,这种方式很常用,它比第2种方式的优势在于,可以传递$event参数。Vue中使用变量$event表示原始DOM事件的Event 对象,拿到event对象后我们可以做很多事,比如获取触发事件的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态等。

<div id="app">
  <!-- v-on指令使用内敛语句调用方法 -->
  <button @click="sayHello('张三',$evnet)">点击</button>
  <p>{{ msg }} </p>
</div>
<script>
	var vm = new Vue({
   el: "#app",
   data: {
     msg: "",
   },
   methods:{
     sayHello(name,event){
       if(evnet){
         event.preventDefault();
       }
       this.msg = "Hello " + name;
     }
   }
	})
</script>

下面列出一些常用的event事件标准属性和方法,具体参考MDN DOM Event接口

属性/方法描述
ctrlKey当鼠标事件触发时,如果 control 键被按下,则返回 true;
altKey当鼠标事件触发的时,如果alt 键被按下,返回true;
shiftKey当鼠标事件触发时,如果 shift 键被按下,则返回 true;
button当鼠标事件触发的时,如果鼠标按钮被按下,将会返回一个数值。
clientX鼠标指针在点击元素(DOM)中的X坐标。
clientY鼠标指针在点击元素(DOM)中的Y坐标。
screenX鼠标指针相对于全局(屏幕)的X坐标
screenY鼠标指针相对于全局(屏幕)的Y坐标
bubbles表示该事件是否在DOM中冒泡。
cancelable用来表示这个事件是否可以取消。
currentTarget返回注册这个事件监听的对象。
eventPhase事件流正在处理哪个阶段。
target触发该事件的元素。
type返回当前 Event 对象表示的事件的名称,及事件的类型(不区分大小写)
initEvent()初始化新创建的 Event 对象的属性。
preventDefault()取消事件,不执行与事件关联的默认动作。
stopPropagation()停止事件冒泡。

v-on修饰符

Vue为v-on提供了事件修饰符,在一定程度上简化了在方法中处理$event对象的逻辑。

  • .stop - 调用 event.stopPropagation()
  • .prevent - 调用 event.preventDefault()
  • .capture - 添加事件侦听器时使用 capture 模式。
  • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
  • .native - 监听组件根元素的原生事件。
  • .once - 只触发一次回调。
  • .left - (2.2.0) 只当点击鼠标左键时触发。
  • .right - (2.2.0) 只当点击鼠标右键时触发。
  • .middle - (2.2.0) 只当点击鼠标中键时触发。
  • .passive - (2.3.0) 以 { passive: true } 模式添加侦听器

其中.capture表示该事件使用捕获模式(默认是冒泡模式),我们在用js原生添加事件监听使用如下方法,event是事件名称,如click;listener是为该事件注册的方法,useCapture是使用捕获模式还是冒泡模式。

addEventListener(event, listener, useCapture)  

useCapture默认是false的,也就是说默认使用冒泡事件,关于捕获和冒泡可以简单的理解,捕获模式是事件传播的方向是由根元素向目标前进,冒泡模式表示事件的传播方向是由目标元素向根元素前进。

3.3.2 v-bind 属性绑定

将表达式动态绑定到HTML元素/组件的属性上,缩写为:符号

<div id="app">
  <a v-bind:href="url">百度</a>
	<!-- v-bind语法糖形式 -->
	<a :href="url">百度</a>
</div>

<script>
 	var vm = new Vue({
    el:"#app",
    data:{
      url:"www.baidu.com",
    }
  });
</script>

v-bind的写法为v-bind:foo="bar",其中foo表示v-bind的参数,代表的是原生/组件的属性名,bar是预期值,即属性对应的值。

参数时可选的,也就是说,v-bind可指定属性名也可不指定属性名,当不带参数时,要绑定到一个包含键值对的预期值对象,对象的key就是参数,对象key对应的value就是参数对应的值,如下:

<img v-bind="{src:'a.png',alt='呵呵',height:32,width:32}">
<!--等价于-->
<img src="a.png" alt="呵呵" height="32" width="32">

可以利用无参指令,一次性绑定多个属性。

在向自定义组件绑定属性值时(通过props传递),数据是单向流动的,父组件向子组件传递数据,反之不行。常规的做法是,父组件通过子组件的props传值给子组件,同时在子组件上注册监听事件,子组件通过$emit方法触发父组件的监听方法,修改props对应的值。

Vue提供了.sync修饰符,它实际上是一个语法糖,加了.sync的prop,vue会自动的为其生成一个事件监听,监听的名称为update:propName,如要子组件要修改prop的值,只需调用this.$emit('update:propName',value)即可,如下所示:

<body>
    <div id="app">
        <h3>{{msg}}</h3>
        <!-- 引用组件,将msg赋值到组件的title属性上,
            并使用.sync修饰符(语法糖)添加v-on事件监听,
            该事件名称的格式为 update:propName,对应到这里是update:title-->
        <son :title.sync="msg" />
    </div>

    <!-- 使用x-template定义组件的HTML模板 -->
    <script type="text/x-template" id="son">
        <div>
            <p>{{title}}</p>
            <button @click="btnClicked">点我</button>
        </div>
    </script>

    <script>
        // 组件定义
        Vue.component('son', {
            //定义组件属性
            props:['title'],
            //找到id为son的HTML模板
            template: "#son",
            methods: {
                btnClicked() {
                    //触发由.sync生成的upadte:title事件
                    this.$emit("update:title","子组件改变后的标题")
                }
            },
        });
        var vm = new Vue({
            el: "#app",
            data: {
                msg: "标题"
            }
        });
    </script>
</body>

注意:.sync修饰符后面不能接表达式。

3.3.3 class与style增强绑定

v-bind可以绑定元素的所有属性,其中最常用的是元素的class和style属性,不过当这两个属性的逻辑较为复杂时,使用字符串的方式返回该属性值体验非常差,所以,Vue增强了对class和style的绑定。

class的绑定

使用v-bind绑定class有对象语法和数字语法两种方式,在对象语法中key表示css的类名,value是个bool值,为true时,该key对应的类名加入class中,为false时,对应的项从class中移除。

<div class="cell" :class="{'active':isActive,'error':isError}"></div>
<script>
var vm = new Vue({
  data:{
    isActive:true,
    isError:false,
  }
});
</script>

上面的代码最终生成的class为:

<div class="cell active"></div>

v-bind:class生成的动态绑定类会与class属性中的类合并。这isActive的值为true,表示active会加入到class类中,isError为false,div的class中会移除error类。

数组语法就相对简单,只要是数组中的字符串,都会加到class类中。在数组中可以使用三元表达式来动态的加载类。

<div :class="[cell,isActive?'active':'',isError?'error':'']"></div>

style内联样式绑定

v-bind:sytle也有对象语法和数据语法,但是对象语法较为常见

<div :style="cellStyle"></div>

<script>
var vm = new Vue({
  cellStyle:{
    fontSize:14+'px',
    color:'red'
  }
});
</script>

结果为:

<div style="color:red;font-size:14px;"></div>
3.3.4 v-model双向绑定

v-model指令用于在表单类元素上实现数据的双向绑定,它的本质也是监听表单类元素的输入事件,更新绑定的数据,vue对以下列表单元素做了处理。

<!-- 输入框元素,绑定value属性,监听input事件 -->
<input type="text" v-model="text">

<!-- 文本域元素,绑定value属性,监听input事件 -->
<textarea v-model="text"></textarea>

<!-- 单个复选框时,v-model绑定一个bool值,该值指示checkbox添加/移除checked属性,监听change事件 -->
<input type="checkbox" v-model="checked" id="checkbox1">
<label for="cbox1">{{checkd}}</label>

<!-- 
  多个复选框时,v-model绑定一个数组,与value属性配合使用,监听change事件。
	选中复选框时,将value值push到数组中,否则,将value值从数组中移除。	 
-->
<input type="checkbox" id="jack" value="jack" v-model="names">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="john" v-model="names">
<label for="john">John</label>

<!-- 单选按钮,v-model绑定字符串,与value值配合使用-->
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>

<!-- 
	选择框,v-model绑定value值,监听change事件,也分为单选和多选,
	因为实际使用select标签较少(样式不统一),这里不在演示
-->

修饰符

  • .lazy修饰符适用于输入框,设置后该修饰符后,v-model不再监听input事件,转而监听change事件。也就是当用户输入文本时,数据不是事实的同步,而是在文本框失去焦点或回车时更新。

    <input type="text" v-model.lazy="text">
    
  • .number将输入框的内容转换为数字类型

  • .trim自动过滤用户输入的首尾空白字符

3.3.5 自定义组件的v-model

v-model指令不仅可以在表单元素上实现数据的双向绑定,还可以在自定义组件上实现双向绑定。上面说了,v-model其实是语法糖,它的本质是绑定值到元素的指定属性上,并监听元素的输入事件,vue在表单元素中为我们实现了该功能,而在自定义组件上,这个功能需要我们自己实现。

v-model在自定义组件上默认绑定value属性,监听input事件。

<body>
    <div id="app">
        <h3>{{count}}</h3>
        <!-- 在自定义组件上使用v-model指令 -->
        <my-component v-model="count" />
    </div>

    <script type="text/x-template" id="myComponent">
        <div>
            <p>{{value}}</p>
            <button @click="btnClicked">点我</button>
        </div>
    </script>

    <script>
        // 组件定义
        Vue.component('myComponent', {
          	//组件中定义value属性
            props:["value"],
            template: "#myComponent",
            methods: {
                btnClicked() {
                    // 发送input事件
                    this.$emit("input",this.value+1);
                }
            },
        });
        var vm = new Vue({
            el: "#app",
            data: {
                count: 0,
            },
        });
    </script>
</body>

因为v-model默认绑定的是value属性,所以需要在组件中定义一个value。v-model指令默认监听input事件(自动生成),所以,执行通过$emit触发input事假即可。

如果希望改变v-model在组件上默认的属性和事件,可以使用model选项,model选项接收一个对象,这个对象有两个属性:prop和event。prop的值默认是value,event的值默认是input。

<script>
	Vue.component('myComponent',{
    model:{
      //修改v-model的默认绑定的value属性和input监听事件
      prop:"count",
      event:"countChange"
    },
    props:["count"],
    template:"#myComponent",
    methods:{
      btnClicked(){
        this.$emit("contChange",this.count+1);
      }
    }
  });
</script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值