6、vue

1.理解MVVM思想

  • 什么是MVVM?
    • MVVM是Model-View-ViewModel的缩写。
  • MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想。
    • 在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。
  • 把Model和View关联起来的就是ViewModel。
    • ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。

2.vue.js介绍

  • Vue是一套用于构建用户界面的渐进式的轻量级框架

  • Vue是MVVM设计模式的具体实现方案

  • Vue只关注视图层,便于与第三方库或既有项目整合

  • Vue可以与现代化的工具链和各种类库结合使用,为复杂的SPA提供驱动

3.vue起步

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。

数据和 DOM 已经被建立了关联,所有东西都是响应式的

  • el
    • 类型:string | Element
    • 限制:只在用 new 创建实例时生效。
    • 详细:提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。
      • 可以是 CSS 选择器
      • 也可以是一个 HTMLElement 实例。
    • 在实例挂载之后,元素可以用 vm.$el 访问。
    • 如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。
  • data
    • 类型:Object | Function
    • 限制:组件的定义只接受 function。
    • 详细:Vue 实例的数据对象
      • Vue 会递归地把 data 的 property 转换为 getter/setter,从而让 data 的 property 能够响应数据变化。
      • 对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 创建的原生对象,原型上的 property 会被忽略。
      • 大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <!-- 引入vue的库文件 -->
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <!-- 显示数据的层 -->
        <div id="app">
            <!-- 文本差值表达式 模板语法 {{data的属性名}}-->
            {{message}}
        </div>
    </body>
    <script>
        //实例化 vue对象new Vue() 括弧放入json对象(key、value)
        let app = new Vue({
            el: "#app",//绑定一个页面已经存在的DOM元素作为vue挂载目标
            data: {//data表示vue实例的数据对象
                message: "Hello Vue"
            }
        });
    </script>
</html>

4.模板语法

  • Vue.js中使用的是基于html的模版语法,可以将Dom元素绑定至Vue实例中的数据。

  • 数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:

    <span>Message: {{ msg }}</span>
  • 页面中模版即就是通过{{}}方式来实现模版数据绑定。

4.1 数据绑定的方式

  • 单向绑定

    • 方式1:使用两对大括号{{}}

    • 方式2:使用v-text、v-html

  • 双向绑定

    • v-model指令实现

5.指令

5.1. 指令(绑定)

  • v-text

    • 预期:string   用法:数据绑定
    • 更新元素的 textContent
    • 如果要更新部分的 textContent,需要使用 {{ Mustache }} 插值
      <span v-text="msg"></span> 
      <!-- 和下面的一样 --> 
      <span>{{msg}}</span>
  • v-html

    • 预期:string   用法:数据绑定
    • 更新元素的 innerHTML。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译
    • 如果试图使用 v-html组合模板,可以重新考虑是否通过使用组件来替代
  • v-model

    • 预期:随表单控件类型不同而不同。
    • 限制:
      • <input>、<select>、<textarea>、components
    • 修饰符:
      • .lazy - 取代 input 监听 change 事件
      • .number - 输入字符串转为有效的数字
      • .trim - 输入首尾空格过滤
    • 用法:
      • 在表单控件或者组件上创建双向绑定
  • v-once 

    • 数据只绑定一次,只渲染元素和组件一次

单向绑定案例:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
        <title></title>
    </head>
    <body>
        <div id="app">
            <p>插入data中message属性名对应值:{{message}}</p>
            <p>v-html指令插入html代码<span v-html="rawHtml"></span></p>
            <p>v-text指令插入html代码<span v-text="rawHtml"></span></p>
            <p v-once>v-once绑定一次{{message}}</p>
        </div>
    </body>
    <script>
        let app = new Vue({
            el: "#app",//把页面已经存在的dom作为vue挂载目标
            data: {//vue实例的数据对象
                message: "Hello Vue",
                rawHtml: "<a href='http://www.baidu.comn'>百度</a>"
            }
        });
    </script>
</html>

双向绑定案例:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <p v-once>v-once绑定一次{{message}}</p>
            <p>input的双向值{{message}}</p>
            <input type="text" v-model="message"><br>
            <input type="radio" v-model="gender" value="男"><input type="radio" v-model="gender" value="女"><p>gender:{{gender}}</p>
            <input type="checkbox" v-model="hobbies" value="吃"/><input type="checkbox" v-model="hobbies" value="喝"/><input type="checkbox" v-model="hobbies" value="玩"/><input type="checkbox" v-model="hobbies" value="乐"/><p>hobbies:{{hobbies}}</p>
            <select v-model="city">
                <option value="cq">重庆</option>
                <option value="bj">北京</option>
                <option value="gs">甘肃</option>
            </select>
            <p>city:{{city}}</p>
            <hr/>
            <!-- 在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 -->
            你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:
            <input type="text" v-model.lazy="message"/>
            <p>message:{{message}}</p>
            可以自动去除用户输入的字符首尾的空白字符,可以给v-model添加trim修饰符:
            <input type="text" v-model.trim="message"/>
            <p>message:{{message}}</p>
            转换用户输入的值为数值类型 ,可以给v-model天剑number修饰符:
            <input type="text" v-model.number="message"/>
            <p>message:{{message}}</p>
        </div>
    </body>
    <script>
        let app = new Vue({
            el: "#app",
            data: {
                message: "Hello Vue",
                gender: "男",
                hobbies: ["吃"],
                city: "cq",
                age: 17
            }
        });
    </script>
</html>

5.2. v-bind(绑定)

  • v-bind
    • 使用v-bind指令进行DOM属性绑定
    • v-bind:属性名="属性值"   等价于   :属性名="属性值"
  • style和class属性用法:

    • style:     .txt{color:#ff0000;} .bgcolor{background-color: #ccc;}

    • html:    <span :class="{txt:true,bgcolor:true}">文本内容</span>

  • v-bind属性数据绑定代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <p v-bind:id="dynamicId">v-bind绑定属性值</p>
            <p :id="dynamicId">v-bind绑定属性值(简化版)</p>
        </div>
    </body>
    <script>
        var app = new Vue({
            el:"#app",
            data:{dynamicId:"Carat17"}
        });
    </script>
</html>


  • v-bind属性操作class和style的实例代码
    • 我们可以传给 v-bind:class 一个对象。
    • 以动态地切换 class 也可以在对象中传入更多字段来动态切换多个 class。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <style>
            .active {color: #92a8d1;}
            .bgColor {background: #f7cac9;}
        </style>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div :class="{active:isActive,bgColor:isBgColor}">绑定class属性</div>
            <div v-bind:style="{color:color,background:background}">绑定内联样式</div>
            <div :style="styleObject">绑定样式对象</div>
        </div>
    </body>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                isActive: true,//false -> 对应的样式不显示
                isBgColor: true,
                color: "#f7cac9",
                background: "#92a8d1",
                styleObject: {
                    background: "#f7cac9",
                    color: "#92a8d1",
                }
            }
        });
    </script>
</html>

5.3 v-if、v-else-if、v-else(条件渲染)

  • v-if、v-elseif、v-else

    • v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

    • 可以使用v-else-if指令来充当 v-if 的“else-if 块",可以连续使用。

    • 可以使用 v-else 指令来表示 v-if 的“else块”。

    • v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

    • v-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后。

    • 控制切换一个元素是否显示也相当简单:

  • 实例代码

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div v-if="isBl">显示</div>
            <div>哈哈</div>
            <!--v-else必须紧跟在v-if或者v-else-if后面,否则不显示,控制台error-->
            <!--<div v-else>不显示</div>-->
            <div v-if="type === 'A'">A</div>
            <div v-else-if="type === 'B'">B</div>
            <div v-else-if="type === 'C'">C</div>
            <div v-else>非ABC</div>
        </div>
    </body>
    <script>
        let app = new Vue({
            el: "#app",
            data: { isBl: false,type: "B"
            }
        });
    </script>
</html>

5.4 v-for

  • v-for  
    • 基于源数据多次渲染元素或模板块,对数组或对象进行循环操作。
    • 遍历一个数组和数组对象
  • 我们可以用 v-for 指令基于一个数组来渲染一个列表。
    • v-for 指令需要使用 item in items 形式的特殊语法
    • items 是源数据数组,而 item 则是被迭代的数组元素的别名
  • 实例代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <ul><!--1、遍历一个数组arr-->
                <li v-for="item in arr">{{item}}</li></ul>
            <ul><!--2、v-for支持第二个参数-->
                <li v-for="(item,index) in arr">
                    索引为:{{index}} ; 元素值为:{{item}}</li></ul>
            <ul><!--3、v-for遍历一个对象数组-->
                <li v-for="item in objs">
                    {{item.name}}</li></ul>
            <ul><!--4、v-for遍历一个对象数组-->
                <li v-for="(item,index) in objs">
                    {{index}}--{{item.id}}--{{item.name}}</li></ul>
            <table border="1px" cellspacing="0px">
                <tr><td>编号</td><td>用户名</td></tr>
                <tr v-for="item in objs">
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                </tr>
            </table>
        </div>
    </body>
    <script>
        let app = new Vue({
            el: "#app",
            data: {//格式:key:value
                arr: ["a", "b", "c"],//value是一个数组,用[]表示一个数组
                objs: [//value是对象数组,[]表示数组,{}表示对象,对象中每一对属性为key和value
                    {id: 1, name: "yi"},
                    {id: 2, name: "you"},
                    {id: 3, name: "wang"}
                ]
            }
        });
    </script>
</html>

5.5 v-on(绑定事件)

  • v-on

    • 用来绑定事件

    • 用法:v-on:事件="函数"

    • 简写方式@事件="函数"

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <input v-on:click="method1()" value="事件监听器1(v-on)" type="button">
            <input v-on:click="method2(17)" value="事件监听器2(v-on)" type="button">
            <input v-on:click="method3(526)" value="事件监听器3(v-on)" type="button">
            <input v-on:click="method4()" value="事件监听器4(v-on)" type="button">
            <!-- v-on.once 表示只做一次-->
            <input type="button" value="只做一次事件绑定(v-on.once)" v-on:click.once="method4()"/>
            <!-- 使用指令v-on的缩写@绑定事件 属性指令v-bind缩写绑定属性 -->
            <input type="button" value="事件监听器5(v-on缩写@绑定)" @click="method1()" :id="dymId"/>
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                dymId: "E23650",
                name: "yi",
                age: 21,
                obj: {username: "jack", password: "12345"
                },
                objs: [
                    {username: "lucy",password: "123"},
                    {username: "tom",password: "321"}
                ]
            },
            methods: {
                method1: function () {//es5 定义方法
                    console.log("method1无参函数");
                },
                method2: function (param) {//es6 定义方法
                    console.log("method2有参函数,参数(param):" + param);
                },
                method3: function (param) {
                    console.log(`method3有参数函数,参数param:${param}`);//es6 模板字符串
                    return "返回值";
                },
                method4: function () {
                    /*`this` 指向 vm 实例
                      this表示当前实例(method4所在的实例)*/
                    let result = this.method3("method4调用method3");
                    console.log("result:" + result);
                    //调用当前实例的data数据
                    console.log("data: " + this.name);
                    console.log(`data: ${this.obj.username} ; ${this.obj.password}`);
                    for (let i of this.objs) {
                        console.log(`${i.username} ; ${i.password}`);
                    }
                    /*
                        for-in总是得到对象的key或数组、字符串的下标。
                        for-of总是得到对象的value或数组、字符串的值,另外还可以用于遍历Map和Set。
                    */
                },
            }
        });
    </script>
