轻松掌握纯前端js框架---VUE Ⅱ

499 阅读11分钟
能力配不上野心,是所有烦扰的根源。这个世界是公平的,你要想得到,就得学会付出和坚持。每个人都是通过自己的努力,去决定生活的样子。

本期主要内容

  1. 指令:
  2. 双向绑定:
  3. 绑定样式:
  4. 自定义指令:

一. 指令:

  1. 根据数组内容,反复创建多个相同结构的元素: v-for

    (1). <要反复创建的元素 v-for="(value, i ) of 数组/对象/字符串">

    (2). 原理: 当new Vue()首次扫描到这里时,或依赖的数组内容发生变化时:

    a. 自动遍历of后的数组或对象...

    b. 每遍历一个成员,就

     1). 将成员的值交给of前的value变量,
     2) 将成员的下标位置交给of前的i变量
    

    c. 反复创建v-for所在HTML元素,并将value和i的值替换到元素中可能发生变化的位置.

    d. 在v-for所在的当前元素上以及当前元素的所有子元素中,都可以用value和i变量进行绑定。

(3). 强调:

a. v-for必须写在要反复生成的元素上,而不能写在父元素上: 

	比如: 反复生成一个ul下的多个li元素,v-for应该写在子元素li上
	
b. of前的变量(value,i)可随意改名!但第一个变量始终接元素值,第二个变量始终接下标

(4). 其实vue中的v-forof,统一了js中的forof和forin的功能!既可遍历数字下标,又可遍历自定义下标。

(5). 问题: 因为v-for反复生成的多个HTML元素,除了内容不同之外,元素本身毫无差别!导致,万一依赖的数组中某个元素发生变化,v-for无法精确的找到具体应该修改哪个HTML元素副本。所以v-for选择最笨的方法,将所有元素删掉,重新遍历数组,重新创建所有副本——效率低

(6). 解决: 凡是用v-for时,都必须同时绑定:key="下标"属性 结果: 每个HTML元素副本上都有一个key="下标"属性。当对数组中某一个元素执行操作时,vue会根据下标找到对应key="下标"的一个元素,只修改这一个元素,不影响其他元素。—— 效率高!

(7). 总结: 鄙视: 为什么v-for必须加:key?

a. 避免在修改某一个数组成员时,重建所有HTML元素副本
b. 如果加了:key,每次只需要修改一个数组元素对应的一个HTML元素副本即可——效率高

(8). 示例: 使用v-for遍历数组和对象

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
</head>
<body>
  <div id="app">
    <ul>
      <!--因为要根据数组tasks的内容反复生成多个相同结构的li元素,所以应该在li元素上用v-for-->
      <li v-for="(value,i) of tasks" :key="i">
        <!--因为反复生成的内容中需要动态获得每个元素值和下标,所以,可以用of前的变量value和i用于绑定语法中动态生成内容-->
        {{i+1}}. {{value}}
      </li>
    </ul>
    <!--想遍历出lilei对象中每个属性的属性名和属性值-->
    <ul>
      <li v-for="(value,key) of lilei" :key="key">{{key}}: {{value}}</li>
    </ul>
  </div>
  <script>
    new Vue({
      el:'#app',
      data:{
        //有一个任务列表,希望展现到页面上
        tasks:["吃饭","睡觉","打亮亮"],//.length=3
        //       0      1      2
        lilei:{
          sname:"Li Lei",
          sage:11,
          className:"初一2班"
        }
      }
    })
  </script>
</body>
</html>
运行结果: 

9). 其实: v-for还会数数:仅根据一个数字,就可反复生成指定数量的HTML元素副本,且从1开始数数,一直数到给的数字为止

a. <要反复生成的元素 v-for="i of 数字">
b. 原理: 当new Vue()首次扫描到这里时,或依赖的数值发生变化时:
	1). 数字是几,就会反复创建几个HTML元素副本
	2). 每次创建副本时,都会将本次数到的数字保存到of前的变量中
	3). 在当前元素及其子元素中变量i,可用于绑定。

(10). 示例: 使用v-for生成分页按钮

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
  <style>
    #pages{ list-style: none; }
    #pages>li{
      float:left;
      padding:5px 10px; 
      border:1px solid #aaa;
      margin:0 5px;
    }
  </style>
</head>
<body>
  <div id="app">
    <!--希望总页数是几,就反复生成几个分页分页按钮-->
    <ul id="pages">
      <li v-for="i of pageCount" :key="i">{{i}}</li>
    </ul>
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{
        pageCount:5,//分页时都会有一个变量保存总页数
      }
    })
  </script>
</body>
</html>
运行结果: 

  1. 绑定事件: v-on

    (1). 标准: <元素 v-on:事件名="事件处理函数()">

    (2). 等效于: DOM中的<元素 on事件名="事件处理函数()">

    (3). 重要差别: VUE中事件处理函数中的this,不再指向当前触发事件的元素。而是指向当前整个new Vue()对象!

    (4). 简写:

    a. "v-on:"可用"@"代替: <元素 @事件名="事件处理函数()">

    b. 如果事件处理函数没有任何实参值,则可以省略():<元素@事件名="事件处理函数">

    (5). 其实: vue中的事件处理函数可以传参!

    a. <元素 @事件名="事件处理函数(实参值列表)">
      methods:{
    	  事件处理函数(形参列表){ ... }
      }
    

    b. 示例: 点哪个div,哪个div喊谁疼!

    <!DOCTYPE html>
    
Document
运行结果:
```

(6). 其实: 在vue中也可获得事件对象: 2种

a. 无需传其它实参值,只希望获得事件对象时:
	1). 同DOM: 事件对象总是默认作为事件处理函数的第一个实参值自动传入
	2). <元素 @事件名="事件处理函数">
		methods:{
			事件处理函数(e){ ... }
		}
	3). 问题: 如果需要同时传入实参值和获得事件对象,实参值和事件对象e传入的位置就会撞车!
	<元素 @事件名="事件处理函数(实参值)">
                     event
		methods:{
			事件处理函数(e){ ... }
		}
	4). 错误的解决1: 在methods中在事件处理函数中e之前多加一个形参变量
	<元素 @事件名="事件处理函数(实参值)">
                     event
		methods:{
			事件处理函数(形参1, e){ ... }
		}
		因为event对象,默认只能传给第一个形参变量,不会给之后的其它形参
	5) 错误解决2: methods中交换事件处理函数的两个形参变量的顺序
	<元素 @事件名="事件处理函数(实参值)">
                     event
		methods:{
			事件处理函数(e, 形参1){ ... }
		}
		因为第一个实参值,不会聪明到自动给第二个形参变量,依然给第一个形参,依然会和event发生冲突
	6). 示例: 鄙视题: vue中如何获得鼠标位置: 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
  <style>
    #app>div{
      width:300px; 
      height:100px;
    }
    #app>div:hover{
      box-shadow:0 0 5px red
    }
  </style>
</head>
<body>
  <div id="app">
    <!--点哪个div的某个位置,就喊某个为疼!-->
    <div id="d1" style="background-color:#aaf" @click="say"></div>
    <div id="d2" style="background-color:#ffa" @click="say"></div>
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{},
      methods:{
        //因为想获得鼠标点击的位置,必须加形参e
        //event
        //  ↓
        say(e){
          alert(`${e.offsetX},${e.offsetY}位置 疼! `)
        }
      }
    })
  </script>
</body>
</html>
运行结果:

b. 如果同时传入实参值和事件对象:借助于vue一个关键词$event

                                 DOM event
							         先↓
<元素 @事件名="事件处理函数(实参值,  $event)">

		methods:{
			事件处理函数(形参1, e){ ... }
		}
	说明: $event和实参值可以交换位置。$event无论在实参列表中第几个位置,都可先获得事件对象event。不受位置影响!

c. 示例: 鄙视题: 如果同时传入自定义实参值和事件对象:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
  <style>
    #app>div{
      width:300px; 
      height:100px;
    }
    #app>div:hover{
      box-shadow:0 0 5px red
    }
  </style>
</head>
<body>
  <div id="app">
    <!--如果既需要传入实参值,又需要获得事件对象-->
    <!--                 DOM event-->
    <!--                    先 ↓  -->
    <div id="d1" @click="say($event,'d1')" style="background-color:#aaf" ></div>
    <!--                 DOM event-->
    <!--                    先 ↓  -->
    <div id="d2" @click="say($event,'d2')" style="background-color:#ffa" ></div>
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{},
      methods:{
        //因为界面上调用事件处理函数时,传入了两个实参,所以methods中定义事件处理函数时,也必须定义两个形参对应
        say(e, id){
          alert(`${id}${e.offsetX},${e.offsetY}位置 疼! `)
        }
      }
    })
  </script>
</body>
</html>
运行结果: 

  1. 防止用户短暂看到{{}}:

    (1). 问题: 如果网速不好,js代码下载较慢,用户可能短暂看到页面上的{{}}语法!——尴尬!

    (2). 解决: 2种方法

    a. 用v-cloak属性:

     1). 2步:
     i. 先在页面上统一位置定义一个属性选择器: 
     [v-cloak]{ display:none } 意为: 凡是带有v-cloak属性的元素,都默认隐藏
     斗篷/幕布
     ii. 在网页中带有{{}}内容的元素上添加v-cloak属性,不需要任何属性值
     2). 结果: 
     i. 如果网速慢, new Vue()所在的js暂时没有下载下来时,v-cloak选择器起作用,带有v-cloak和{{}}的元素暂时隐藏
     ii. 当new Vue()所在的js文件,下载完成后,new Vue()自动找到所有v-cloak属性,自动移除这些元素上的v-cloak属性。这些带有{{}}的元素就显示出来了!
     3). 强调: v-cloak属性是vue内置的指令名,不要随意修改!
    

    b. 用v-text指令代替{{}}:

     1). 1步: <元素 v-text="变量或js表达式">    </元素>
     2). 原理: 当new Vue()首次扫描到这里或依赖的变量发生变化时
     	vue都会先计算""中js表达式的结果,然后用结果代替元素的内容
     3). 为什么可以屏蔽{{}},因为根本就没用{{}}。
     4). 问题: {{}}的好处在于可以随意和其它写死的字符串拼接出一个新的字符串显示。但是用v-text,则无法将写死的字符串和变量或表达式随意拼接。
     5). 解决: 在v-text中将写死的字符串和变量或表达式随意拼接,必须用模板字符串(反引号和${})
     <元素 v-text="`xxxx ${变量或js表达式}`">    </元素>
    

    (3). 示例: 分别使用v-text和v-cloak解决短暂看到{{}}的问题:

    <!DOCTYPE html>
    
Document

用户名:{{uname}}

运行结果:

2s后才看到

```
  1. 要绑定的内容是HTML片段: v-html

    (1). 问题: 如果用{{}}或v-text绑定一段HTML片段,则不会将要绑定的内容交给浏览器解析,而是保持原样显示在页面上——不是我们想要的

    (2). 为什么: {{}}和v-text底层其实相当于DOM中的.textContent

    (3). 解决: 今后,只要绑定一段HTML片段,都要用v-html代替v-text和{{}}

    (4). 为什么: v-html底层相当于DOM中的.innerHTML

    <元素 v-html="变量或js表达式"> </元素>

    (5). 原理: v-html会先将要绑定的内容交给浏览器解析,然后将解析后的可以给人看的内容替换元素的内容显示出来。

    (6). 示例: 分别使用{{}}、v-text和v-html绑定HTML片段内容

	<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