</html>

6.计算属性

  • 计算属性

    • 计算属性也是用来存储数据的,但具有以下几个特点:

      • 数据可以进行逻辑处理操作

      • 可以对计算属性中的数据进行监视

  • 计算属性缓存 vs 方法
    • 我们可以将同一函数定义为一个方法而不是一个计算属性。
    • 两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
    • 注意:

      • 计算属性是基于它的依赖进行更新的,只有在相关依赖发生改变时才能更新。

      • 计算属性是缓存的,只要相关依赖没有改变,多次访问计算属性得到的值是之前缓存的计算结果,不会多次执行

    • get和set方法

      • 计算属性由两部分组成:get和set,分别用来获取计算属性和设置计算属性的操作。

      • 默认只有get方法。

  • 代码实例:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            普通属性
            <p>{{message}}</p><!--显示message值-->
            <p>{{message.split('').reverse().join('')}}</p>
            计算属性
            <p>{{reverseMessage}}</p><!--可以像普通属性一样在模板中绑定计算属性-->
            <p>{{reverseMessage}}</p>
            绑定2次数据,缓存关闭控制台打印2次,说明调用了2次
            <p>{{myReverseMessage}}</p>
            <p>{{myReverseMessage}}</p>
            <p>{{reverseMessageMethod()}}</p>
            <p>{{reverseMessageMethod()}}</p>
            <input @click="changeMyReverseMessage" value="设置计算属性的值(setter)" type="button">
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                message: "Vue"
            },
            methods: {
                reverseMessageMethod: function () {
                    console.log("调用reverseMessageMethod方法");
                    return this.message.split('').reverse().join('');
                },
                changeMyReverseMessage: function () {
                    console.log("调用changeMyReverseMessage方法");
                    this.myReverseMessage = 'React';
                }
            },
            computed: {
                //计算属性的getter
                reverseMessage: function () {//不能关闭缓存
                    console.log("计算属性");
                    return this.message.split('').reverse().join('');// `this` 指向 vm 实例
                },
                //计算属性默认只有getter,不过在需要时也可以提供一个setter:
                myReverseMessage: {
                    //cache: false,//关闭缓存
                    /*
                        es5    get: function (){}
                        es6    get(){}
                    */
                    get() {
                        console.log("myMessage的get计算属性..")
                        return this.message.split('').reverse().join('');
                    },
                    set(msg) {
                        this.message = msg;
                    }
                }
            },
        });
        //现在再运行vm.myReverseMessage = "abc"时,setter会被调用,message会被更新。
        //vm.myReverseMessage = "abc";
    </script>
</html>

7.监听属性

7.1 watch

监听属性watch它是一个对data的数据监听回调, 当依赖的data的数据变化时, 会执行回调。在回调中会传入newVal和oldVal两个参数。Vue实列将会在实例化时调用$watch(), 他会遍历watch对象的每一个属性。

watch的使用场景是:当在data中的某个数据发生变化时, 我们需要做一些操作, 或者当需要在数据变化时执行异步或开销较大的操作时. 我们就可以使用watch来进行监听。

watch分为普通监听和深度监听

7.2 普通监听

缺点:不能深度监听(对象属性的改变),刷新或首次加载不能执行。

普通监听代码示例

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <p>{{message}}</p>
            <p>
                <input v-model="age" type="text">
            </p>
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                message: "",
                age: 21,
                desc: "单身"
            },
            watch: {
                //1、普通监听
                age(newVal, oldVal) {
                    this.message = `今年${newVal}岁,保持${this.desc}`;
                }
            }
        });
    </script>
</html>

7.3. handler方法immediate属性

如上watch有一个特点是: 第一次初始化页面的时候, 是不会去执行age这个属性监听的, 只有当age值发生改变的时候才会执行监听计算. 因此我们上面第一次初始化页面的时候, 'message' 属性值默认为空字符串。那么我们现在想要第一次初始化页面的时候也希望它能够执行 'age' 进行监听, 最后能把结果返回给 'message' 值来。因此我们需要修改下我们的 watch的方法,需要引入handler方法和immediate属性

  • handle:watch中需要具体执行的方法。
  • immediate:true立即执行handle方法(首次加载、刷新、数据改变)都会执行,缺点监听不到对象属性中发生的改变。

代码如下所示:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <p>{{message}}</p>
            <p><input v-model="age" type="text"></p>
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                message: "",
                age: 21,
                desc: "单身"
            },
            watch: {
                age: {
                    handler(newVal, oldVal) {
                        this.message = `今年${newVal}岁,保持${this.desc}`;
                    },
                    immediate: true
                }
            }
        });
    </script>
</html>

 

7.4 deep属性

deep:true深度检测。例如数组对象中的某个属性改变执行handle方法。

watch里面有一个属性为deep,含义是:是否深度监听某个对象的属性的值, 该值默认为false。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script type="text/javascript" src="js/vue.js"></script>
	</head>
	<body>
		<div id="app">
			<p>信息:{{obj.message}}</p>
			<!-- 双向绑定数据属性age -->
			<!-- <p>年龄:<input type="text" v-model="age"/></p> -->
            <!-- 双向绑定数据属性obj的age属性 -->
			 <p>年龄:<input type="text" v-model="obj.age"/></p> 
		</div>
	</body>
	<script type="text/javascript">
		var app = new Vue({
			el:"#app",
			data:{
				/* message:"",
				age:31,
				desc:"单身" */
				
				//obj包含3个属性对象
				obj:{
					message:"",
					age:31,
					desc:"单身"
				}
			},
			watch:{
				//监听数据属性age,当age发生变化,触发如下的匿名函数,但是第一次加载页面不触发函数
				/* age:function(newVal,oldVal){
					this.message = '今年'+newVal+"岁,"+this.desc;
				} */
				//监听数据属性age,当age发生变化,触发如下的handler方法
				//age监听还加入了immediate为true的属性,表示第一次页面加载的时候也会执行该handler函数的
				/* age:{
					handler:function(newVal,oldVal){
						this.message = '今年'+newVal+"岁,"+this.desc;
					},
					immediate:true
				} */
				//监听obj对象
				obj:{
					handler:function(newVal,oldVal){
						this.obj.message = '今年'+newVal.age+"岁,"+this.obj.desc;
					},
					immediate:true,
					deep:true //需要添加deep为true即可对obj进行深度监听
				} 
			}
		});
	</script>
</html>

如上述代码, 如果我们不把 deep: true添加的话,当我们在输入框中输入值的时候,改变obj.age值后,obj对象中的handler函数是不会被执行到的。受JS的限制, Vue不能检测到对象属性的改变。它只能监听到obj这个对象的变化,比如说对obj变化操作会被监听到,但是对obj的内部属性的值变化监听不到。

7.5 watch 和 computed的区别是:

相同点:他们两者都是观察页面数据变化的。

不同点:computed只有当依赖的数据变化时才会计算, 当数据没有变化时, 它会读取缓存数据。 watch每次都需要执行函数。watch更适用于数据变化时的异步操作。

8.VUE生命周期钩子

8.1 vue数据是怎么驱动视图的

一堆数据放在那里是不会有任何作用的,它必须通过我们的View Model(视图模型)才能操控视图。

图中的model其实就是数据,一般我们写成js对象的格式;

中间的这个所谓View Model,就是

var vm = new Vue({
	el: '#app',
	data: {
		message: 'Hello Vue!'
	}
})

我们把数据放到了vm里,然后把各种directive(命令)放到视图里,这样我们就可以以vm为媒介,通过改变数据来改变视图。

误区

vm实际上是Vue这个类的一个实例,非常容易混淆的是,你会很容易的以为

vm.el == '#app';
vm.data== {
	message: 'Hello Vue!'
};
vm.data.message =='Hello Vue!';

实际上这都是错误的,

以上虽然是错误的写法,有时候我们还是希望获得这些属性的,通过vm你需要这样写:

vm.$el === document.getElementById('app') //结果为true
vm.$data.message ==='Hello Vue!' //结果为true

这个$符代表的就是vm的真实属性了

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body><div id="app"></div></body>
    <script>
        let obj = {
            message: "Hello Vue"
        }
        let vm = new Vue({
            el: "#app",
            data: obj
        });
        console.log(vm.el === "#app");//false
        console.log(vm.data === {message: "Hello Vue"});//false
        console.log(vm.data);//undefined
        //console.log(vm.data.message === "Hello Vue");//报错

        console.log(vm.$el === "#app");//false
        console.log(vm.$el === document.getElementById("app"));//true
        console.log(vm.$el);//<div id="app"></div>  //div的dom对象
        console.log(vm.$data === {message: "Hello Vue"});//false
        console.log(vm.$data === obj);//true
        console.log(vm.$data);//创建vue对象时,传入的data对应的对象
        console.log(vm.$data.message === "Hello Vue");//true
    </script>
</html>

8.2 vue生命周期钩子

  • 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

  • 比如 created 钩子可以用来在一个实例被创建之后执行代码:

new Vue({
	data: {
		a: 1
	},
	created: function () {
		// `this` 指向 vm 实例 
		console.log('a is: ' + this.a)
	}
})
// => "a is: 1"
  • vue实例从创建到销毁的过程,称为生命周期,共有八个阶段

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            message:{{message}}<br>
            <input v-model="message" type="text">
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                message: "hello"
            },
            beforeCreate() {
                console.dir("Vue对象创建前状态======");
                console.dir("el=" + this.$el);
                console.dir("data=" + this.$data);
                console.dir("message=" + this.message);
            },
            created() {
                console.dir("Vue对象创建完毕状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            },
            beforeMount() {
                console.dir("Vue对象挂载前状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            },
            mounted() {
                console.dir("Vue对象挂载结束状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            },
            beforeUpdate() {
                console.dir("Vue对象更新前状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            },
            updated() {
                console.dir("Vue对象更新完成状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            },
            beforeDestroy() {
                console.dir("Vue对象销毁前状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            },
            destroyed() {
                console.dir("Vue对象销毁完成状态======");
                console.dir("el=" + this.$el);
                console.dir(this.$data);
                console.dir("message=" + this.message);
            }
        });
        //销毁了就不能更新了
        /*console.dir(vm.$data);
        vm.$destroy();*/
    </script>
</html>

简化版生命周期

9.VUE完整示例new vue()参数详解

  • 1.1el:"#id", //DOM成员

    • 提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。

  • 1.2 template:"<tag></tag>`", //DOM成员

    • 一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发 slot

  • 1.3 render: (h)=>{h(App)}, //DOM成员

    • 字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。

  • 1.4 data //数据成员

    • data():{ return{ } },

    • Vue实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化

  • 1.5 methods //数据成员

    • methods:{ func(){ } }

    • methods将被混入到 Vue 实例中,可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用

    • 方法中的 this自动绑定为 Vue 实例

  • 1.6 watch //监听属性

    • watch:{ key:value $route:function (newValue, oldValue) { //监控路由 } }

    • 整个为一个对象,键是需要观察的表达式,值是对应回调函数

  • 1.7 computed //计算属性

    • computed:{ getTotalCount(){ const totalCount=0; return totalCount; } },

    • vue的计算属性,将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例

  • 1.8 props //数据成员

    • props:['counts','ids'],

    • 用于父子组件的eventbus传值,是数组或对象,props的成员是子组件接收的来自父组件的数据

  • 1.9 propsData //数据成员

    • 创建实例时传递 props。主要作用是方便测试

  • 1.10 filters //资源

    • filters('filterName',(input,function(){ return newvalue }))

    • 包含 Vue 实例可用过滤器的哈希表。

  • 1.11 directives //资源

    • 包含 Vue 实例可用指令的哈希表。

  • 1.12 components //组件

    • (即该组件的子实例)这里是包含 Vue 实例可用组件的哈希表。

  • 1.13 name //杂项

    • 允许组件模板递归地调用自身。注意,组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name。

  • 1.14 parent //杂项

    • 指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.parent访children 数组中。

  • 1.15 mixins //杂项

    • mixins 选项接受一个混合对象的数组。Mixin钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。

  • 1.16 extends //杂项

    • 允许声明扩展另一个组件。这主要是为了便于扩展单文件组件。这和 mixins 类似,区别在于,组件自身的选项会比要扩展的源组件具有更高的优先级。

  • 1.17 delimiters //杂项

    • 改变纯文本插入分隔符。

  • 1.18 functional //杂项

    • 使组件无状态(没有 data )和无实例(没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使他们更容易渲染。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script src="js/vue.js" type="text/javascript"></script>
	</head>
	<body>
		<div id="app">

		</div>
	</body>
	<script type="text/javascript">
		var vm = new Vue({
            // 挂载点,可认为是element的简写,创建一个vue实例得知道是在哪一块元素上创建Vue实例,对哪一块视图进行操作
			el: "#app", 
            // 又称内部数据,该属性值可以是对象,也可以是函数,但优先推荐使用函数,对象里的函数又称方法。
			data: {}, 
        //  并且若是组件中的data则必须使用函数。数据成员,在.vue组件中data是一个函数,要写成data () {}这种方式
			
        // 又称外部数据,一般用于组件中接受外来传递的数据,在组件使用时,通过标签全局属性的方式进行传参
			props: {} 
			computed: { // 计算属性
				// 计算属性名: function(){..}
			},
			watch: { // 监视属性

			},
			methods: { // 成员方法
				// 方法名: function(){..}
			},
			template: "" // 模板
			beforeCreate: function() { // 生命构造函数,还有其他7个...

			},
			compontents: {

			},
			// 其他option....
		})
	</script>
</html>

10.Vue组件的定义、注册方式和模板使用

10.1 组件的定义

组件(Component)是 Vue.js 最强大的功能之一。

组件可以扩展 HTML 元素,封装可重用的代码.

vue组件是把页面(html代码,CSS代码)进行模块化

如下图所示,一个页面分四个部分,每个部分是个vue组件,而页面成了vue组件的容器。

vue.js中创建组件有三个步骤:定义组件,注册组件以及使用组件

定义组件

  • 方式1:先创建一组件构造器,然后由组件构造器创建组件。如:var myCom = Vue.extend({})

  • 方式2:使用Vue.component()直接创建组件。

定义组件名的方式有两种:

  • 使用 kebab-case:使用 kebab-case (短横线分隔命名) 定义一个组件(kebab发音/kI'ba:b/)

  • 使用 PascalCase:(驼峰式命名) 定义一个组件

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>。当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name><MyComponentName> 都是可接受的。

10.2 组件的分类

  • 全局组件

  • 局部组件

    通过选项components定义,但只能在当前Vue实例中使用

10.3 引用模版

将组件内容放到<template>中引用。

注意:template组件中,有且只有一个根元素

代码示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>全局组件和局部组件</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app1">
            <h1>{{message}}</h1>
            <!-- 使用全局组件 -->
            <global-componet-a></global-componet-a>
        </div>
        <div id="app2">
            <h1>{{message}}</h1>
            <!-- 使用全局组件 -->
            <global-componet-a></global-componet-a>
        </div>
        <div id="app3">
            <h1>{{message}}</h1>
            <!-- 使用全局组件 -->
            <global-componet-a></global-componet-a>
            <!-- 使用局部组件 -->
            <component-a></component-a>
        </div>
    </body>
    <!-- 定义模板 -->
    <template id="myTemplate">
        <!--template模板只能定义一个根元素-->
        <div>
            <h3>{{info}}</h3>
            <a href='http://www.baidu.com'>百度</a>
        </div>
    </template>
    <script>
        //全局组件定义和注册 Vue.component("全局组件名称",{})
        //{} 相当于创建vue对象传入的选项参数
        Vue.component("global-componet-a", {
            template: `#myTemplate`,
            //定义的数据属性要用函数返回对象(对象中存储数据),因为组件每次使用都是一个独立的对象实例,
            //如果data使用原来的方式还是返回的是对象,不调用函数,那么组件每个实例使用的是同一个data属性对象,使用组件的每个实例,共享data属性数据对象实例,是不可以的
            data() {
                return {
                    info: "全局组件hello"
                }
            }
            //使用的是模板,使用模板id或者模板放入html标签
            //template:"#myTemplate"
        });

        let app1 = new Vue({
            el: "#app1",
            /*data:{
                message:"vue-1调用全局组件"
            }*/
            data() {
                return {
                    message: "vue-1调用全局组件"
                }
            }
        });

        let app2 = new Vue({
            el: "#app2",
            /*data:{
                message:"vue-2调用全局组件"
            }*/
            data() {
                return {
                    message: "vue-2调用全局组件"
                }
            }
        });

        let app3 = new Vue({
            el: "#app3",
            /*data:{
                message:"vue-3调用全局组件全局组件和局部组件"
            },*/
            data() {
                return {
                    message: "vue-3调用全局组件全局组件和局部组件"
                }
            },
            //局部组件定义和注册,使用范围只能在app3挂载的DOM对象中
            components: {
                //可以定义多个组件
                //格式 组件名称:{}
                "component-a": {
                    data() {
                        return {info: "局部组件a的hello"}
                    },
                    //使用的是模板,使用模板id或者模板放入html标签
                    template: `#myTemplate`
                },
                "component-b": {
                    data() {
                        return {info: "局部组件b的hello"}
                    },
                    //使用的是模板,使用模板id或者模板放入html标签
                    template: `#myTemplate`
                }
            }
        });
    </script>
</html>

11. 组件间数据的通信

11.1 父子组件

在一个组件内部定义另一个组件,称为父子组件

子组件只能在父组件内部使用

默认情况下,子组件无法访问父组件中的数据,每个组件实例的作用域是独立的

11.2 组件间数据通信

11.2.1 子组件访问父组件的数据(父传子)

  • 在调用子组件时,绑定想要获取的父组件中的数据在子组件内部,使用props选项声明获取的数据,即接收来自父组 件的数据。

  • 即父组件通过props向下传递数据给子组件。

  • 注:组件中的数据存在方式共有三种:data、props、computed

props传递数据两种形式

  • 数组方式: props:['msg', 'username', 'age', 'userObj']

  • 对象方式:该方式提供了对数据的校验、类型监测、默认值设置操作。

prop命名,可以在在组件中使用postTitle(驼峰名称),在html中是使用短横线命名post-title,如下代码示例

Vue.component('blog-post', {
  // 在 JavaScript 中是 驼峰命名 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 短横线 的 -->
<blog-post post-title="hello!"></blog-post>

父组件传子组件的代码实例

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
        <title></title>
    </head>
    <body>
        <div id="app">
            <h2>{{message}}</h2>
            <input type="text" v-model="message">
            <!--props是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来,即子组件中使用的父组件数据发生变化,无法传导给父组件。-->
            <!--使用局部组件-->
            <!--局部组件props数组定义的属性名称,在html使用传值时,v-bind属性绑定 绑定模式-->
            <component-a :parent-message="message"></component-a>
        </div>
    </body>
    <template id="myTemplate">
        <div>
            <p>{{childMessage}}</p>
            <p>父组件传入的值:{{parentMessage}}</p>
        </div>
    </template>
    <script>
        //定义子组件对象
        let componentA = {
            template: `#myTemplate`,//使用模板
            data() {
                return {
                    childMessage: "子组件定义childMessage的值"
                }
            },
            //通过props属性定义的数组,来定义外界传入组件的值
            props: ["parentMessage"]
        }
        new Vue({
            el: "#app",
            data() {
                return {
                    message: "父的message的值"
                }
            },
            //注册子组件
            components: {
                //格式   "子组件名称":定义的子组件对象
                "component-a": componentA
            }
        });
    </script>
</html>

11.2.2 父组件访问子组件的数据(子传父)

  • 第一步:在子组件中使用 vm.$emit(事件名,数据) 触发一个自定义事件,事件名自定义。

  • 第二步:父组件在使用子组件的地方监听子组件触发的事件,并在父组件中定义方法,用来获取数据。

  • 总结:子组件通过events(事件)给父组件发送消息,实际上就是子组件把自己的数据发送到父组件

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <h2>{{info}}</h2>
            <!--使用局部子组件-->
            <!--绑定自定义item-click事件到父组件回调方法上-->
            <component-a @item-click="parentClick"></component-a>
        </div>
    </body>
    <!--模板-->
    <template id="myTemplate">
        <div>
            <!--根据分类展示多个按钮,当点击按钮会调用方法btnClick并把每一类的对象传进去-->
            <input type="button" v-for="item in categories" :value="item.name" @click="btnClick(item)">
            <!--<input type="button" v-for="item in categories" v-bind:value="item.name" v-on:click="btnClick(item)">-->
        </div>
    </template>
    <script>
        //定义组件
        let componentA = {
            template: `#myTemplate`,
            data() {
                return {
                    categories: [
                        {id: 1, name: "电脑"},
                        {id: 2, name: "一毛"},
                        {id: 3, name: "数据"},
                        {id: 4, name: "生鲜"},
                    ]
                }
            },
            methods: {
                btnClick(item) {
                    // 触发自定义的事件同时把item数据传进去
                    // //vm.$emit( eventName, […args] )
                    // $emit("自定义事件的名称",数据):触发自定义事件
                    this.$emit("item-click", item);
                }
            }
        }
        //根组件
        let vm = new Vue({
            el: "#app",
            data() {
                return {
                    info: "哈哈",
                }
            },
            components: {
                "component-a": componentA
            },
            methods: {
                // 父组件的回调函数,接受参数(参数是子组件触发自定义事件传进来)
                parentClick(item) {
                    this.info = `子组件点击按钮,父组件收到子组件数据id为${item.id},name为${item.name}`
                }
            }
        })
    </script>
</html>

11.2.3 非父子通信

适用于组件间全部通信方式。

创建一个Vue实例作为中央事件总线,通过它来监听(on)(emit)事件。

//实例Vue实例作为中央事件总线
var Event=new Vue();
//发送事件
Event.$emit(事件名,数据);
//监听事件
Event.$on(事件名,data => {});

假设兄弟组件有三个,分别是 A、B、C 组件,C 组件如何获取 A 或者 B 组件的数据

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../js/vue.js"></script>
        <title></title>
    </head>
    <body>
        <div id="app">
            <!--使用三个组件 A B C-->
            <my-a></my-a>
            <my-b></my-b>
            <my-c></my-c>
        </div>
    </body>
    <!--定义A B C 的模板-->
    <template id="a">
        <div>
            <h3>{{msg}}</h3>
            <!--<input type="button" :value="msg" @click="send">-->
            <input type="button" value="A组件将数据发送到C组件" @click="send">
        </div>
    </template>
    <template id="b">
        <div>
            <h3>{{msg}}</h3>
            <input type="button" value="B组件将数据发送到C组件" @click="send">
        </div>
    </template>
    <template id="c">
        <div>
            <h3>{{msg}}</h3>
            <p>{{name}}</p>
            <p>{{age}}</p>
        </div>
    </template>
    <script>
        //空的Vue实例对象触发事件和绑定事件,完成数据传递
        let Event = new Vue();
        //定义三个组件 A B C
        let A = {
            template: `#a`,
            data() {
                return {
                    msg: "A组件",
                    name: "yi"
                }
            },
            methods: {
                send() {
                    /*
                        $emit( eventName, […args] )触发自定义事件 把数据传出
                        触发当前实例上的事件。附加参数都会传给监听器回调。
                    */
                    Event.$emit("data-a", this.name);
                }
            }
        };
        let B = {
            template: `#b`,
            data() {
                return {
                    msg: "B组件",
                    age: 21
                }
            },
            methods: {
                send() {
                    Event.$emit("data-b", this.age);
                }
            }
        };
        let C = {
            template: `#c`,
            data() {
                return {
                    msg: "C组件",
                    name: "",
                    age: ""
                }
            },
            //Vue实例对象被挂载后调用mounted()
            mounted() {
                /*
                    $on( event, callback )监听当前实例上的自定义事件。
                    事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。
                */
                Event.$on("data-a", (name) => {
                    //箭头函数:(参数)=>{}
                    this.name = name;
                    //箭头函数内部不会产生新的this
                });
                //箭头函数,当传入的参数只有一个时,可以省略小括号
                Event.$on("data-b", age => this.age = age);
            }
        };
        //根组件
        let vm = new Vue({
            el: "#app",
            components: {//注册三个组件 A B C
                "my-a": A,
                "my-b": B,
                "my-c": C
            },
        });
    </script>
</html>

11.2.4 总结组件之间的通讯

11.3 单向数据流

props是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来,即子组件中使用的父组件数据发生变化,无法传导给父组件。而且不允许子组件直接修改父组件中的数据,会直接报错,这样更有利于组件间的解耦

解决方式:

  • 方式1:如果子组件想把它作为局部数据来使用,可以将数据存入另一个变量中再操作,不影响父组件中的数据。

  • 方式2:如果子组件想修改数据并且同步更新到父组件

    • 可以将父组件中的数据包装成对象,然后在子组件中修改对象的属性(因为对象是引用类型,指向同一个内存空间),推荐使用。

12.过滤器

常见的场景:当我们从后端请求到数据列表时,我们需要对其中符合条件的数据进行筛选。

如当我们拿到数据,希望把英文首字母大写,等等。

过滤器分为两种

  • 单个组件的过滤器,也叫做局部过滤器,

  • vue实例全局的过滤器,它可以被应用在任何地方。

过滤器使用地方两个位置

   双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。

  • {{ message | filterA }}双括号插值内

  • <h1 v-bind:id="message | filterA">{{ message }}</h1>v-bind绑定的值的地方

过滤器也可以使用多个,会依次执行

例如:{{ message | filterA |filterB }} 这个例子中会把message 的当做参数传入A过滤器进行过滤,A过滤器过滤完的数据返回值会传入B过滤器

全局过滤器:Vue.filter("过滤器名称",函数);

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app1"><h2>{{message | upperCase}}</h2></div>
        <div id="app2"><h2>{{message | upperCase}}</h2></div>
    </body>
    <script>
        /*Vue.filter( id, [definition] )
            参数:{string} id ; {Function} [definition]
            用法:注册或获取全局过滤器
        */
        Vue.filter("upperCase", (value) => {
            if (!value) return "";
            value = value.toString();
            return value.toUpperCase();//字符转换成大写
        });
        //创建Vue对象
        new Vue({
            el: "#app1",
            data() {
                return {
                    message: "Hello Vue"
                }
            }
        });
        new Vue({
            el: "#app2",
            data() {
                return {
                    message: "Hello React"
                }
            }
        });
    </script>
</html>

局部过滤器:定义局部的过滤器 定义格式:fliters:{过滤器名称:函数}

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="app"><component-a :msg="message"></component-a></div>
    </body>
    <template id="myTemplate"><h3>{{msg | capitalize}}</h3></template>
    <script>
        let componentA = {//定义局部组件
            props: ["msg"],
            template: `#myTemplate`,
            filters: {//定义局部过滤器,注意是filters
                capitalize(value) {
                    if (!value) return "";
                    value = value.toString();
                    value = value.charAt(0).toUpperCase() + value.substring(1);
                    return value;
                }
            }
        }
        new Vue({//根组件
            el: "#app",
            data() {
                return {
                    message: "hello"
                }
            },
            components: {//注册局部组件
                "component-a": componentA
            }
        });
    </script>
</html>


13.路由

13.1 定义

  • 路由,其实就是指向的意思,当我点击页面上的home按钮时,页面中就要显示home的内容,如果点击页面上的about 按钮,页面中就要显示about 的内容。

13.2 分类

13.2.1 后端路由

例如,分配一个站点,服务器地址是:http://192.168.1.200:8899,在这个网站中提供了三个界面

http://192.168.1.200:8899/index.html          主页
http://192.168.1.200:8899/about/about.html  关于我们页面
http://192.168.1.200:8899/feedback.html       反馈界面

当我们在浏览器输入 http://192.168.1.200:8899/index.html 来访问界面的时候,web 服务器就会接收到这个请求,然后把 index.html 解析出来,并找到相应的 index.html 并展示出来,这就是路由的分发,路由的分发是通过路由功能来完成的

13.2.2 前端路由

1、虽然前端路由和后端路由的实现方式不一样,但是原理都有是相同的,其中一个方式

前端路由的功能都是通过 hash 「散列值」 来实现的,hash 能兼容低版本的浏览器

2、后端路由每次仿问一个页面都要向浏览器发送请求,然后服务端再响应解析,在这个过程中肯定会存在延迟,但是前端路由中仿问一个新的界面的时候只是浏览器的路径改变了,没有和服务端交互「所以不存在延迟」,这个对用户体验来说是大大的提高。如下所示:

http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/about.html
http://192.168.1.200:8080/#/feedback.html

由于 web 服务器不会解析 # 后面的东西「所以通过 hash 能提高性能」,但是客户端的 js 可以拿到 # 后面的东西,有一个方法是 window.location.hash 来读取,使用这个方法来匹配到不同的方法上

3、举个例子

http://www.xxx.com/path/a/b/c.html?key1=Tiger&key2=Chain&key3=abc#/path/d/e.html

我们把这个地址分析一下

http:协议
www.xxx.com:域名
/path/a/b/c.html:路由,即服务器上的资源
?key1=Tiger&key2=Chain&key3=abc:这 Get 请求的参数
#/path/d/e.html:hash 也叫散列值,也叫锚点

上面的 hash 是和浏览器交互的,其它的都是和服务器进行交互

13.3 Vue 路由

路由案例

推荐使用官方支持的 vue-router

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
        <script src="../js/vue-router-3.5.1.js"></script>
    </head>
    <body>
        <div id="app">
            <!--使用 router-link 组件进行导航 -->
            <!--Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。-->
            <!--通过传递 `to` 来指定链接 -->
            <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
            <router-link to="/">跳转到main</router-link>
            <router-link to="/message">跳转到message</router-link>
            <router-link to="/mine">跳转到mine</router-link>
            <!-- 路由出口/插座 -->
            <!--router-view 将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。-->
            <router-view>
                <!-- 路由匹配到的组件将渲染在这里 -->
            </router-view>
        </div>
    </body>
    <template id="t_main">
        <div style="width: 400px;height: 400px;background: #f7cac9">
            <h1>{{title}}</h1>
        </div>
    </template>
    <template id="t_message">
        <div style="width: 400px;height: 400px;background: #92a8d1">
            <h1>{{title}}</h1>
        </div>
    </template>
    <template id="t_mine">
        <div style="width: 400px;height: 400px;background: plum">
            <h1>{{title}}</h1>
        </div>
    </template>
    <script>
        /*Vue.extend( options )
            参数:{Object} options
            用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
            注意:data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数。
                 这里只是构造函数 但是还没有实例化所以还不是一个组件对象。
        */
        let Main = Vue.extend({//Main 组件构造器,控制台打印:ƒ VueComponent(options)
            template: `#t_main`,
            data() {return {title: "main"}},
        });
        let Message = Vue.extend({//Message 组件构造器,控制台打印:ƒ VueComponent(options)
            template: `#t_message`,
            data() {return {title: "message"}},
        });
        let mine = {//组件对象 控制台打印:Object
            template: `#t_mine`,
            data() {return {title: "mine"} },
        };
        let routes = [//定义一些路由,每个路由都映射到一个组件。
            {path: "/", component: Main},
            {path: "/main", component: Main},
            {path: "/message", component: Message},
            {path: "/mine", component: mine},
        ]
        let router = new VueRouter({//创建路由实例并传递 `routes` 配置
            routes   // `routes: routes` 的缩写
        });
        new Vue({
            el: "#app",
            //VueRouter对象赋值router属性
            router,  //相当于 router:router
        });
    </script>
</html>

嵌套路由

实际应用界面,通常由多层嵌套的组件组合而成。

比如,我们 “首页”组件中,还嵌套着 “登录”和 “注册”组件,那么URL对应就是/home/login和/home/register

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
        <script src="../js/vue-router-3.5.1.js"></script>
    </head>
    <body>
        <div id="app">
            <router-link to="/home">首页</router-link>
            <router-link to="/news">新闻</router-link>
            <router-view></router-view>
        </div>
    </body>
    <template id="t_home">
        <div style="width: 300px;height: 300px;background: #f7cac9">
            <h1>{{title}}</h1>
            <!--
            path:编码URL的pathname部分,与路由地址有关。
            query:从URL的search部分提取的已解码查询参数的字典。
            name:路由记录的名称。如果什么都没提供,则为undefined。
            params:从path中提取的已解码参数字典。
            -->
            <router-link :to="{path:'/home/login',
            query:{id:1,username:'张三',age:21}}">登录
            </router-link>
            <router-link :to="{name:'register',params:{'name':'李四','abc':123}}">注册</router-link>
            <router-view></router-view>
        </div>
    </template>
    <template id="t_news">
        <div style="width: 300px;height: 300px;background:plum ">
            <h1>{{title}}</h1></div>
    </template>
    <template id="t_login">
        <div style="width: 150px;height: 150px;background: #92a8d1">
            <h1>{{title}},{{this.$route.query.id}},{{this.$route.query.username}},{{this.$route.query.age}}</h1></div>
    </template>
    <template id="t_register">
        <div style="width: 150px;height: 150px;background: orange">
            <h1>{{title}},{{this.$route.params.name}}</h1></div>
    </template>
    <script>
        let componentHome = {//首页组件
            template: `#t_home`,
            data() {
                return {
                    title: "首页"
                }
            }
        };
        let componentNews = {//新闻组件
            template: `#t_news`,
            data() {
                return {
                    title: "新闻"
                }
            }
        };
        let componentLogin = {//登录组件
            template: `#t_login`,
            data() {
                return {
                    title: "登录"
                }
            }
        };
        let componentRegister = {//注册组件
            template: `#t_register`,
            data() {
                return {
                    title: "注册"
                }
            }
        };
        //定义一些路由,每个路由都映射到一个组件。
        let routes = [
            {path: "/", redirect: "/home"},//redirect 跳转url,如下当访问 / 跳转到 /home 这个路径
            {
                path: "/home",
                component: componentHome,
                children: [//子路由
                    {path: "/home/login", component: componentLogin},
                    //动态字段以冒号开始
                    //path路径使用:参数名 占位
                    //在router-link配置中使用params:{'参数名':'参数值'} 补位
                    {path: "/home/register/:name/:abc", component: componentRegister, name: "register"}
                ]
            },
            {path: "/news", component: componentNews},

        ]
        let router = new VueRouter({//创建路由实例并传递 `routes` 配置
            routes  // `routes: routes` 的缩写
        })
        new Vue({
            el: "#app",
            router
        });
    </script>
</html>

 14.使用axios进行ajax操作

14.1 Axios简介

vue本身不支持发送AJAX请求,需要使用vue-resource、axios等插件实现。

axios是一个基于Promise的HTTP请求客户端,用来发送请求,也是vue2.0官方推荐的,同时不再对vue-resource进行更新和维护。

参考:GitHub上搜索axios,查看API文档 https://github.com/axios/axios

14.2 Axios特点

Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中。本质上也是对原生XHR(XmlHttpRequest)的封装,只不过它是Promise 的实现版本,符合新的ES规范,有如下特点:

从浏览器中创建 XMLHttpRequests

从 node.js 创建 http 请求

支持 Promise API

拦截请求和响应

转换请求数据和响应数据

取消请求

自动转换 JSON 数据

客户端支持防御 XSRF

14.3 Axios基本用法

axios({options})

axios.get(url,{options});

GET传参方式:

1.通过url传参

2.通过params选项传参

POST传参方式:

axios.post(url,data,{options});

默认情况下,axios将JavaScript对象序列化为JSON。要以application / x-www-form-urlencoded格式发送数据,您可以使用以下选项之一。

传参方式:

1.自己拼接为键值对

2.使用transformRequest,在请求发送前将请求数据进行转换

3.如果使用模块化开发,可以使用qs模块进行转换

Vue中axios中箭头函数的this和function(response)函数体中的this的区别

1、在methods下的函数this指向的是当前创建的vue实例, 2、axios与后台交互后回调函数的内部的this指向window而并非指向当前的vue实例, 3、若想拿到后台回传的数据更新data里的数据,不能在回调函数中直接使用this,要用外部函数定义的变量(如:_this)存储的this,也就是当前vue的实例。 4、使用箭头函数之后,箭头函数指向的函数内部的this已经绑定了外部的vue实例了

Axios的get请求、post请求和传参方式代码示例

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../js/vue.js"></script>
        <script src="../js/axios-0.21.1.js"></script>
        <script src="../js/qs-6.10.1.js"></script>
    </head>
    <body>
        <div id="app">
            <input type="button" value="axiox请求1(get)" @click="sendAxiosGet1"/>
            <input type="button" value="axiox请求2(get)" @click="sendAxiosGet2">
            <input type="button" value="axiox请求3(get)" @click="sendAxiosGet3">
            <input type="button" value="axiox请求4(post)" @click="sendAxiosPost1()"/>
            <input type="button" value="axiox请求5(post)" @click="sendAxiosPost2()"/>
            <input type="button" value="axiox请求6(post)" @click="sendAxiosAjaxPost3()"/>
            <table border="1px" cellpadding="1" cellspacing="0">
                <tr>
                    <td v-for="value in columnName">{{value}}</td>
                </tr>
                <tr v-for="student in students">
                    <td>{{student.id}}</td>
                    <td>{{student.name}}</td>
                    <td>{{student.age}}</td>
                    <td>{{student.email}}</td>
                </tr>
            </table>
        </div>
    </body>
    <script>
        new Vue({
            el: "#app",
            data() {
                return {
                    columnName: {
                        no: "编号",
                        name: "姓名",
                        age: "年龄",
                        email: "邮箱",
                    },
                    students: [
                        {id: 1001, name: "张三", age: 18, email: "aa@qq.com"},
                    ]
                }
            },
            methods: {
                /*
                ajax使用语法:axios({}).then().catch()
                then() 表示ajax请求成功后,处理服务器响应数据
                catch() 表示ajax请求异常或者失败,处理异常或者失败情况
                */
                sendAxiosGet1() {//axios的get请求
                    var _this = this;
                    axios({
                        method: "get",//请求方式 get/post
                        url: "http://rap2api.taobao.org/app/mock/238982/students",//请求的服务器url地址
                        params: {}, //请求的数据参数
                        reponseType: "json", //服务器响应数据的类型
                        reponseEncoding: "utf-8" //服务器响应数据的编码格式
                    }).then(function (response) {//请求成功后的回调
                        console.dir(response);
                        console.dir(response.data);//服务器返回的数据
                        console.dir(response.data.students);
                        console.dir(this);  //this表示window对象,不是Vue对象实例
                        console.dir(_this);	//_this表示Vue对象实例
                        _this.students = response.data.students // 服务端返回的数据赋值给属性students,页面就展示了数据
                    }).catch(function (e) {//请求失败后的回调
                        console.log(e);
                        alert(e);
                    });
                },
                sendAxiosGet2() {
                    axios({
                        url: "http://rap2api.taobao.org/app/mock/238982/students",
                        //get 请求参数, get请求就会把数据拼接到url上
                        //http://rap2api.taobao.org/app/mock/238982/students?p1=A&p2=B
                        //params: {p1: 'A', p2: 'B'}转换成  ?p1=A&p2=B
                        params: {p1: 'A', p2: 'B'},
                        method: "get",
                        responseType: "json",
                        responseEncoding: "utf-8",
                    }).then((response) => {
                        //服务端返回的数据 response.data
                        console.log(response.data);
                        console.log(this);//this表示vue对象(箭头函数)
                        this.students = response.data.students;
                    }).catch(e => {//异常信息
                        console.log(e);
                    });
                },
                sendAxiosGet3() {
                    axios.get("http://rap2api.taobao.org/app/mock/238982/students", {
                        params: {p1: "A", p2: "B"}, //请求的数据参数
                        reponseType: "json", //服务器相应数据的类型
                        reponseEncoding: "utf-8" //服务器响应数据的编码格式
                    }).then((response) => {
                        this.students = response.data.students // 服务端返回的数据赋值给属性students,页面就展示了数据
                    }).catch((e) => {
                        console.log(e);
                        alert(e);
                    });
                },
                sendAxiosPost1: function () {
                    axios({
                        url: "http://192.168.80.254:8080/student/list",//请求服务器的url
                        method: "post",//请求方式
                        // `data` 是作为请求主体被发送的数据 data用于post、put、patch请求
                        data: {p1: "A", p2: "B"},//默认情况下,axios-post请求请求参数data对象序列化为JSON
                        responseType: "json", //服务器响应数据类型
                        responseEncoding: "utf-8" //服务器响应数据编码
                    }).then((response) => {
                        this.students = response.data.students;
                    }).catch((e) => {
                        console.log(e);
                    });
                },
                sendAxiosPost2: function () {
                    // String类型
                    let requestData = Qs.stringify({p1: "A", p2: "B"});
                    console.log(requestData);
                    //typeof返回对应数据
                    console.log(typeof (requestData));
                    //FormData对象 上传文件和提交数据
                    let formData = new FormData();
                    formData.append("pageNo", "1");
                    formData.append("pageSize", "10");
                    console.log(formData.get("pageSize"));

                    axios({
                        url: "http://192.168.80.254:8080/student/list",//请求服务器的url
                        method: "post",//请求方式
                        //headers 设置请求头类型
                        //Content-Type的默认类型是application/json
                        //告诉服务端参数提交是form-data的格式 (id=1&name=zhangsan)
                        headers: {"Content-Type": "application/x-www-form-urlencoded"},
                        // `data` 是作为请求主体被发送的数据 data用于post、put、patch请求
                        // 要以applicaton/x-www-form-urlencoded格式发送数据
                        //Qs.stringify方法把json对象序列化成url形式,以&拼接 (p1=A&p2=B)的form-data的格式
                        data: Qs.stringify({p1: "A", p2: "B"}),
                        responseType: "json", //服务器响应数据类型
                        responseEncoding: "utf-8" //服务器响应数据编码
                    }).then((response) => {
                        this.students = response.data.students;
                    }).catch((e) => {
                        console.log(e);
                        alert(e);
                    });
                },
                //axios.post(url,data,{}).then().catch()  data表示数据
                sendAxiosAjaxPost3: function () {
                    //Qs.stringify() 把json对象序列化url格式,用&连接  p1=A&p2=B
                    let paramData = Qs.stringify({
                        p1: "A",
                        p2: "B"
                    });
                    axios.post("http://192.168.80.254:8080/student/list", paramData, {
                        // post默认是 参数类型json
                        // 服务器要支持json数据格式,希望请求参数 form类型  application/x-www-form-urlecoded
                        //设置请求头 请求的数据类型为 application/x-www-form-urlecoded
                        headers: {
                            "Content-Type": "application/x-www-form-urlencoded"
                        },
                        responseType: "json", //服务端响应的数据类型
                        responseEncoding: "UTF-8" //服务端响应数据的编码
                    }).then(response => {
                        console.log(response);
                        this.students = response.data.students;
                    }).catch(e => {
                        console.log(e);
                        alert(e);
                    });
                }
            },
        });
    </script>
</html>

后端代码:

@WebServlet(urlPatterns = {"/student/list"})
public class StudentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p1 = req.getParameter("p1");
        String p2 = req.getParameter("p2");
        System.out.println("p1=" + p1 + ",p2=" + p2);
        //设置响应数据格式和编码
        resp.setContentType("application/json;charset=utf-8");
        //允许跨域请求(后台允许跨域请求)
        resp.setHeader("Access-Control-Allow-Origin", "*");
        //获取数据
        List<Student> students = getStudents();
        Result result = new Result(students);
        //使用Gson把result对象转换json字符串
        Gson gson = new Gson();
        String json = gson.toJson(result);
        //输出json字符串到前端
        resp.getWriter().write(json);
    }

    /**
     * 获取学生数据集合
     *
     * @return
     */
    private List<Student> getStudents() {
        List<Student> students = new ArrayList<>();
        students.add(new Student(1L,"李四",20,"mm@163.com"));
        students.add(new Student(2L,"王五",21,"mm@163.com"));
        students.add(new Student(3L,"赵六",22,"mm@163.com"));
        students.add(new Student(4L,"钱七",23,"mm@163.com"));
        students.add(new Student(5L,"贵八",24,"mm@163.com"));
        return students;
    }
}
public class Result {
    List<Student> students;

    public Result() {
    }

    public Result(List<Student> students) {
        this.students = students;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }
}
public class Student {
    private Long id;
    private String name;
    private Integer age;
    private String email;

    public Student() {
    }

    public Student(Long id, String name, Integer age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

15.export和import

export和import在es5不支持,所以直接下面的代码直接在浏览器不能运行

15.1 export

模块是独立的文件,该文件内部的所有的变量外部都无法获取。如果希望获取某个变量,必须通过export输出

// profile.js
export let firstName = 'Michael';
export let lastName = 'Jackson';
export let year = 1958;

或者用更好的方式:用大括号指定要输出的一组变量

// profile.js
let firstName = 'Michael';
let lastName = 'Jackson';
let year = 1958;
​
export {firstName, lastName, year};

除了输出变量,还可以输出函数或者类(class),

export function multiply(x, y) {
  return x * y;
};

还可以批量输出,同样是要包含在大括号里,也可以用as重命名:

function v1() { ... }
function v2() { ... }
​
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

export 命令规定的是对外接口,必须与模块内部变量建立一一对应的关系

main.js import profile.js文件

//profile.js
// 写法一
export let m = 1;
​
// 写法二
let m = 1;
export {m};
​
// 写法三
let n = 1;
export {n as m};
​
​
// 报错
export 1;
​
// 报错
let m = 1;
export m;

15.2 import

export定义了模块的对外接口后,其他JS文件就可以通过import来加载这个模块,

import {firstName, lastName, year} from './profile';  //使用相对路径或者绝对路径function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

import命令接受一对大括号,里面指定要从其他模块导入的变量名,必须与被导入模块(profile.js)对外接口的名称相同

如果想重新给导入的变量一个名字,可以用as关键字,

import { lastName as surname } from './profile'

import后的from 可以指定需要导入模块的路径名,可以是绝对路径,也可以是相对路径, .js路径可以省略,如果只有模块名,不带有路径,需要有配置文件指定。

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。(是在编译阶段执行的)

module的整体加载

除了指定加载某个输出值,还可以用(*)指定一个对象,所有的变量都会加载在这个对象上。

// circle.js。输出两个函数export function area(radius) {
  return Math.PI * radius * radius;
}
​
export function circumference(radius) {
  return 2 * Math.PI * radius;
}
​
​
// main.js 加载在个模块import { area, circumference } from './circle';
​
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
​
//上面写法是逐一指定要加载的方法,整体加载的写法如下。
import * as circle from './circle';
​
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变。

import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};

export default

之前的例子中,使用import导入时,都需要知道模块中所要加载的变量名或函数名,用户可能不想阅读源码,只想直接使用接口,就可以用export default命令,为模块指定输出

// export-default.js
export default function () {
  console.log('foo');
}

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

// import-default.js
import customName from './export-default';
customName(); // 'foo'

export default也可以用于非匿名函数前。

下面比较一下默认输出和正常输出。

// 第一组
export default function crc32() { // 输出
  // ...
}

import crc32 from 'crc32'; // 输入


// 第二组
export function crc32() { // 输出
  // ...
};

import {crc32} from 'crc32'; // 输入

在一个文件或模块中,export、import可以有多个,export default仅有一个,通过export方式导出,在导入时要加{ },export default则不需要

16.掌握cli方式搭建项目、安装依赖

16.1 vue-cli 是vue.js的脚手架,用于自动生成vue.js模板工程的。

  • 脚手架工具简单讲就是自动将项目需要的环境、依赖等信息都配置好

16.2 安装node.js

使用安装包,安装成功后 cmd 运行 npm -v 命令查看版本

16.3 设置node.js prefix(全局)和cache(缓存)路径(根据自己的电脑磁盘空间大小选择盘符,路径不能用中文或者特殊字符)

设置全局模块存放路径(路径自定义,保证磁盘有足够的空间)

npm config set prefix "C:\tools\nodejs\node_global"

设置缓存文件夹(路径自定义,保证磁盘有足够的空间)

npm config set cache "C:\tools\nodejs\node_cache"

设置成功后,之后用命令npm install XXX -g安装以后模块就在C:\develop_tool\nodejs\node_cache里

16.4 设置环境变量(非常重要)

说明:设置环境变量可以使得住任意目录下都可以使用cnpm、vue等命令,而不需要输入全路径。

编辑系统变量PATH中添加

C:\tools\nodejs\node_global

根据步骤3设置的路径进行配置

添加系统变量NODE_PATH,输入路径

C:\tools\nodejs\node_global\node_modules

根据步骤3设置的路径进行配置

16.5、基于 Node.js 安装cnpm(淘宝镜像)

npm作为国外的node仓库安装工具,国内用户在安装相关的资源的时候,会出现安装失败,以及速度很慢的情况。为了解决npm安装的问题,国内出现了很多npm的镜像网址,taobao的npm镜像算是使用频率比较高的了。

(1)使用阿里定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:

npm install -g cnpm --registry=https://registry.npm.taobao.org

(2)检测cnpm版本,如果安装成功可以看到cnpm的基本信息。

cnpm -v

16.6 安装vue命令行工具,即vue-cli 脚手架

cnpm install vue-cli -g

检查vue-cli脚手架版本,如果安装成功可以看到vue-cli脚手架的版本信息

vue -V或者vue --version

16.7 使用vue-cli来构建vue项目

cmd 打开命令窗口 路径切换到 vue项目的文件夹

运行如下命令,安装@vue/cli-init

为了防止卡在downloading template 

npm install -g @vue/cli-init

使用命令创建vue模板项目

vue init webpack-simple 项目名
vue init webpack 项目名

webpack-simple和webpack 是项目模板,分别为简单模式和完全模式。

简单模式仅包含基于vue.js开发必要的一些资源和包,使用该模式可以快速地投入到开发。

完全模式则还包括ESLink、单元测试等功能。

16.8 目录切换到项目目录下

1、直接用命令提示符输入命令

如:cd C:\Users\hzw\Documents\WebstormProjects\my-vue-cli

执行 npm run dev

出现如下就说明启动成功了

2、用WebStorm输入命令

出现如下就说明启动成功了

项目运行成功后,使用浏览器打开对应页面如下:

http://localhost:8080/

注:这里是默认服务启动的是本地的8080端口,所以请确保你的8080端口不被别的程序所占用。

若8080被占用,需要修改一下当前vue项目的配置文件 config里的index.js的 port

16.9 项目目录解析

maven            编译打包程序     +   jar依赖

npm               js库维护

webpack         编译(把es6的语法 转换成es5,让浏览器都支持)打包

16.10 加载过程

16.11 加载过程

App.vue中声明了路由插座,插座用来根据路由配置与浏览器地址选择加载的视图。

路由文件声明了当路径为空时,以HellowWorld组件作为元素。

16.12 vue文件结构

  • vue文件是vue工程化开发时的组件的编程载体。
  • vue文件并不能独立运行,而是要通过webpack打包并封装为一个vue组件对象。

17.npm、webpack和node关系

17.1 什么是webpack?(了解)

  • Webpack 是一个前端资源加载/打包工具。
    • 将根据模块的依赖关系进行静态分析
    • 然后将这些模块按照指定的规则生成对应的静态资源。
  • WebPack可以看做是模块打包机:
    • 它做的事情是,分析你的项目结构,
    • 找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等)
    • 并将其转换和打包为合适的格式供浏览器使用。

17.2 webpack的核心作用

  • 模块化开发中,我们会编写大量模块,如果不打包就上线,那么页面加载或交互时,将会发起大量请求。
  • 为了性能优化,需要使用webpack这样的打包器对模块进行打包整合,以减少请求数。
  • 就像简单的vue项目,所有组件最终都将被打包到一个app.js中。
  • 相较于无差别打包依赖模块的传统打包器,webpack的核心优势在于它从入口文件出发,递归构建依赖关系图。
  • 通过这样的依赖梳理,webpack打包出的bundle不会包含重复或未使用的模块,实现了按需打包,极大的减少了冗余。

如图:

webpack是一个工具,这个工具可以帮你处理好各个包/模块之间的依赖关系(modules with dependencies),并将这些复杂依赖关系的静态文件打包成一个或很少的静态文件,提供给浏览器访问使用;除此之外,webpack因为可以提高兼容性,可以将一些浏览器尚不支持的新特性转换为可以支持格式,进而减少由新特性带来的浏览器的兼容性问题

17.3 什么是npm

官网:https://www.npmjs.com/

介绍了webpack,我们可能会疑问,我的JS,CSS,HTML文件分开写,挺好的呀,为什么要使用webpack工具,进行复杂的各项配置。在传统前端开发模式下,我们确实是按照JS/CSS/HTML文件分开写的模式就可以,但是随着前端的发展,社区的壮大,各种前端的库和框架层出不穷,我们项目中可能会使用很多额外的库,如何有效管理这些引入的库文件是一个大问题,而且我们知道基于在HTML中使用<script>引入的方式,有两个弊端,一个是会重复引入,二是当库文件数量很多时管理成为一个大难题 面对这样的局面,为了简化开发的复杂度,前端社区涌现了很多实践方法。模块化就是其中一项成功实践,而npm就是这样在社区(node)中产生的

npm 由三个独立的部分组成:

  • 网站

  • 注册表(registry)

  • 命令行工具 (CLI)

网站 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。 注册表 是一个巨大的数据库,保存了每个包(package)的信息。 CLI 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。

一般来说提起npm有两个含义,一个是说npm官方网站,一个就是说npm包管理工具。npm社区或官网是一个巨大的Node生态系统,社区成员可以随意发布和安装npm生态中的包,也就是不用在重复造轮子了,别人造好了,你直接安装到你的项目中就可以使用,但是因为前面说了,当包引入数量很多时管理就成为了一个问题,这个就是npm为开发者行了方便之处,npm已经为你做好了依赖和版本的控制,也就是说使用npm可以让你从繁杂的依赖安装和版本冲突中解脱出来,进而关注你的业务而不是库的管理。

webpack就是将你从npm中安装的包打包成更小的浏览器可读的静态资源,这里需要注意的是,webpack只是一个前端的打包工具,打包的是静态资源,和后台没有关系,虽然webpack依赖于node环境

17.4 什么是node?

实node和nodejs两个概念没有太大差别,唯一的区别就是,人们说起node的时候语境更多的是再说node环境,而说nodejs时更多的是在说node是一门可以提供后端能力的技术。本质上来说,node就是nodejs,nodejs就是node,简单的说 Node.js 就是运行在服务端的 JavaScript。

17.5 webpack npm node之间关系?

  • webpack是npm生态中的一个模块,我们可以通过全局安装webpack来使用webpack对项目进行打包;

  • webpack的运行依赖于node的环境,没有node是不能打包的,但是webpack打包后的项目本身只是前端静态资源和后台没有关系,也就是说不依赖与于node,只要有后台能力的都可以部署项目

  • npm是于Node社区中产生的,是nodejs的官方包管理工具,当你下载安装好node的时候,npm cli 就自动安装好了

  • 正是因为npm的包管理,使得项目可以模块化的开发,而模块化的开发带来的这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就是webpack工具存在的意义

18.使用ElementUI

18.1 功能简介

ElementUl:一套为开发者、设计师和产品经理准备的基于Vue 2.0的桌面端组件库。

  • 提供了组件化布局的基础功能。
  • 提供了常用的网页元素默认样式。
  • 提供了国际化等功能。
  • 对表单做了扩展功能,增加了时间、日期滑块以及手机端常用的输入类型。
  • 官网:https://element.eleme.cn/#/zh-CN/component/installation

18.2 功能说明

  • Basic基础组件: Layout布局、lcon图标、Button按钮
  • Form表单组件:Radio单选框、Input输入框、Form表单
  • Data数据组件:Table表格、Progress进度条、Pagination分页
  • Notice提示组件: Alert警告、Message消息提示、Notification通知
  • Navigation导航组件:NavMenu导航菜单、Tabs标签页、Dropdown下拉菜单
  • Others其它组件:Carousel走马灯、Calendar日历、Drawer抽屉

18.3 安装

1、cmd 切换到项目目录,执行命令

npm i element-ui -S

2、在main.js文件中加入代码

//导入ui组件js文件
import ElementUI from 'element-ui'
//导入ui组件的css文件
import 'element-ui/lib/theme-chalk/index.css

//声明插件安装
Vue.use(ElementUI)

18.4 使用

在src文件夹中的component文件夹下创建.vue结尾的文件,组件开发,在router文件夹index.js中配置路由

18.5 常用组件标签演示

Button 按钮

Icon图标

18.6 表单组件

rules属性配置验证规则

  •  rules对象采用键值对 {prop: [{}]} 格式配置,属性值:
    • type 类型验证:string/number/boolean/array/object/date/email/any等
    • required 必填属性:true/false
    • pattern 模板类型:正则表达式
    • min 最少位数:number
    • max 最多位数:number
    • message 检测提示:string
    • validator 验证器:function(rule, value, callback)
    • trigger 验证触发事件:focus/blur/change

Vue的ref属性和refs

  • ref 被用来给DOM元素或子组件注册引用信息。
  • 引用信息会根据父组件的 $refs 对象进行注册。
    • 如果在普通的DOM元素上使用,引用信息就是元素;
    • 如果用在子组件上,引用信息就是组件实例

注意:只要想要在Vue中直接操作DOM元素,就必须用ref属性进行注册

<!-- 这是.vue文件,定义和编辑组件,使用的vue-cli脚手架创建的项目 -->
<template>
  <div>
    <!--ref 的作用:通过Vue对象中 this.$refs 获取DOM元素-->
    <input type="text" ref="input1" id="input1">
    <input type="button" :value="btnName" @click="add"/>
  </div>
</template>
<script>
  export default {
    name: "ElementInput",
    data() {
      return {
        btnName: "添加"
      }
    },
    methods: {
      add() {//this.$refs 获取input type="text" 标签的DOM元素
        //1、this.$refs.ref对应的名称
        console.log(this.$refs.input1);
        //2、this.$refs[ref对应的名称]
        console.log(this.$refs['input1']);
        //3、getElementById
        console.log(document.getElementById("input1"));
      }
    }
  }
</script>
<style scoped>
</style>

18.7 ElementUI登录小案例


<template>
	<div>
		<el-form :model="ruleForm" :rules="rules" ref="ruleForm1" label-width="100px">
			<el-form-item label="用户名" prop="name">
				<el-input v-model="ruleForm.name"></el-input>
			</el-form-item>
			<el-form-item label="密码" prop="password">
				<el-input v-model="ruleForm.password" type="password"></el-input>
			</el-form-item>
			<el-form-item label="记住密码" prop="remember">
				<el-switch v-model="ruleForm.remember"></el-switch>
			</el-form-item>
			<el-form-item>
				<el-button type="primary" @click="submitForm('ruleForm1')">登录</el-button>
				<el-button @click="resetForm('ruleForm1')">重置</el-button>
			</el-form-item>
		</el-form>
	</div>
</template>

<script>
	export default {
		name: "Login",
		data() {
			return {
				ruleForm: {
					name: '',
					password: '',
					remember: true
				},
				rules: {
					name: [{
							required: true,
							message: '请输入用户名',
							trigger: 'blur'
						},
						{
							min: 3,
							max: 5,
							message: '长度在 3 到 5 个字符',
							trigger: 'blur'
						}
					],
					password: [{
						required: true,
						message: '请输入密码',
						trigger: 'blur'
					}, {
						min: 5,
						max: 10,
						message: '长度在 5 到 10 个字符',
						trigger: 'blur'
					}]
				}
			}
		},
		methods: {
			submitForm(formName) {
				this.$refs[formName].validate((valid) => {
					if (valid) {
						alert('submit!');
						// 发送axios请求,进行登录操作
						this.$axios({
							method: "post",
							url: "http://192.168.140.254:8080/login",
							data: this.$qs.stringify({
								op: "login",
								userName: this.ruleForm.name,
								password: this.ruleForm.password
							}),
							headers: {
								"Content-Type": "application/x-www-form-urlencoded"
							}
						}).then(response => {
							console.log(response);
							// 判断是否登录成功
							// 如果登录成功 并 选择记住密码 this.ruleForm.remember
							if (this.ruleForm.remember == true) {
								//记住密码
							}
							// 如果不成功,提示用户名或密码错误

						}).catch(error => {
							alert("axios异步请求失败");
						});
					} else {
						alert('error submit!');
						return false;
					}
				});
			},
			resetForm(formName) {
				console.log(this.$refs[formName]);
				this.$refs[formName].resetFields();
			}
		}
	}
</script>

<style>
</style>

19.vuex状态管理

19.1 功能简介

1、当写vue项目时,当涉及到频繁的组件之间的数据通讯、一个组件需要多次派发事件时,我们的代码就会变得复杂、冗余、难以维护

2、Vuex是一个专为Vue.js应用程序开发的状态管理模式。我们可以把一些共享的数据保存至vuex中,方便各个组件修改或获取公共状态。

3、vue的单向数据流的简洁性在以下情况下很容易被破坏

  • 多个视图依赖于同一状态

  • 来自不同视图的行为需要改变同一状态

    解决思想:

    我们可以把组件的共享状态提取出来,作为全局单例模式管理。这样,组件树构成了一个巨大的视图,不论在树的哪个位置,都能获取状态或触发行为。

4、vuex的示意图

19.2 下载以及安装

1、vuex的安装方式

  • 步骤1:在package.json文件中的dependencies属性中增加vue的依赖包声明

    "dependencies": {
            "vuex": "^3.0.1",
            "vuex-persistedstate": "^2.5.4"
        }
  • 步骤2:使用cmd指令进入package.json所在目录执行安装指令,安装后在node_modules目录下生成vuex、vuex-persistedstate包

    npm install

     

  • 步骤3:在独立的store配置文件中引入vuex组件(如:store.js)

    import Vue from'vue'
    import Vuex from 'vuex' 
    import createPersistedState from "vuex-persistedstate"
    //使用vuex插件
    Vue.use(Vuex)
  • 步骤4:创建vue全局实例对象,并设定持久化插件,写在store.js中

    export default new Vuex.Store({
        //全局状态
        state:{loginStatus:false},
        //同步数据提交
        mutations:{},
        //引入持久化组件
        plugins:[createPersistedState()]
    })
  • 步骤5:在main.js中引入vuex实例,并以插件的方式添加到组件实例对象中

    import store from './store‘
    ​
    new Vue({
      el: '#app',
      router,
      store,
      components: { App },
      template: '<App/>'
    })

vue的所有组件可以通过this.$store.state访问全局状态

<template>
    <div style="border: 1px #0000FF solid;">
        <div>从state上获取的count:{{count}}</div>
    </div>
</template><script>
    export default {
        name: "VuexTest",
        //计算属性获取
        computed: {
            count() {
                //this.$store.state 找到存储数据store的对象count属性
                return this.$store.state.count;
            }
        }
    }
</script><style>
</style>

store.js

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)
​
export default new Vuex.Store({
    state: { //要设置的全局访问的state对象
        count: 0
    }
})

19.3 state核心组件

1、vuex使用单一状态树state对象来作为“唯一数据源”,专门用于存放数据。

  • 在组件中访问state下的数据,通过this.$store.state来访问

  • 不能通过this.$store.state进行赋值,会造成显示与存储不统一

  • 一般建议当需要访问state的属性时,通过计算属性访问,避免对数据赋值

 

2、getters属性:当需要访问的数据需要通过对state中的数据需要进行运算或逻辑处理才能返回结果时,可以使用getters。

  • getters可以简单的把它理解为store中的计算属性,当store里的值发生改变时,getters会返回重新计算的值。

  • getters下的方法第一个参数为state,第二个参数为getters,意味着在getters方法中可以访问state以及其他getters的函数返回值。

<template>
    <div style="border: 1px #0000FF solid;">
        <div>从state上获取的count:{{count}}</div>
        <div>从getters上获取的getCount:{{this.$store.getters.getCount}}</div>
    </div>
</template><script>
    export default {
        name: "VuexTest",
        //计算属性获取
        computed: {
            count() {
                //this.$store.state 找到存储数据store的对象count属性
                return this.$store.state.count;
            }
        }
    }
</script><style>
</style>

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
	state: { //要设置的全局访问的state对象
		count: 0
	},
	//通过复杂的计算得到数据可以使用getters
	getters: {
		//store里的值发生改变时,getter会返回重新计算的值
		getCount(state, getters) {
			return state.count;
		}
	}
})

19.4 Mutations组件

1、mutations组件:通过mutation更新state中数据,不可以直接操作store中的数据,只有正确提交mutations,才能保证state中的数据在个组件中渲染与存储一致。

2、mutations组件的提交:

this.$store.commit(“login”,playLoad)
  • login为事件类型,与mutations中声明保持一致

  • playLoad:为对象或基本类型的数据,提交荷载

注意事项:

Vuex的store中的状态是响应式的,当我们变更状态时,监视状态的Vue组件也会自动更新。这也意味着Vuex中的mutation也需要与使用Vue一样遵守一些注意事项:

  • 在store中初始化好所有所需属性

  • 当需要在对象上添加新属性时,应该使用Vue.set(obj,'newProp',123),或者以新对象替换老对象

    state.obj={...state.obj,newProp:123}

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
	state: { //要设置的全局访问的state对象
		count: 0
	}
	//通过复杂的计算得到数据可以使用getters
	getters: {
		//store里的值发生改变时,getter会返回重新计算的值
		getCount(state, getters) {
			return state.count;
		}
	},
	//通过mutations改变store存储的数据
	mutations: {
		//state存储数据对象,num为相加或者相减的数据
		addCount(state, num) {
			state.count = state.count + num;
		},
		subCount(state, num) {
			//count为0不能再做减法
			if (state.count > 0) {
				state.count = state.count - num;
			} else {
				state.count = 0;
			}
		}
	}
})

VueTest.vue

<template>
	<div style="border: 1px #0000FF solid;">
		<input type="button" value="-" @click="sub" />
		<input type="button" value="+" @click="add" />
		<div>从state上获取的count:{{count}}</div>
		<div>从getters上获取的getCount:{{this.$store.getters.getCount}}</div>
        <br />
		<router-link to="/v-test1">VuexTest1</router-link>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name: "VuexTest",
		//计算属性获取
		computed: {
			count() {
				//this.$store.state 找到存储数据store的对象count属性
				return this.$store.state.count;
			}
		},
		methods: {
			add() {
				//this.$store.commit() 提交修改,
				//第一个参数为store.js的mutations定义的事件类型,第二个参数为数量
				this.$store.commit("addCount", 1);
			},
			sub() {
				this.$store.commit("subCount", 1);
			}
		}
	}
</script>

<style>
</style>

VueTest1.vue

<template>
	<div style="border: 1px red solid;">
		<div>通过this.$store.state.count获取值{{count}}</div>
		<div>通过getters方法获取状态数据{{this.$store.getters.getCount}}</div>
	</div>
</template>

<script>
	export default {
		name: "VuexTest1",
		computed: {
			count() {
				return this.$store.state.count;
			}
		}
	}
</script>

<style>
</style>

19.5 Actions组件

1、Actions组件:Mutations组件的异步数据更新

  • actions组件与Mutations组件的作用都是对state数据属性赋值,并同步数据显示渲染。

  • Mutations组件仅适用于同步数据赋值,而actions适用于异步数据赋值

  • Actions组件最终通过提交Mutations中的事件类型对state属性赋值

2、Actions组件的声明

         

  • context为当前vuex实例对象,与this.$store一致,可以调用commit方法

  • playload为荷载

  • 通过调用dispath触发action组件事件类型

    store.dispatch(addCountAsync',playLoad)

19.6 模块化管理

由于vuex使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿

  • 解决方案:将store分模块管理,每个模块都有自己内部的state、mutation、action、getter

19.7 持久存储

问题:在登录成功后,把用户信息保存在vuex中作为全局共享数据,在页面刷新之后数据会丢失

  • 分析:由于vuex中的数据保存在运行内存中,当页面刷新时,页面会重载vue实例,vuex中的数据会被重新赋值

  • 解决思路:把vuex中的state数据保存一份到客户端存储中

    • localStorage:永久本地存储。本案例采用vuex-persistedstate中间件处理,简化api调用

    • sessionStorage:存储至页面关闭

    • cookie:手动设置有效时间,但存储量过小

19.8 案例

VueTest.vue

<template>
	<div style="border: 1px #0000FF solid;">
		<input type="button" value="-" @click="sub" />
		<input type="button" value="+" @click="add" />
		<div>从state上获取的count:{{count}}</div>
		<div>从getters上获取的getCount:{{this.$store.getters.getCount}}</div>
        <br />
		<router-link to="/v-test1">VuexTest1</router-link>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name: "VuexTest",
		//计算属性获取
		computed: {
			count() {
				//this.$store.state 找到存储数据store的对象count属性
				return this.$store.state.count;
			}
		},
		methods: {
			add() {
				//this.$store.commit() 提交修改,
				//第一个参数为store.js的mutations定义的事件类型,第二个参数为数量
				//this.$store.commit("addCount", 1);
				//通过this.$store.dispatch 触发 actions定义的事件类型
				this.$store.dispatch("myAddCount",1);
			},
			sub() {
				//this.$store.commit("subCount", 1);
				this.$store.dispatch("mySubCount",1);
			}
		}
	}
</script>

<style>
</style>

VueTest1.vue

<template>
	<div style="border: 1px red solid;">
		<div>通过this.$store.state.count获取值{{count}}</div>
		<div>通过getters方法获取状态数据{{this.$store.getters.getCount}}</div>
		<br/>
		<router-link to="/v-test2">VuexTest2</router-link>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name: "VuexTest1",
		computed: {
			count() {
				return this.$store.state.count;
			}
		}
	}
</script>

<style>
</style>

VueTest2.vue

<template>
	<div style="border: 1px green solid;">
		<div>通过this.$store.state.count获取值{{count}}</div>
		<div>通过getters方法获取状态数据{{this.$store.getters.getCount}}</div>
	</div>
</template>

<script>
	export default {
		name: "VuexTest2",
		computed: {
			count() {
				return this.$store.state.count;
			}
		}
	}
</script>

<style>
</style>

路由配置

{
    path: "/v-test",
    name: "VuexTest",
    component: VuexTest,
    children:[
        {
            path: "/v-test1",
            name: "VuexTest1",
            component: VuexTest1,
            children:[
                {
                    path: "/v-test2",
                    name: "VuexTest2",
                    component: VuexTest2
                }
            ]
        }
    ]
}

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from "vuex-persistedstate"

Vue.use(Vuex)

export default new Vuex.Store({
	state: { //要设置的全局访问的state对象
		count: 0
	},
	plugins: [
		//持久化state
		createPersistedState({
			//指定存储位置(默认位置是local storage)
			//sessionStorage表示会话存储,当浏览器关闭后,存储就消失了
			//localStorage 表示本地存储,当浏览器关闭后 ,存储不消失,再次打开网站 数据恢复
			//storage:window.sessionStorage,
			//reducer: 指定需要缓存的状态属性
			reducer(val) {
				console.log(val);
				return {
					count: val.count
				}
			}
		})
	],
	//通过复杂的计算得到数据可以使用getters
	getters: {
		//store里的值发生改变时,getter会返回重新计算的值
		getCount(state, getters) {
			return state.count;
		}
	},
	//通过mutations改变store存储的数据
	mutations: {
		//state存储数据对象,num为相加或者相减的数据
		addCount(state, num) {
			state.count = state.count + num;
		},
		subCount(state, num) {
			//count为0不能再做减法
			if (state.count > 0) {
				state.count = state.count - num;
			} else {
				state.count = 0;
			}
		}
	},
	actions: {
		myAddCount(context, num) {
			//context为当前vuex实例对象,与this.store一致,可以调用commit方法
			//第一个参数addCount为mutations定义的方法
			//context.commit("addCount", num);
            setTimeout(()=>{
                context.commit("addCount", num);
            },2000);
		},
		mySubCount(context, num) {
			//context为当前vuex实例对象,与this.store一致,可以调用commit方法
			//第一个参数subCount为mutations定义的方法
			context.commit("subCount", num);
		}
	}
})

 

posted @   carat9588  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示