</head>
<body>
  <div id="app">
    <h3>{{html}}</h3>
    <h3 v-text="html"></h3>
    <h3 v-html="html"></h3>
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{
        html:`来自&lt;&lt;<a href="javascript:;">新华社</a>&gt;&gt;的消息`
      }
    })
  </script>
</body>
</html>
运行结果: 

  1. 只在首次加载时绑定一次:

    (1). 问题: 有些界面上显示的内容,只在首次加载时更新一次。之后几乎不会改变!如果这种元素也包含在虚拟DOM树中,无形中就增大了虚拟DOM树,可能影响遍历的速度

    (2). 解决: 让这种元素只在首次加载时绑定一次,而且不加入虚拟DOM树中。

    (3). 如何: <元素 v-once></元素>

    (4). 原理: 凡是带有v-once的元素,只在首次加载时绑定一次,之后即使变量发生变化也不会改变。因为v-once的元素根本就没有加入到虚拟DOM树。

    (5). 优点: 减少了虚拟DOM树中的内容,加快遍历的速度

    (6). 示例: 使用v-once绑定页面加载完成时间

    <!DOCTYPE html>
    
Document

页面加载完成时间(一次性): {{time}}

当前系统时间(反复多次): {{time}}

运行结果:
```

  1. 防止内容中{{}}被编译: v-pre

    (1). 特殊情况:在元素内容中刚巧包含{{}},但是不想被vue编译,只是想原样显示出来

    (2). <元素 v-pre>xxx{{xxx}}xxx</元素>

    (3). 结果: vue不会将内容中的{{}}解析为变量或js表达式,而是原样显示在页面上。

    (4). 示例: 防止内容中的{{}}被编译:

	<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
</head>
<body>
  <div id="app">
    <h3 v-pre>Vue框架用{{变量名}}方式标记页面中可能发生变化的位置</h3>
  </div>
  <script>
    new Vue({
      el:"#app"
    })
  </script>
</body>
</html>
运行结果: 

二. *****双向绑定:

  1. 问题: 以上12种绑定方式都无法获得用户在页面上文本框中输入的新值!

  1. 原因: 因为前12中绑定或指令都是单向绑定:

    (1). 单向绑定: 只能将程序中的变化,自动送到界面去,无法将界面上用户所做的修改,反向更新回程序中的变量中。(只能从Model->View,不能从View->Model)

  1. 解决: 今后只要希望随时获得用户在页面上表单元素中所做的修改时,都要用双向绑定

    (1). 双向绑定: 既能将程序中的变化,自动送到界面去,又能将界面上用户所做的修改,反向更新回程序中的变量中。(既能从Model->View,又能从View->Model)

  2. 如何: 通常用于绑定表单元素,因为只有表单元素,用户才能在页面上修改! <表单元素 v-model:value="自定义变量">


	data:{
		自定义变量: 初始值
	}
	
  1. 示例: 点按钮,获得文本框中输入的关键词,执行搜索操作
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
</head>
<body>
  <div id="app">
    <!--界面中哪个可能发生变化? 文本框的内容可能发生变化
      因为文本框的内容是value属性,要绑定value属性值,应该用:绑定,自定义变量名为keywords-->
    <!--但是,因为:value只能单向绑定(M->V),不能双向绑定(V->M),所以要改为v-model:value,才能双向绑定(V->M)-->
    <!--点哪里可能触发查找事件? button-->
    <input v-model:value="keywords"><button @click="search">百度一下</button>
  </div>
  <script>
    new Vue({
      el:"#app",
      //既然页面上需要一个keywords变量,data中就应该定义一个keywords变量支持页面
      data:{
        keywords:"mac"//开局是空字符串
      },
      methods:{
        //因为页面上需要一个事件处理函数search(),所以methods中就要定义一个search()函数
        search(){
          //如果keywords中收到的关键词不是空字符串,则执行查找操作
          if(this.keywords.trim()!==""){
            console.log(`查找 ${this.keywords.trim()} 相关的内容...`)
          }
        }
      }
    })
  </script>
</body>
</html>
运行结果:

  1. 双向绑定原理: 在单向绑定原理(访问器属性+虚拟DOM树)基础上,又自动为表单元素绑定了onchange事件

    onchange事件是DOM中常用事件,意为当内容发生改变时自动触发!

  2. 示例: 使用@change事件和DOM e.target,模拟双向绑定

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
</head>
<body>
  <div id="app">
    <!--当文本框内容发生改变时,自动获得文本框中的新值,保存到变量keywords中-->
    <input :value="keywords" @change="change"><button @click="search">百度一下</button>
  </div>
  <script>
    new Vue({
      el:"#app",
      //既然页面上需要一个keywords变量,data中就应该定义一个keywords变量支持页面
      data:{
        keywords:"mac"//开局是空字符串
      },
      methods:{
        //因为页面上需要一个事件处理函数search(),所以methods中就要定义一个search()函数
        search(){
          //如果keywords中收到的关键词不是空字符串,则执行查找操作
          //if(this.keywords.trim()!==""){
            console.log(`查找 ${this.keywords.trim()} 相关的内容...`)
          //}
        },
        //如何获得当前元素的内容: DOM e.target
        change(e){
          //            当前文本框的内容
          this.keywords=e.target.value;
        }
      }
    })
  </script>
</body>
</html>
运行结果: 

  1. 问题: 有些表单元素用户在修改时value是固定不变的!改的是其他属性:

    比如: 性别:

<input type="radio" value="1" name="sex">男
<input type="radio" value="0" name="sex">女
  1. 解决: 分析,用户操作这些元素时,到底该的是哪个属性?

    1). 比如: 用户选中或不选中radio,改的是radio的checked属性!

    2). 所以: v-model应该绑定在checked属性上

	<input type="radio" value="固定值" name="分组名" v-model:checked="变量名">

3). 原理:

i. 开局: 将程序中的变量值显示到页面上:radiov-model:checked="变量名"v-model会用变量值和当前radio的value做比较,如果变量值等于当前radio的value,则当前radio选中。否则,如果变量值不等于radio的value,则当前radio不选中

ii. 当用户切换了radio的选中状态时:

如果当前radio选中,则v-model自动将当前radio的value值传到程序中保存在data中的变量里。如果当前radio未选中,则v-model什么也不做

  1. 示例: 在vue中选择性别:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/vue.js"></script>
</head>
<body>
  <div id="app">
    <h3>您的性别是: {{sex}}</h3>
    <!--因为用户选择性别时同时改变两个radio的checked属性,所以两个radio上都要写v-model:checked=xxx
        又因为无论用户点哪个input,修改的都是性别这一个属性值,所以两个radio绑定时都绑定sex一个变量-->
    性别: 
    <label><input type="radio" value="1" name="sex" v-model:checked="sex">男</label>
    <label><input type="radio" value="0" name="sex" v-model:checked="sex">女</label>
  </div>
  <script>
    new Vue({
      el:"#app",
      //因为页面上只需要一个变量sex标记性别
      data:{
        sex:1, //开局为1 表示男
      }
    })
  </script>
</body>
</html>
运行结果: