Vue学习笔记

介绍

vue.js

一个渐进式(全家桶)的Javascript框架,采用数据驱动视图的思想,即以数据为核心,优先操作数据而非视图

涉及工具
  1. Vue全家桶
  1. vue:适用于小型项目
  2. vue-router:适用于当项目中存在很多页面时
  3. vuex + axios:适用于当项目中数据也很多时
  1. webpack

一个前端模块化打包构建工具

MVC

一种软件架构模式

  • M:Model,指数据层
  • V: View,指视图层
  • C:Controller,逻辑控制层
MVVM
  • 介绍

MVVM是Vue采用的设计模式,实质就是对MVC中的C层进行了封装,使得M和V能够直接交互,得以实现的数据的双向绑定

  • 使用这种设计模式的初衷

对于DOM,它是前端性能的瓶颈,即想要提高性能,就要尽可能地降低对DOM的操作,而DOM的操作是在Controller中进行的,Vue相当于将Controller进行了高性能的封装,用户可以无需考虑Controller层,只有V层和M层的直接对话,感受到一种数据双向绑定的体验,即V层中的数据改变,M层会自动同步改变,M层中数据改变,V层中也会自动同步改变

Vue的基本使用

代码示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!--
    插值表达式:即 {{}},mustache,也称小胡子语法
    作用:可以直接读取data中的数据,常用于设置标签中的内容
    注意:{{}} 当中只能放表达式,不能放入if、for等语句,且不能用在属性位置
    -->
    <h1>{{ msg }}</h1>
</div>
<!-- 1. 安装:npm i vue -->
<!-- 2. 引入 -->
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    /**
     * 3. 创建vue实例并进行数据绑定
     * el:[View] 值为一个选择器(推荐id选择器),表示指定vue管理的边界
     * data:[Model]数据,
     * vm:vue实例,注意一个边界只能对应一个实例
     */
    const vm = new Vue({
        el: '#app',
        data: {
            msg: 'hello, vue ~'
        }
    });

    /**
     * 可以使用vm直接获取data中的属性
     * 原因是vue会对data中的属性进行遍历,并设置为vm属性
     */
    console.log(vm.msg)
</script>
</body>
</html>
数据和视图的双向绑定
  • 示例

v-model:将input的value和data中的数据绑定,二者将同步变化
现象:当修改data.msg时,input的value同步修改;当修改input内容时,data.msg也会同步改动

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="msg" />
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            msg: 'hello'
        }
    });
    setTimeout(function() {
        vm.msg = 'data change';
    }, 3000);
    setInterval(function() {
        console.log(vm.msg)
    }, 1000);
</script>
</body>
</html>
  • 数据劫持与Object.defineProperty()
  • 说明

Vue的数据双向绑定是通过Object.defineProperty()来实现的,而defineProperty操作也被称为数据劫持,它是在ES5中提供的一个无法shim(兼容)的特性,这也是Vue无法支持IE8及以下版本的原因

  • defineProperty的基本使用
/**
 * Object.defineProperties(要拦截的对象, 要拦截的属性名, 属性的set和get方法);
 */

let obj = {};
let temp;

Object.defineProperty(obj, 'name', {
    set(value) {
        console.log("调用了set方法");
        console.log("要设置的值:", value);
        temp = value;
    },
    get() {
        console.log("调用了get方法");
        return temp;
    }
});

obj.name = 'xiao ming'; //会触发set方法
console.log(obj.name); //会触发get方法

  • defineProperty实现数据双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" id="content">
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    let obj = {};
    let temp;
    
    /* View -> Model */
    let input = document.querySelector("#content");
    input.onclick = function(){
        obj.name = this.value;
    };
    
    /* Model -> View */
    Object.defineProperty(obj, 'name', {
        set(value) {
           temp = value;
            input.value = value;
        },
        get() {
            return temp;
        }
    })
</script>
</body>
</html>

指令

简介

Directivies,vue中的指令是带有v-前缀的特殊属性,可以直接在html标签中使用,为对应的标签提供一些特殊的功能,常用的指令有:v-model, v-bind, v-if, v-for

v-model:数据双向绑定

代码示例见 Vue基本使用代码示例 ,该指令一般用在表单元素(如单选、多选、文本输入、下拉框等)中,要注意的是,绑定不同表单元素时,绑定的值也会不同

  1. 绑定多选框:绑定的是布尔类型的值
  2. 绑定文本框:绑定的是字符串类型的值
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="msg" />
    <input type="checkbox" v-model="isChecked">
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            msg: 'hello',
            isChecked: true
        }
    });
</script>
</body>
</html>
v-html & v-text:文本内容绑定

v-text:效果同 {{}},相当于 innerText
v-html:相当于innerHTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <div>{{msg}}</div>
    <div v-text="msgText"></div>
    <div v-html="msgHtml"></div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: "msg",
            msgText: "text",
            msgHtml: "<span>html</span>"
        }
    })
</script>
</body>
</html>
v-bind:数据动态绑定
  • 示例

通常用在属性上

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--绑定title属性值-->
<div v-bind:title="msg" id="app">this is a div</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            msg: "title"
        }
    });
</script>
</body>
</html>
  • 简写

由于使用频繁,所以可以省略成一个:

<div :title="msg"> this is a div </div>
  • 用在样式上
  • :class='bar'

表示vue中存在bar属性,class为该属性的值,值对应为style标签中的一个类名

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .red {
            color: red;
            background-color: #f99;
        }
    </style>
</head>
<body>
<div id="app">
    <div :class='r'>this is a div</div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            r: 'red'
        }
    })
</script>
</body>
</html>
  • :class='{clsname: isactive}'

classname对应为style标签中的一个类名,isactive表示是否生效,是个布尔值,需要在vue中声明

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .red {
            color: red;
            background-color: #f99;
        }
    </style>
</head>
<body>
<div id="app">
    <div :class='{red: isRed}'>this is a div</div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            isRed: true
        }
    })
</script>
</body>
</html>
  • :class='[a, b]'

注意,数组中a和b没有引号,此时表示的是data中的属性值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .red-color {
            color: red;
        }
        .pink-bgc {
            background-color: #f99;
        }
    </style>
</head>
<body>
<div id="app">
    <div :class='[a, b]'>this is a div</div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            a: 'red-color',
            b: 'pink-bgc'
        }
    })
</script>
</body>
</html>
  • :class='["a", "b"]'

注意,数组中a和b有引号,此时表示的是style标签中的类名

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .red-color {
            color: red;
        }
        .pink-bgc {
            background-color: #f99;
        }
    </style>
</head>
<body>
<div id="app">
    <div :class='["red-color", "pink-bgc"]'>this is a div</div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
    })
</script>
</body>
</html>
  • :style='{attr1: value1, attr2:value2}'

表示进行行内样式设置,值是个对象,对象中的属性对应行内样式style中可以设置的样式属性,属性值则对应vue中的属性值,多个样式之间用逗号相隔

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <div :style='{color: r, backgroundColor: p}'>this is a div</div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            r: 'red',
            p: 'pink'
        }
    })
</script>
</body>
</html>
v-on:事件绑定
  • 示例
v-on:事件名='函数名
事件名:如click,为点击事件的事件名
函数名:对应vue的methods中的一个方法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button v-on:click="click1">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        methods: { //绑定事件的事件函数放在methods属性中
            click1() {
                alert(1);
            }
        }
    })
</script>
</body>
</html>
  • 简写
@click='click1'
  • 事件中的this:vm实例
const vm = new Vue({
	el: "#app",
	data: {
		msg: "message"
	}
    methods: {
        click1(){
            alert(1)
        },
        click2(){
            alert(2)
            console.log(this === vm); //true
            this.click1(); //1
            console.log(this.msg); //message
        }
    }
});
  • 传参
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="click('xiao ming')">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        methods: {
            click(name) {
                alert('hello ' + name);
            }
        }
    });
</script>
</body>
</html>
  • 事件对象

不传参时,为参数 e(参数名可自定义)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="click">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        methods: {
            click(e) {
                console.dir(e);
            }
        }
    });
</script>
</body>
</html>

传参时,需使用vue预留的关键字$event作为事件参数额外传入

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="click($event, 12)">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        methods: {
            click(e, num) {
                console.log(num + ": ", e);
            }
        }
    });
</script>
</body>
</html>
  • 事件修饰符
  • prevent

相当于e.preventDefault,即阻止浏览器默认行为

<a href="https://www.baidu.com" @click.prevent="fn">click me</a>
  • stop

相当于e.stopPropagation,即阻止事件冒泡

<button @click.prevent="fn">click me</button>
  • capture

启用事件捕获

<button @click.capture="fn">click me</button>
  • self

不会受到冒泡或捕获的影响,只有点击自己时才会被触发

<button @click.self="fn">click me</button>
  • once

只触发一次事件

<button @click.once="fn">click me</button>
  • passive

提供移动端的性能,事件的三阶段:捕获-事件对象-冒泡,使用passive后,如果是冒泡,则会跳过捕获阶段,捕获同理

<button @click.passive="fn">click me</button>
  • 按键修饰符
//注:enter键的键盘码为13
if(e.keyCode === 13) {//...} <=等价于=> @keyup.13='fn' <=等价于=> @keyup.enter='fn'
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input @keyup.13="fn" type="text"/>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        methods: { //绑定事件的事件函数放在methods属性中
            fn() {
                alert(1);
            }
        }
    })
</script>
</body>
</html>
v-for:数据遍历
  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!--  遍历数组方式一:只要值  -->
    <ul>
        <li v-for="item in arr">{{item}}</li>
    </ul>
    <!--  遍历数组方式二:要值和索引  -->
    <ul>
        <li v-for="(item, index) in arr">{{index}}: {{item}}</li>
    </ul>
    <!--  遍历数组:数组中的元素是一个个对象  -->
    <ul>
        <li v-for="obj in jsonArr">{{obj.id}}: {{obj.name}}</li>
    </ul>
    <!--  遍历对象:只要值  -->
    <ul>
        <li v-for="value in obj">{{obj}}</li>
    </ul>
    <!--  遍历对象:要值和键  -->
    <ul>
        <li v-for="(value, key) in obj">{{key}}: {{value}}</li>
    </ul>
    <!--  快速生成指定个数的元素:i将从1开始  -->
    <ul>
        <li v-for="i in 3">{{i}}</li>
    </ul>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            arr: ["张三", "李四", "王五"],
            jsonArr: [
                {id: 1, name: "张三"},
                {id: 2, name: "李四"},
                {id: 3, name: "王五"}
            ],
            obj: {
                name: "张三",
                gender: "男"
            }
        }
    })
</script>
</body>
</html>
  • key属性

vue官网推荐在使用v-for指令时,加上key属性

  • 问题
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p v-for="item in list">
        {{item}}: <input type="text" />
    </p>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            list: [
                {id:1, name:'张三'},
                {id:2, name:'李四'},
                {id:3, name:'王五'}
            ]
        }
    })
</script>
</body>
</html>

页面效果为:
在这里插入图片描述
在李四对应文本框中输入以下内容:
在这里插入图片描述
继续在浏览器中给list添加元素并放在第一个元素位置上
在这里插入图片描述
可以看到输入的内容跑到张三对应的文本框中了,这是因为默认是跟着索引走的

  • 方案

使用 key 之后就不会存在这样的问题,而key的使用又分为以下三种情况

  1. 当遍历的数据是个对象时:直接使用对象的key作为v-for的可以即可
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p v-for="(value, key) in people" :key="key">
        {{key}}: {{value}}
    </p>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            people: {
                name: "张三",
                age: 12,
                gender: "男"
            }
        }
    })
</script>
</body>
</html>
  1. 当遍历的数据是个数组,且数组中存的是对象时:使用对象中的固定且唯一的属性作为v-for的key
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p v-for="item in list" :key="item.id">
        {{item}}: <input type="text" />
    </p>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            list: [
                {id:1, name:'张三'},
                {id:2, name:'李四'},
                {id:3, name:'王五'}
            ]
        }
    })
</script>
</body>
</html>
  1. 当遍历的数据是个数组,但数组中存的不是对象,或者是对象但是没有固定且唯一的属性时:可以使用数组的索引作为v-for的key,但是切记不要让数组中元素的顺序发生改变
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p v-for="(item, index) in list" :key="index">
        {{item}}: <input type="text" />
    </p>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            list: ['张三', '李四', '王五']
        }
    })
</script>
</body>
</html>
v-if, v-else-if, v-else:条件渲染
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1 v-if="length > 15">long</h1>
    <h1 v-else-if="length > 10">normal</h1>
    <h1 v-else>short</h1>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            length: 12
        }
    });
</script>
</body>
</html>
v-show:隐藏显示
  • 语法
v-show="boolean"; //主要用于元素的隐藏和显示,值为布尔类型,true表示显示
  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <div v-show="show">~~~~~~~~~~~~~~~~~~~~~~</div>
    <button :title="title" @click="toggle">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            show: true,
            title: "this is a click button"
        },
        methods: {
            toggle() {
                console.log(this.show);
                this.show = !this.show;
            }
        }
    });
</script>
</body>
</html>
  • v-if的异同

值都是布尔类型,但是实现的方式不同,v-show是通过display: none实现节点的显示与否,而v-if则是通过创建和删除节点实现的,通常搭配其他条件渲染指令一起使用;此外频繁的切换显示隐藏时,使用v-show更加适合

v-pre:不进行解析

该指令可以将指定标签中的表达式视为普通文本,不进行解析

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{msg}}</h1> <!--显示:hello-->
    <h1 v-pre>{{msg}}</h1> <!--显示:{{msg}}-->
    <h1 v-pre v-text="msg"></h1> <!--显示:空(无论二者位置先后)-->
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: "hello"
        }
    });
</script>
</body>
</html>
v-cloak:解决闪烁问题
  • 初衷

使用{{}},即mustache插值表达式时,渲染时偶尔会未渲染完毕,而先闪烁出{{msg}},之后才出现解析后的结果,v-cloak用于解决此问题

  • 原理和使用
  1. 给要遮盖的元素添加这个指令
  2. 根据属性选择器,找到要遮盖的元素,设置display:none
  3. 当数据解析完毕后,vue会自动删除该指令
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
<div id="app">
    <div v-cloak>{{msg}}</div>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: 'message'
        }
    })
</script>
</body>
</html>
v-once:只解析一次

即只会解析一次,之后无论数据如何变化,都保持不变

自定义指令
  • 全局自定义组件 & 基本介绍

钩子函数

Vue.directive('test', {
    bind() {
        //当自定义指令和标签绑定时触发,如 <h1 v-test></h1>
    },
    inserted() {
        //当自定义指令绑定的标签插入到父元素时触发,如 <div id="app"><h1 v-test></h1></div>
    },
    update() {
        //当自定义指令中的数据发生变化时触发,如 <h1 v-test>{{ num }}</h1>中的num变化时触发
    },
    componentUpdated() {
        //当自定义指令中的数据变化完成之后触发,如 <h1 v-test>{{ num }}</h1>中的num变化后触发
    },
    unbind() {
        //当自定义指令和标签解绑时触发,如 绑定的元素被移除(非v-show似的隐藏,而是v-if似的有无)
    }
})

参数

bind(element, binding) {
    console.log(element); //自定义指令绑定的元素,如el
    console.log(binding.name); //自定义指令名称,如此例为 test
    console.log(binding.value); //指令绑定的值,如 v-test='1+1',则value为2
    console.log(binding.oldValue); //指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,且无论值是否改变都可用
    console.log(binding.expression); //指令表达式,如 v-test='1+1',则为 1+1
    console.log(binding.arg); //传给指令的参数,如 v-test:foo 中,参数为 foo
    console.log(binding.modifiers); //一个包含修饰符的对象,如 v-test.foo.bar 中,修饰符对象为 { foo: true, bar: true }
}

使用

<div id="app">
    <p v-test></p>
</div>

  • 局部组件 & 应用
<template>
    <div class="used_v">
        <input type="text" v-test placeholder="v-test demo">
    </div>
</template>

<script>
    export default {
        name: "UsedV",
        directives: {
            test: {
                inserted(el) {
                    el.focus(); //自动聚焦
                    el.style.color = '#f99'
                }
            }
        }
    }
</script>

<style scoped>
    .used_v {
        color: aqua;
    }
</style>

计算属性

指令和表达式的重新计算

指的是,当data中的数据变化时,指令(如v-for)和表达式(如{{}}、fn等)都会重新执行一遍


另外,如果进行了 v-model 双向数据绑定,view中数据改变,引起data改变,进而导致重新计算


所以,如果页面中使用了函数调用,即fn()的形式,每次重新计算都将进行一次调用,很影响性能

computed及特点

一般而言,数据放在data属性中,事件放在methods属性中,而计算属性则放在computed属性中


计算属性有以下几个特点

  1. 写起来是个方法,但是不能当方法用,即不能加()
  2. 一定要有返回值,返回值即为属性的值
  3. 不能和data中的属性重名,不过可以使用data中的属性值
  4. 一般用在根据一个已知的data值,得到一个data中不存在的新值
  5. 新值只和相关数据有关,而与其他数据的变化无关
示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{result}}</h1> <!--当count值变化时,此表达式会重新计算-->
    <input type="number" v-model="count">
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            count: 0
        },
        computed: {
            result() {
                return this.count > 0 ? parseInt(this.count) + 10 : 'none';
            }
        }
    })
</script>
</body>
</html>

几个特殊的数据问题

$nextTick:vue的dom操作为异步
  • 现象
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{num}}</h1>
    <button @click="fn">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            num: 100
        },
        methods: {
            fn() {
                console.log(document.querySelector('h1').innerText); //100
                this.num += 100;
                console.log(document.querySelector('h1').innerText); //100
            }
        }
    })
</script>
</body>
</html>
  • 解释

如题,当data中的num变化时,vue会进行指令的重新计算并更新dom,但是这个操作是异步的,可能尚未更新完成,主线程代码已经往下走,导致打印的num仍为之前的num

  • 方案

vue提供了$nextTick(callback)解决此问题,可以将它理解为一个事件,当vue的dom操作完成时触发执行其中的callback回调函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{num}}</h1>
    <button @click="fn">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            num: 100
        },
        methods: {
            fn() {
                console.log(document.querySelector('h1').innerText); //100
                this.num += 100;
                this.$nextTick(() => { //注意this不能省略
                    console.log(document.querySelector('h1').innerText); //200
                })
            }
        }
    })
</script>
</body>
</html>
数据变化的统一更新
  • 现象
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{num}}</h1>
    <button @click="fn">click me</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            num: 0
        },
        methods: {
            fn() {
                this.num += 100;
                for(var i = 0; i < 100; i++){
                    this.num += 1;
                }
                this.num += 100;
            }
        }
    })
</script>
</body>
</html>

click后,页面中直接显示的300,而非100,101,…,300

  • 解释

vue对dom的重新更新操作,会等待fn函数结束,确定了num的值之后,再统一的进行一次更新操作

$set:数据响应式问题
  • 现象
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="obj.name">
    <button @click="fn">button</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            obj: {}
        },
        methods: {
            fn() {
                this.obj.name = "张三"
            }
        }
    })
</script>
</body>
</html>

无论是在input中输入,还是点击button,可以在devTools中观察到obj始终为空

  • 解释

Vue无法检测到对象属性的添加和删除,因为Vue只会在Vue对象实例化时对属性执行getter & setter,所以属性必须在其初始化之前就存在,才能让Vue将它转换成响应式的

  • 方案

vue提供了$set,一个用于设置属性的方法来解决此问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="obj.name">
    <button @click="fn">button</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            obj: {}
        },
        methods: {
            fn() {
                this.$set(this.obj, 'name', "张三");
            }
        }
    })
</script>
</body>
</html>
  • 注意
$set(target, key, value);
/*
 * target:不能是Vue实例,也不能是Vue的根属性 -- data属性
 */

监听器

说明

监听器和计算属性的区别在于一是它不需要返回值,而是计算属性得到的是data中不存在的值,而监听器则监听的就是data中存在的值


可以用于数据持久化,即一旦数据发生变化,自动执行存储


放在watch属性中,使用时根据监听对象类型(基本数据类型和引用数据类型)而有所不同

监听基本数据
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="num">
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            num: 100
        },
        watch: {
            num(newValue, oldValue) {
                console.log("newValue: ", newValue);
                console.log("oldValue: ", oldValue);
            }
        }
    })
</script>
</body>
</html>
监听对象

方式一:直接监听对象的具体属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="obj.name">
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            obj: {
                name: "张三"
            }
        },
        watch: {
            'obj.name'(newValue, oldValue) {
                console.log("newValue: ", newValue);
                console.log("oldValue: ", oldValue);
            }
        }
    })
</script>
</body>
</html>

方式二:通过开启深度监听,监听整个对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="obj.name">
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            obj: {
                name: "张三"
            }
        },
        watch: {
            obj: {
                deep: true, //开启深度监听(直接使用obj(newValue, oldValue)监听到的是引用类型obj的引用地址)
                immediate: true, //立即开启,即会监听到刚加载时的值
                handler(newValue, oldValue) {
                    console.log("newValue: ", newValue);
                    console.log("oldValue: ", oldValue);
                }
            }
        }
    })
</script>
</body>
</html>
监听数组

方式一:数组也是个对象,所以可以用监听对象的方式进行监听

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="arr[0].xiaoming.age">
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            arr: [
                {
                    xiaoming: {
                        gender: 'female',
                        age: 12
                    }
                },
                {
                    xiaohong: {
                        gender: 'male',
                        age: 12
                    }
                }
            ]
        },
        watch: {
            arr: {
                deep: true,
                handler (newArr) {
                    console.log("newArr: ", newArr);
                }
            }
        }
    })
</script>
</body>
</html>

方式二:数组以拦截器方式进行监听

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="fn">button</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            arr: [
                {
                    xiaoming: {
                        gender: 'female',
                        age: 12
                    }
                },
                {
                    xiaohong: {
                        gender: 'male',
                        age: 12
                    }
                }
            ]
        },
        methods: {
            fn(){
                /*特殊的不生效情形一:通过下标的方式修改数组的值*/
                // this.arr[0] = {};
                //解决方案:实质是内部使用了this.arr.splice(0, 1, 'abc')
                // this.$set(this.arr, 0, {});

                /*特殊的不生效情形二*/
                // this.arr.length = 0;
                //解决方案:即将所有的数据删除
                this.arr.splice(0, this.arr.length);
            }
        },
        watch: {
            /**
             * 监听的实现
             * 基本数据和对象:通过setter方法实现
             * 数组:通过拦截器,拦截push pop shift unshift reverse sort splice这7个方法
             *      即一旦发现数组调用了这7个方法,就会触发监听
             */
            arr(newArr) {
                console.log("newArr: ", newArr);
            }
        }
    })
</script>
</body>
</html>

过滤器

语法
{{data | filterName}}
/*
 * data:数据
 * |:管道符
 * filterName:过滤器名称
 */
全局过滤器

顾名思义,所有Vue实例都可以用的过滤器,注意注册代码要写在实例化代码之前

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{date | dateFilter}}</h1>
    <h1>{{date | dateFilter('YYYY-MM-DD HH:mm:ss')}}</h1>
</div>
<script src="../lib/moment.js"></script>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    //如果带参数,则第一个参数为data中的数据,即要进行过滤操作的数据
    Vue.filter('dateFilter', (date, format='YYYY-MM-DD') => {
        return moment(date).format(format);
    });
    const vm = new Vue({
        el: "#app",
        data: {
            date: new Date()
        }
    })
</script>
</body>
</html>
局部过滤器

只有当前vue实例可以使用,放在filters属性中,且当和全局过滤器的过滤器名重复时,局部过滤器优先生效

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{date | dateFilter}}</h1>
    <h1>{{date | dateFilter('YYYY-MM-DD HH:mm:ss')}}</h1>
</div>
<script src="../lib/moment.js"></script>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            date: new Date()
        },
        filters: {
            dateFilter(date, format='YYYY-MM'){
                return moment(date).format(format);
            }
        }
    })
</script>
</body>
</html>

生命周期

介绍
  • 组件(实例)生命周期

所有的Vue组件,都是Vue实例,即一个组件就对应为一个实例,所以组件的生命周期即为实例的生命周期,具体而言,一个组件(实例)从开始到最后消失所经历的各种状态,就是它的一个生命周期

  • 生命周期钩子函数

从组建被创建,到被挂到页面上运行,再到最后页面关闭、组件被销毁,这三个阶段总是伴随着组件的各种事件,而这些事件,就被称为组件的生命周期函数,也叫钩子函数,这些钩子函数的名称都是Vue中已经规定好的

  • 生命周期的三个阶段
  1. 挂载阶段:进入页面
  2. 更新阶段:数据发生变化时
  3. 卸载阶段:关闭页面(实例卸载)
挂载阶段
数据初始化(找到数据)
  1. new Vue(): instance

创建Vue实例

  1. init: events & lifecycle

初始化Vue内部的事件,并开启Vue生命周期

  • 钩子函数:beforeCreate

此时尚未实现数据响应式,所以无法操作或获取data、methods中的数据或函数等

  1. init: injections & reactivity

数据初始化,将data进行劫持并实现数据响应式

  • 钩子函数:created

此时已经实现数据响应式,可以发送ajax请求、操作data中的数据以及本地数据等

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: "hello"
        },
        beforeCreate() {
            console.log("beforeCreate: ", this.msg); //undefined
        },
        created(){
            console.log("created: ", this.msg); //hello
        }
    });
</script>
</body>
</html>
找到模板
  1. has 'el' option ?

判断是否有el属性,如果没有,可以手动调用$mount来指定Vue实例管理的边界

const vm = new Vue({
    data: {
        msg: "hello"
    }
});
vm.$mount("#app");
  1. has 'template' option ?

判断是否有template属性,
如果没有则将el指定的边界元素作为模板进行编译;
如果有则将该属性的值作为模板进行编译

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app" v-cloak>el模板:{{msg}}</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: "hello"
        },
        //template: `<h1>${this.msg}</h1>` --> 无法直接引入变量,显示为undefined
        template: `<h1>{{this.msg}}</h1>` //hello(有了template后,el的整个#app都不会显示)
    })
</script>
</body>
</html>
DOM渲染
  • 钩子函数:beforeMount

在渲染DOM之前调用,此时DOM中的数据还是模板

  • 渲染DOM

创建vm.$el替换el中的内容,即将写的模板内容翻译成DOM,然后渲染到页面中

  • 钩子函数:mounted

在DOM渲染完成后执行,此时可以操作DOM

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{msg}}</h1>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: "hello"
        },
        beforeMount() {
            console.log("beforeMount: ", document.querySelector('h1').innerHTML); //{{msg}}
        },
        mounted(){
            console.log("mounted: ", document.querySelector('h1').innerHTML); //hello
        }
    });
</script>
</body>
</html>
更新阶段
  1. 钩子函数:beforeUpdate

当数据发生了变化就会进入更新阶段,而首先进入的就是该钩子函数,由于此时尚未开始更新,所以获取到的是更新前的DOM数据

  1. Virtual DOM: rerander & patch

更新DOM,更新的方式是以patch补丁的方式进行的,而非将整个页面都进行重新渲染,即哪里发生改动,就更新哪里

  • 虚拟DOM

每个标签上都有着众多的属性和方法,所以如果是直接使用真实DOM,一旦有标签发生变化,就会遍历该标签的所有属性和方法,若其含有子节点,还会再递归遍历其子节点,以查找是否存在差异,如果存在差异则执行更新。


虚拟DOM,实质上就是一个JS对象,因为操作js,更简单,速度也更快。
具体就是一个标签就对应着一个JS对象,该对象上含有该标签的所有信息(如tagName属性就是标签的名字),当标签变化时,会生成一个变化后的JS对象,通过DIFF算法,找到其中的差异部分,进而将该变化部分更新到真实DOM中

  1. 钩子函数:updated

当更新完成后触发,此时获取到的是更新后的数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{msg}}</h1>
    <button @click="fn">button</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            msg: "hello"
        },
        beforeUpdate() {
            console.log("beforeUpdate: ", document.querySelector('h1').innerHTML); //hello
        },
        updated(){
            console.log("updated: ", document.querySelector('h1').innerHTML); //hello update.
        },
        methods: {
            fn() {
                this.msg = "hello update."
            }
        }
    });
</script>
</body>
</html>
卸载阶段
  1. 钩子函数:beforeDestroy

当页面关闭时,或者手动调用vm.$destroy(),将进入卸载阶段,首先触发的是就是该钩子函数,即在关闭之前执行的函数

  1. Teardown
    卸载过程,此时会将所有的watchers、子组件以及事件等都进行卸载,即Vue会主动清理自身的内容,包括响应式数据、@click绑定事件等
  • 钩子函数:destroy
  • 常用于清理定时器、手动创建的DOM对象等
const vm = new Vue({
    el: "#app",
    data: {
        msg: "第二阶段-更新",
        timerId: ''
    },
    created() {
        this.timerId = setInterval(function () {
            console.log('正在执行定时任务中...')
        }, 1000);
    },
    beforeDestroy() {
        clearInterval(this.timerId); //最好在beforeDestroy钩子函数中进行清理
    },
    destroy() {
    }
});

组件

说明
  • 介绍
  • 可以看做是一个可以放复用的UI模块
  • 小到一个标签,大到一个页面,都可以是一个组件
  • 一个组件对应一个Vue实例
  • 组件化开发

对于一个完整的页面,可以抽离成是有一个一个独立的组件组成的,而这些组件还可以复用在其他页面上,即它和模块化一样,都是一种可以实现复用的开发概念,只不过模块化侧重的是逻辑&业务方面,而组件则侧重UI&界面

全局组件和局部组件
  • 全局组件

所有Vue实例都可以使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!-- 2.组件的使用 -->
    <global-component></global-component>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    /* 1.组件的配置 */
    Vue.component(
        'globalComponent', //组件名
        {
            //template:含义同el
            template: `<div>{{msg}} this is a global component demo</div>`,
            //data:含义同data属性,这里是个方法,但要求返回值为一个对象
            //不直接为对象的原因:只让组件复用,而不让数据复用
            data() {
                return {
                    msg: 'hello'
                }
            },
            //其余配置项同Vue示例中的配置项,常用如下所示
            methods: {},
            watch: {},
            computed: {},
            filters: {}
        }
    );
    /* 3.组件的触发:使用时,必须创建对应的Vue实例才会生效 */
    const vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>
  • 局部组件

只有当前Vue实例能用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!-- 3.使用 -->
    <child-component></child-component>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    /* 1.创建局部组件 */
    const Child = {
        template: `<div>{{childMsg}}</div>`,
        data: function () {
            return {
                childMsg: '这是子组件(局部组件)中的数据'
            }
        }
    };
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg: "这是父组件的数据"
        },
        /* 2.引入创建的局部组件,作为当前实例(组件)的子组件 */
        components: {
            childComponent: Child
        }
    });
</script>
</body>
</html>
  • 注意
  1. 注册的位置

组件代码需在Vue实例化代码之前编写

  1. 只能有一个根节点
template: `<div>component</div>` //正确
template: `<div>component</div><h1></h1>` //错误
组件的独立性

组件是独立的个体,不能直接访问其他组件的数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <child-component></child-component>
    <h1>{{childMsg}}</h1> <!-- 父组件无法访问子组件的数据 -->
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const Child = {
        /* 子组件无法访问父组件的数据 */
        template: `<div>{{parentMsg}}</div>`,
        data: function () {
            return {
                childMsg: '这是子组件(局部组件)中的数据'
            }
        }
    };
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg: "这是父组件的数据"
        },
        components: {
            childComponent: Child
        } 
    });
</script>
</body>
</html>
组件之间的通信机制
父传子
  • 说明

指子组件访问父组件的数据

  • 方式一:prop
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :msg1="parentMsg1" :msg2="parentMsg2"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('child', {
        template: `<div>子组件:{{msg1}},{{msg2}}</div>`,
        props: ['msg1', 'msg2']
    });
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg1: '父组件数据1',
            parentMsg2: '父组件数据2'
        }
    })
</script>
</body>
</html>
  • 方式二:provide + inject
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Parent></Parent>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>子组件:{{app.msg}}</div>`,
        inject: ['app']
    });
    Vue.component('Parent', {
        template: `<div>父组件:<Child></Child></div>`,
        data() {
            return {
                msg: '父组件数据'
            }
        },
        provide() {
            return {
                app: this
            }
        }
    });
    const vm = new Vue({
        el: '#app'
    })
</script>
</body>
</html>
子传父
  • 说明

指父组件访问子组件的数据

  • 方式一:自定义事件 + $emit
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child @pfn="parentFunc"></Child>
    <span>{{msg}}</span>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>子组件</div>`,
        data() {
            return {
                childMsg: '子组件数据'
            }
        },
        created() {
            /**
             * $emit:可以实现手动触发事件
             * $emit传入的事件名称只能使用小写,不能使用大写的驼峰规则名称
             */
            this.$emit('pfn', this.childMsg);
        }
    });
    const vm = new Vue({
        el: "#app",
        data: {
            msg: ''
        },
        methods: {
            parentFunc(childParam) {
                this.msg = childParam;
            }
        }
    })
</script>
</body>
</html>
  • 方式二:sync + update
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p>{{parentMsg}}</p>
    <Child :middleData.sync="parentMsg"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `
            <div>子组件:
                <input type="text" @keyup.enter="valueChanged" v-model="childMsg">
            </div>
        `,
        data() {
            return {
                childMsg: ''
            }
        },
        props: ['middledata'], //注意,会自动将middleData转为小写,即middledata,所以此处写的是小写的
        methods: {
            valueChanged() {
                this.$emit('update:middledata', '子组件数据: ' + this.childMsg)
            }
        }
    });
    const vm = new Vue({
        el: '#app',
        data: {
            parentMsg: '父组件数据'
        }
    })
</script>
</body>
</html>
非父子
  • 说明

指非父子关系的组件之间的数据传递

  • 事件总线方式:接收方注册事件,发送方触发事件

如,当点击Foo组件时,Foo组件将数据传递给Bar组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Foo></Foo>
    <Bar></Bar>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    /**
     * 创建事件总线:实质上就是一个空的Vue实例
     */
    const bus = new Vue();

    Vue.component('Foo', {
        template: `<div>Foo:<button @click="sendMsg2Bar">button</button></div>`,
        data() {
            return {
                fooMsg: 'Foo的数据'
            }
        },
        methods: {
            sendMsg2Bar() {
                /**
                 * 注意使用bus事件总线来触发
                 */
                bus.$emit('barevent', this.fooMsg)
            }
        }
    });
    Vue.component('Bar', {
        template: `<div>Bar:{{barMsg}}</div>`,
        data() {
            return {
                barMsg: ''
            }
        },
        created() {
            /**
             * 一样的,用事件总线来注册事件
             */
            let that = this;
            bus.$on('barevent', dataFromFoo => {
                that.barMsg = dataFromFoo;
            })
        }
    });
    const vm = new Vue({
        el: '#app'
    })
</script>
</body>
</html>
关于prop
  • 单向数据流

指所有的prop属性,都会使得父子之间形成一个单向的下行绑定,即父级的更新都会向下流动到子组件中,反之则不行


之所以如此设计,是为了防止子组件改变父组件的状态,即子组件不允许修改父组件传来的prop数据,从而避免导致应用数据流向的难以理解

  • 和双向数据绑定并不矛盾

双向数据绑定指的是同一组件的关系,而单向数据流则是组件之间的关系

  • Vue是单向还是双向?

Vue是单向,v-model属于例外

  • 只读属性
  • 概述

对于prop中的属性,应该视为只读属性,原则上不建议对齐修改

  • 基本数据类型:无法修改
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :msg="parentMsg"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js">>> </script>
<script>
    Vue.component('Child', {
        template: `<div>子组件:{{msg}}</div>`,
        props: ['msg'],
        mounted() {
            console.log(this.msg); //父组件数据
            this.msg = "aaa"; //控制台报错
        }
    });
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg: '父组件数据',
        }
    })
</script>
</body>
</html>
  • 复杂数据类型:不修改引用地址即可
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :msg="parentMsg"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>子组件:{{msg}}</div>`,
        props: ['msg'],
        mounted() {
            this.msg.key = 'hello'; //可以修改(但是不建议修改)
            console.log(this.msg); //父组件数据
            this.msg = {}; //控制台报错,无法修改地址
        }
    });
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg: {
                key: '父组件数据'
            },
        }
    })
</script>
</body>
</html>
  • 大小写问题
  1. 浏览器会自动将属性/标签转为小写
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :childMsg="parentMsg"></Child> <!--会自动将属性childMsg转为childmsg-->
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<DIV>子组件:{{childmsg}}</DIV>`, //会自动转为<div>
        props: ['childmsg'], //所以此处需要用小写的childmsg
    });
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg: '父组件数据'
        }
    })
</script>
</body>
</html>
  • 使用-处理
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :child-msg="parentMsg"></Child> <!--会自动将属性child-msg转为childMsg-->
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<DIV>子组件:{{childMsg}}</DIV>`, //会自动转为<div>
        props: ['childMsg'], //此处可以直接用驼峰,无需转小写
    });
    const vm = new Vue({
        el: "#app",
        data: {
            parentMsg: '父组件数据'
        }
    })
</script>
</body>
</html>

注意:-不适用于事件

<child @parent-func="parentFunc"></child> //事件时:无法将parent-func转为parentFunc
  • 类型和默认值
  1. 直接改属性赋予一个静态值:都是字符串类型
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child num="123" bool="true"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>{{num}} - {{bool}}</div>`,
        props: ['num', 'bool'],
        created() {
            console.log(typeof this.num); //string
            console.log(typeof this.bool); //string
        }
    });
    const vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>
  1. 属性前添加冒号:可获得真实类型
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :num="123" :bool="true"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>{{num}} - {{bool}}</div>`,
        props: ['num', 'bool'],
        created() {
            console.log(typeof this.num); //number
            console.log(typeof this.bool); //boolean
        }
    });
    const vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>
  1. 指定所需类型:非指定类型则报错
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :num="123" bool="true"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>{{num}} - {{bool}}</div>`,
        props: {
            num: Number,
            bool: Boolean //控制台报错
        },
        created() {
            console.log("num: ", this.num); //num: 123
            console.log("bool: ", this.bool); //bool: true
        }
    });
    const vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>
  1. 指定默认值
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child :bool="true"></Child>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>{{num}} - {{bool}}</div>`,
        props: {
            num: {
                type: Number,
                default: 100
            },
            bool: {
                type: Boolean,
                default: false
            }
        },
        created() {
            console.log("num: ", this.num); //num: 100
            console.log("bool: ", this.bool); //bool: true
        }
    });
    const vm = new Vue({
        el: "#app"
    })
</script>
</body>
</html>
关于refs
  • 作用
  1. 可以获取DOM元素 或 组件
  2. 可以实现子传父
  • 原理

通过ref属性将组件或标签注册到refs中,此后便可以用this.$refs方式获取refs中的注册进来的标签或组件

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <Child ref="child"></Child>
    <span ref="span">this is a span tag</span>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component('Child', {
        template: `<div>子组件</div>`,
        data() {
            return {
                childMsg: "子组件数据"
            }
        }
    });
    const vm = new Vue({
        el: "#app",
        mounted() {
            console.log(this.$refs); //refs = { child: VueComponent {_uid: 1, _isVue: true, $options:...} 组件对象, span: <span ref="span"></span>}
            console.log(this.$refs.child.childMsg); //子组件数据(实现了子传父)
            console.log(this.$refs.span); //<span>this is a span tag</span>
            console.log(this.$refs.span.innerHTML); //this is a span tag
        }
    })
</script>
</body>
</html>
关于$attrs 和 $listeners
  • 作用

$attrs:从上往下传递不被props引用的属性
$listeners:从上往下传递事件

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <parent @penent="fn" a="aaa" b="bbb" c="ccc"></parent>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const Child = {
        template: `<button @click="cfn">child component</button>`,
        created() {
            console.log("child-attrs: ", this.$attrs);
            console.log("child-listeners: ", this.$listeners);
        },
        methods: {
            cfn() {
                this.$emit('penent');
            }
        }
    };
    const Parent = {
        /**
         * 属性传递语法:v-bind="$attrs"
         * 事件传递语法:v-on="$listeners"
         */
        template:
            `<div>
                父组件:{{a}} <Child v-bind="$attrs" v-on="$listeners"></Child>
            </div>`,
        props: ['a'],
        created() {
            console.log("parent-attrs: ", this.$attrs);
            console.log("parent-listeners: ", this.$listeners);
        },
        components: {Child}
    };
    new Vue({
        el: "#app",
        methods: {
            fn() {
                console.log("事件已经传递下去了")
            }
        },
        components: {parent: Parent}
    })
</script>
</body>
</html>
组件复用的注意事项
  • 问题

组件复用,即后续(非第一次)再使用该组件时,由于该组件已经存在,而如created生命周期函数只会在组件创建时数据初始化结束后调用一次,所以将不会再被触发

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/one/1">one</router-link>
    <router-link to="/two/2">two</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const One = {
        template: `<div>one</div>`,
        created() {
            console.log(this.$route.params.id); //只会输出一次
        }
    };
    const router = new VueRouter({
       routes: [
           {path: '/one/:id', component: One},
           {path: '/two/:id', component: One}
       ]
    });
    new Vue({
        router,
        el: "#app"
    })
</script>
</body>
</html>
  • 方案

使用watch监听$route

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/one/1">one</router-link>
    <router-link to="/two/2">two</router-link>
    <router-view></router-view>
</div>
<script >> src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const One = {
        template: `<div>one</div>`,
        watch: {
            $route(newValue) {
                console.warn(newValue.params.id); //每次点击都会输出
            }
        },
        created() {
            console.log(this.$route.params.id); //只会输出一次
        }
    };
    const router = new VueRouter({
       routes: [
           {path: '/one/:id', component: One},
           {path: '/two/:id', component: One}
       ]
    });
    new Vue({
        router,
        el: "#app"
    })
</script>
</body>
</html>
单文件组件
  • 说明

指后缀为.vue的组件,由三个部分组成

  1. template:模板结构
  2. script:组件代码逻辑
  3. style:样式
  • 注意

单文件组件无法直接在浏览器中使用,需要经过如webpack等打包工具处理后,才能使用

  • 示例:foo.vue
<template>
    
</template>

<script>
    export default {
        data() {
            return {
                msg: "hello vue"
            }
        },
        computed() {
            
        }, 
        methods: {
            
        }
    }
</script>

<style scoped> 
<!--
  scoped:表示该css样式只限于在当前组件中使用
  默认情况下:组件之间的样式会共享,如a组件的h1标签设置为红色背景,则b组件的h1标签背景也会变成红色
  样式互不影响原理:Vue会给每个组件的所有DOM都添加上一个属性,格式为(data-v-组件唯一值),然后通过属性选择器进行DOM样式设置

  注意:对于组件中动态添加的样式,如 vue-quill-editor, v-html等添加的样式,一样也是不起效果的
  使动态添加的样式生效的方案:
     方案一:再添加一个没有 scoped 的 style,并将动态添加的样式放入。(这种方式需要保证加入的样式不会影响到其他组件)
     方案二:穿透语法( 固定父元素 /deep/ 动态子元素 )
     方案三:穿透语法( 固定父元素 >>> 动态子元素 )(不支持less)
-->
</style>

也可以将单个文件对接三个文件的引入

//User.vue
<template src="./User.html"> //注意:User.html不需要其他标签,直接写template中的内容即可
</template>
<script src="./User.js">
</script>
<style scoped lang="less" src="./User.less">
</script>
混入
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <one></one>
    <two></two>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const Mixin = {
        methods: {
            sayHi() {
                console.log("Hello " + this.name)
            }
        }
    }
    Vue.component('one', {
        template: `<button @click="sayHi">hi</button>`,
        mixins: [Mixin],
        data() {
            return {
                name: "ONE"
            }
        }
    })
    Vue.component('two', {
        template: `<button @click="sayHi">hi</button>`,
        mixins: [Mixin],
        data() {
            return {
                name: "TWO"
            }
        },
        methods: {
            sayHi() { //如果组件自身就有该方法,优先使用组件自己的方法
                console.log("Hello Two ~")
            }
        }
    })
    new Vue({
        el: '#app'
    })
</script>
</body>
</html>
内置组件
component
  • 作用

控制要显示的组件

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="show('one')">show one</button> <!--记得参数加上引号-->
    <button @click="show('two')">show two</button>
    <component :is="name"> <!--is的值表示要进行展示的组件,可以是组件名,也可以是组件的name属性值-->
        <one></one>
        <two></two>
    </component>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const ONE = {
        name: "one",
        template: `<p>ONE COMPONENT</p>`
    };
    Vue.component("two", {
        name: "two",
        template: `<p>TWO COMPONENT</p>`
    });
    new Vue({
        el: "#app",
        data: {
            name: 'two' //先默认展示two组件
        },
        methods: {
            show(name) {
                this.name = name;
            }
        },
        components: {one: ONE}
    })
</script>
</body>
</html>
keep-alive
  • 缓存

上例中,如果组件one存在input,则再切到two再切回one时,input的值将不会被保留,keep-alive具有缓存作用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="show('one')">show one</button> <!--记得参数加上引号-->
    <button @click="show('two')">show two</button>
    <keep-alive include="one,two"> <!--默认当中所有组件都会开启缓存,include用于指定开启缓存的组件(逗号前后不要有空格)-->
        <component :is="name">
            <one></one>
            <two></two>
        </component>
    </keep-alive>

</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    const ONE = {
        name: "one",
        template: `<div><input type="text" placeholder="one" name="one"></div>`
    };
    Vue.component("two", {
        name: "two",
        template: `<div><input type="text" placeholder="two" name="two"></div>`
    });
    new Vue({
        el: "#app",
        data: {
            name: 'two' //先默认展示two组件
        },
        methods: {
            show(name) {
                this.name = name;
            }
        },
        components: {one: ONE}
    })
</script>
</body>
</html>
  • 结合vue-touter的使用
<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

路由

SPA

Single Page Application,单页面应用,即只有一个页面的应用


可以理解为原本的多个页面,抽取出共用部分(顶部、侧边等)作为唯一的页面,而页面的内容则通过组件的方式进行设置


优点是减少了请求体积,加快了页面响应速度,并可以进行局部刷新,提高用户体验,但是缺点是不利于SEO,且需要学习路由

路由的介绍

指的是浏览器URL中的哈希值(#hash)和展示视图内容之间的对应规则,是一套映射规则,一对一的映射规则,并由开发人员制定规则


具体为,当URL中的哈希值发生改变时,路由会根据制定好的规则,找到并展示对应的视图内容


对于Vue路由而言,一个哈希值,就对应为一个组件

路由的基本使用与声明式导航
  • 声明式导航

点击标签进入入口,而无需手动去修改请求地址

  • 安装

npm i vue-router

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/one">ONE</router-link> <!--声明式导航形式的 入口-->
    <router-link to="/two">TWO</router-link> <!--最终会被转成<a href="#/two">TWO</a>-->
    <router-view></router-view> <!--出口-->
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const One = {
        template: `<div>one</div>`
    };
    /*不能用以下方式注册的组件:Two is undefined*/
    // Vue.component('Two', {
    //     template: `<div>two</div>`
    // });
    const Two = {
        template: `<div>two</div>`
    };
    const router = new VueRouter({
        routes: [
            {path: '/one', component: One},
            {path: '/two', component: Two}
        ]
    });
    const vm = new Vue({
        router, // 将路由实例挂载到Vue上
        el: "#app",
        data: {}
    })
</script>
</body>
</html>
编程式导航
  • $router

$route为路由对象,$router则是路由实例,主要用于编程式导航

  • 常用API
  • push

跳转,可实现点击One组件的button按钮,跳转到Two组件,有历史记录,可以回退到上一级

  • back

回退,可实现点击One组件的button按钮,返回到上个页面

  • replace

跳转,可实现点击One组件的button按钮,跳转到Two组件,没有历史记录,回退时只能直接返回到根路径/

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link :to="{name: 'one'}">one</router-link>
    <router-link :to="{name: 'two'}">two</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const One = {
        template: `
            <div>
                <button @click="handler('push')">push demo</button>
                <button @click="handler('back')">back demo</button>
                <button @click="handler('replace')">replace demo</button>
            </div>
        `,
        methods: {
            handler(type) {
                if (type === 'push') {
                    this.$router.push('/two');
                } else if (type === 'back') {
                    this.$router.back();
                } else if (type === 'replace') {
                    this.$router.replace('/two');
                }
            }
        }
    };
    const Two = {
        template: `<div>two component</div>`
    };
    const router = new VueRouter({
        routes: [
            {path: "/one", name: 'one', component: One},
            {path: "/two", name: 'two', component: Two}
        ]
    });
    new Vue({
        el: "#app",
        router
    })
</script>
</body>
</html>
导航守卫
  • 简介

所谓导航守卫,指的是在实现导航之前的拦截操作

  • 需求

所有页面在进入前需先进入登录页进行登录

  • 实现
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/other">other</router-link>
    <router-link to="/login">login</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Login = {
        template: `<div>Login Page</div>`
    };
    const Other = {
        template: `<div>Other Page</div>`
    };
    const router = new VueRouter({
        routes: [
            {path: '/login', component: Login},
            {path: '/other', component: Other}
        ]
    });
    /**
     * to:目标路由对象$route
     * from:来源路由对象$route
     * next():执行下一步
     * next(false):不执行
     * next('/one'):跳转到one组件页面
     */
    router.beforeEach((to, from, next) => {
        if ('/login' === to.path) next();
        else next('/login')
    });
    new Vue({
        router,
        el: "#app"
    })
</script>
</body>
</html>
  • 全局导航守卫
/**
 * 全局前置守卫:beforeEach
 * 调用阶段:导航被确认之前
 * 场景:一般用于登录拦截,如上例所示
 **/
const router = new VueRouter({ //... })
router.beforeEach((to, from, next) => { //... })

/**
 * 全局解析守卫:beforeResolve
 * 调用阶段:也是导航被确认之前,但是它同时在所有组件内守卫 以及 异步路由组件被解析,即(const a = () => import('...'))之后才被调用
 **/

/**
 * 全局后置守卫:afterEach
 * 调用阶段:导航被确认之后
 **/
router.afterEach((to, from) => { //无需next})
  • 单个路由独有的导航守卫
const router = new VueRouter({
    routes: [
        {
           path: '/foo',
           component: Foo,
           beforeEnter: (to, from, next) => { //... },
           //...
        }
    ]
})
  • 组件级别的导航守卫
const ONE = {
    template: `<div></div>`
    beforeRouteEnter(to, from, next) { 
        //进入组件路由之前触发
    },
    beforeRouteUpdate(to, from, next) { 
        //当组件被复用时触发
        //如先后调用了(http://...#/two/2)和(http://...#/two/3)
    },
    beforeRouteLeave(to, from, next) { 
        //当组件离开时触发,如跳转到其他组件
    }
}
动态路由和参数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/detail/1">iphone</router-link> <!--参数:1-->
    <router-link to="/detail/2">huawei</router-link> <!--参数:2-->
    <router-link to="/detail">xiaomi</router-link> <!--参数:-->
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Detail = {
        template: `<div>参数:{{ $route.params.id }}</div>`
    };
    const router = new VueRouter({
        routes: [
            {
                /**
                 * :id表示获取id参数,此时id为必须有的参数
                 * :id?此时id参数可以不传
                 */
                path: '/detail/:id?',
                component: Detail
            }
        ]
    });
    const vm = new Vue({
        router,
        el: '#app'
    })
</script>
</body>
</html>
路由-组件 传参
  • 方式一:$route
const One = {
    template: `<div>{{ $route.params.id }}</div>`
};
const router = new VueRouter({
    routes: [
        {
            path: '/one/:id',
            component: One,
        }
    ]
});
  • 方式二:props的布尔形式
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/test/100">click</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Com = {
        template: `<div>{{ id }}</div>`,
        props: ['id']
    };
    const router = new VueRouter({
        routes: [
            {
                path: '/test/:id',
                name: 'com',
                component: Com,
                props: true //将参数(id)作为组件的属性存在
            }
        ]
    });
    new Vue({
        router,
        el: "#app"
    });
</script>
</body>
</html>
  • 方式三:props的对象形式
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link :to="{name: 'com'}">click</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Com = {
        template: `<div>{{ message }}</div>`,
        props: ['message']
    };
    const router = new VueRouter({
        routes: [
            {
                path: '/test',
                name: 'com',
                component: Com,
                props: {message: 'a demo'} //将参数(id)作为组件的属性存在
            }
        ]
    });
    new Vue({
        router,
        el: "#app"
    });
</script>
</body>
</html>
  • 方式四:props的函数形式
routes: [
    {
        path: '/test',
        name: 'com',
        component: Com,
        props: to => {return {message: 'a demo'}} //将参数(id)作为组件的属性存在
    }
]
入口高亮
  • 说明

Vue预留了两个类,且被点击的入口将带有该类

  1. router-link-exact-active:精确匹配
  2. router-link-active:模糊匹配
  • 引用预留类
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .hl-color {
            color: hotpink;
        }
        .hl-fz {
            font-size: 72px;
        }
    </style>
</head>
<body>
<div id="app">
    <router-link to="/one">one</router-link>
    <router-link to="/two">two</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const One = {
        template: `<div>---one---</div>`
    };
    const Two = {
        template: `<div>---two---</div>`
    };
    const router = new VueRouter({
        routes: [
            {path: '/one', component: One},
            {path: '/two', component: Two}
        ],
        linkActiveClass: 'hl-color',
        linkExactActiveClass: 'hl-fz'
    });
    const vm = new Vue({
        router,
        el: '#app'
    })
</script>
</body>
</html>
  • 精确匹配和模糊匹配
  • 精确匹配

router-link-exact-active的匹配模式,即当url#后的hash值,如/one,等于to中的值,如:

<router-link to="/one">ONE</router-link>

,会加上该类

  • 模糊匹配

router-link-active的匹配模式,即当url#后的hash值,如/one,包含to中的值,如:

<router-link to="/">ONE</router-link>

中的/,会加上该类

  • 只允许精确匹配
<router-link to="/" exact>only exact</router-link>
$route
  • 说明

表示路由对象,是对location.hash以及路由实例new VueRouter({...})的封装

  • 常用属性

如有hash值:#/api/:id?age=23#aaa

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/api/1?age=23#aaa">fullPath</router-link> <!-- /api/1?age=23#aaa -->
    <router-link to="/api/2?age=23#aaa">hash</router-link> <!-- #aaa -->
    <router-link to="/api/3/?age=23#aaa">params1</router-link> <!-- {} -->
<!--    <router-link to="/api/3/?age=23#aaa">params2</router-link> &lt;!&ndash; { "id": "3" } &ndash;&gt;-->
    <router-link to="/api/4?age=23#aaa">path</router-link> <!-- /api/4 -->
    <router-link to="/api/5?age=23#aaa">query</router-link> <!-- { "age": "23" } -->
    <router-link to="/api/6?age=23#aaa">name</router-link> <!-- api_path -->
    <router-link to="/api/7?age=23#aaa">meta</router-link> <!-- {} -->
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const ONE = {
        template: `<div>{{ $route.fullPath }}</div>`
    };
    const TWO = {
        template: `<div>{{ $route.hash }}</div>`
    };
    const THREE = {
        template: `<div>{{ $route.params }}</div>`
    };
    const FOUR = {
        template: `<div>{{ $route.path }}</div>`
    };
    const FIVE = {
        template: `<div>{{ $route.query }}</div>`
    };
    const SIX = {
        template: `<div>{{ $route.name }}</div>`
    };
    const SEVEN = {
        template: `<div>{{ $route.meta }}</div>`
    };
    const router = new VueRouter({
        routes: [
            {path: '/api/1', component: ONE},
            {path: '/api/2', component: TWO},
            {path: '/api/3', component: THREE},
            // {path: '/api/:id', component: THREE},
            {path: '/api/4', component: FOUR},
            {path: '/api/5', component: FIVE},
            {name: "api_path", path: '/api/6', component: SIX},
            {path: '/api/7', component: SEVEN},
        ]
    });
    const vm = new Vue({
        router,
        el: '#app'
    })
</script>
</body>
</html>
  • 一个hash url对应一个路由对象
  • 元信息
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/test">click</router-link>
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Com = {
        template: `<div>component</div>`,
        created() {
            console.log(this.$route.meta.title);
            //应用举例
            document.title = this.$route.meta.title;
        }
    };
    const router = new VueRouter({
        routes: [
            {
                path: '/test',
                component: Com,
                meta: {title: 'test'}
            }
        ]
    });
    new Vue({
        el: "#app",
        router
    })
</script>
</body>
</html>
嵌套路由
  • 需求

点击Parent:显示Parent组件内容
点击Child:显示Parent包含着Child 的内容

  • 组件和出口的关系

出口<router-view>确定组件的显示位置
有几个出口,组件就显示几次

  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <router-link to="/parent">parent</router-link> <!-- 对应hash路径为 #/parent -->
    <router-link to="/child">child</router-link> <!-- 对应hash路径为 #/child -->
    <router-view></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Parent = {
        /**
         * <router-view/>用于显示children中相应的组件
         * #/child
         * parent content
         * child content
         * child content
         */
        template: `<div>parent content<router-view/><router-view/></div>`
    };
    const Child = {
        template: `<div>child content</div>`
    };
    const router = new VueRouter({
        routes: [
            {
                path: '/parent',
                component: Parent,
                children: [
                    {path: '/child', component: Child} //对应哈希路径为 #/child
                    // {path: 'child', component: Child} 注意,此时对应的哈希路径为 #/parent/child
                ]
            }
        ]
    });
    new Vue({
        router,
        el: "#app"
    })
</script>
</body>
</html>
命名路由
  • 场景

有时候通过一个名称来标识一个路由更加方便,特别是在链接一个路由,或者是执行一些跳转操作时

  • 实现
routes: [
    {path: '/one', name: 'one', component: One},
    {path: '/two', component: Two}
]
// -------------------------------
<router-link :to="{name: 'one'}">one</router-link> <!--冒号表示不解析为字符串-->
命名视图
  • 需求

当哈希路径为/时,同时显示三个组件(header, main, footer)

  • 实现
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!--
    效果:
    header组件
    header组件
    main组件
    footer组件
    -->
    <router-view></router-view> <!--无name时,则使用default对应的组件,即header组件-->
    <router-view name="header"></router-view>
    <router-view name="main"></router-view>
   <router-view name="footer"></router-view>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vue-router/dist/vue-router.js"></script>
<script>
    const Header = {
        template: `<div>header组件</div>`
    };
    const Main = {
        template: `<div>main组件</div>`
    };
    const Footer = {
        template: `<div>footer组件</div>`
    };
    const router = new VueRouter({
        routes: [
            {
                path: '/',
                components: {
                    default: Header,
                    header: Header,
                    main: Main,
                    footer: Footer
                }
            }
        ]
    });
    new Vue({
        router,
        el: "#app"
    })
</script>
</body>
</html>
重定向
  • 需求

当进入/时,重定向到/one组件

  • 实现

方式一:通过path属性

routes: [
    {path: '/', redirect: '/one'},
    {path: '/one', component: One}
]

方式二:通过name属性

routes: [
    {path: '/', redirect: {name: 'one'}},
    {path: '/one', name: 'one', component: One}
]

方式三:通过函数

routes: [
    {path: '/', redirect: to => { return {name: 'one'} }  },
    {path: '/one', name: 'one', component: One}
]

脚手架:vue-cli

说明

vue-cli,一个可以快速生成 vue 项目的脚手架工具,只要执行一条命令即可,且生成的项目不仅包含基本目录结构,同时还将 webpack 配置项全部配置好

安装与使用
npm i vue-cli -g
vue -V //查看的是脚手架 vue-cli 的版本号
vue init webpack 项目名称(vuecli_demo)
项目结构

在这里插入图片描述

build: "webpack配置文件目录"
config: "vue项目配置文件目录"
src: "源文件目录"
src/assets: "静态资源目录(参与打包)"
src/components: "组件目录"
src/router: "路由目录"
src/App.vue: "根组件"
src/main.js: [
    "1. 引入路由,创建Vue实例,并将路由挂载到实例中",
    "2. 引入根组件App.vue,通过使用 template 属性引用了该组件作为模板输出"
    "3. 同时有 el 和 template 属性,最终 template 会覆盖 el,但是 el 又不可删除,因为需要先用 el 进行占位"
    "4. import router from './router',一是 js 后缀可以省略,二是 index 名称可以省略,所以补全了应该是:'./router/index.js'"
]
static: "静态资源目录(不参与打包)"
static/.gitkeep: "git上传时,默认不会上传空文件夹,添加 .gitkeep 则可被上传"
.editorconfig: "编辑器配置,用于不同编辑器(vscode、webstorm等)的规范约定,如缩进数等,不过需要配合插件使用才能生效"
.eslintignore: "用于配置一些不需要进行 eslint 检测的文件"
.postcssrc.js: "处理 css 的配置文件"
index.html: "入口界面"
el & template & render & $mount
  • el & template
  1. el 指定了Vue的管理范围,template 是Vue的html模板;
  2. 当template不存在时,el 指定范围中的html内容将作为模板;
  3. el和template同时存在时,template将替换掉el中的内容,如下,显示的是child
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    abc
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    new Vue({
        el: "#app",
        template: `<div id="app">child</div>`
    })
</script>
</body>
</html>

或者

<script>
    const Child = {
        template: `<div id="app">child</div>`
    };
    new Vue({
        el: "#app",
        components: { Child },
        template: `<Child/>`
    })
</script>
  • el & $mount
    m o u n t 和 e l 一 样 都 是 指 定 挂 载 范 围 , 只 不 过 mount和el一样都是指定挂载范围,只不过 mountelmount需要手动挂载
const vm = new Vue({
    el: "#app",
    data: {}
});
//等价于
const vm = new Vue({
    data: {}
}).$mount("#app");
  • el & template & render

同时有 el 和 template 以及 render 时,将显示 render 的内容,即其优先级最高

const vm = new Vue({
    el: "#app",
    template: "<div>template内容</div>",
    render: function(createElement) {
         return createElement(组件名); //除了createElement标准用法,这里还可以直接用组件名
    }
});
main.js & index.html & App.vue
  • main.js & index.html
el: "#app" //可见mian.js中的el挂载的就是 index.html 中的 #app

二者间通过插件html-webpack-plugin产生关联关系,即在index.html中会引入main.js

  • main.js & App.vue
components: { App } //先将App作为局部组件引入
template: '<App/>' //再将App作为模板显示(template会替换掉el)
Vue的两种编译模式
  • 完整版(运行时 + 编译器)

除了可以使用render函数渲染组件,由于包含编译器,所以也可以直接使用template模板属性;


使用script标签引入的vue.js就是完整版

new Vue({
  template: '<div>{{ hi }}</div>'
});
  • 运行时版

vue.runtime.esm.js,只能使用render函数来渲染组件;


通过import Vue from 'vue'引入的即为运行时版本

new Vue({
  render (h) {
    return h('div', this.hi)
  }
});
  • 总结

当使用vue-loader或vueify的时候,*.vue文件内部的模板会在构建的时候预编译成JavaScript,在最终打好的包里实际上是不需要编译器的,所以只用运行时版本即可(vue-cli默认就是运行时版本)

脚手架3.0
//安装
npm i -g @vue/cli

//项目初始化
vue create 项目名称

//开发
npm run serve

//打包部署
npm run buiild

//添加 vuex
//1. 在src下创建 store/index.js (名字不限定)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
    state: () => {
        return {
            msg: "hello vuex"
        }
    }
});
export default store;

//2. 在main.js中进行引入挂载
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

//3. 在HelloWorld中使用
<template>
  <div class="hello">
    <p>{{msg}}</p>
    <button @click="fn">say</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data: () => {
    return {
      msg: "i want to say ..."
    }
  },
  methods: {
    fn() {
      this.msg = this.$store.state.commonMsg;
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .hello > p {
    color: aqua;
  }
</style>

Vuex

说明

Vuex是对组件的 data 的数据管理工具,在此之前,组件之间的数据通信是通过 如父传子、子传父 等方式进行的,vuex作为中间的数据管理,集中统一地管理项目中组件之间需要通讯的数据

基本使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vuex/dist/vuex.js"></script>
<script>
    /*创建vuex实例*/
    const store = new Vuex.Store({
        strict: true, //开启strict模式之后,只有在 mutations 中才能修改 state 中的数据
        state: { //相当于vue的data
            msg: "test"
        },
        mutations: { //相当于vue的methods
            changeMsg(stat, payload) { //mutations中的所有方法的第一个参数都是 state
                this.state.msg += payload.newMsg;
            }
        },
        getters: { //相当于vue的computed
        	test(){}
        }
    });
    //store.state.msg = "changed",strict模式已开启,无法直接修改
    store.commit('changeMsg', {newMsg: " changed"}); //使用mutations中的方法进行修改
    console.log(store.state.msg); //test changed
</script>
</body>
</html>
整合Vue
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h1>{{$store.state.num}}</h1>
    <button @click="fn">increment</button>
</div>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vuex/dist/vuex.js"></script>
<script>
    const store = new Vuex.Store({
        state: {
            num: 100
        },
        mutations: {
            numIncrement(state) {
                this.state.num += 1;
            }
        }
    });
    const vm = new Vue({
        store,
        el: "#app",
        data: {},
        methods: {
            fn() {
                this.$store.commit('numIncrement')
            }
        }
    })
</script>
</body>
</html>
Actions
  • 说明

类似于 mutations,不同之处在于Actions通过调用mutations中的方法,而不是直接自己去修改状态;另外Actions可以包含异步操作,而mutations中不能使用异步(会触发严格模式的校验而报错);还有就是 mutations通过commit触发执行,而actions则通过dispatch

  • 示例

store/index.js

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        commonMsg: "hello vuex",
        num: 100
    },
    mutations: {
        numIncrement(state, payload) {
            this.state.num += 1;
        }
    },
    actions: {
        asyncNumIncrement(context, payload) { //context相当于 store 实例
            setTimeout(function () {
                context.commit('numIncrement')
            }, 1000);
        }
    }
})

HelloWorld.vue

<template>
  <div class="hello">
    <p>{{msg}}</p>
    <p>{{$store.state.num}}</p>
    <button @click="fn">say</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data: () => {
    return {
      msg: "i want to say ..."
    }
  },
  methods: {
    fn() {
      this.msg = this.$store.state.commonMsg;
      this.$store.dispatch('asyncNumIncrement')
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .hello > p {
    color: aqua;
  }
</style>

辅助函数
mapGetters
  • 作用

简化 getters 的引用

  • 实现

将getters中的方法映射到具体组件的computed中,即当做组件的计算属性进行使用

  • 示例

store/index.js

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        commonMsg: "hello vuex",
        num: 100,
        date: new Date()
    },
    getters: {
        showDate(state) {
            /**
             * 注意这里不能用this.date,因为转到具体组件时,该组件不一定有date属性
             */
            return "date is: " + state.date
        }
    },
    mutations: {
        numIncrement(state, payload) {
            this.state.num += 1;
        }
    },
    actions: {
        asyncNumIncrement(context, payload) { //context相当于 store 实例
            setTimeout(function () {
                context.commit('numIncrement')
            }, 1000);
        }
    }
})

HelloWorld.vue

<template>
    <div class="hello">
        <p>{{msg}}</p>
        <p>{{$store.state.num}}</p>
        <p v-show="showDate">{{date}}</p>
        <button @click="fn">say</button>
    </div>
</template>

<script>
    import {mapGetters} from 'vuex'

    export default {
        name: 'HelloWorld',
        data: () => {
            return {
                msg: "i want to say ...",
                showDate: false
            }
        },
        computed: {
            ...mapGetters({"date": "showDate"})
        },
        methods: {
            fn() {
                this.msg = this.$store.state.commonMsg;
                this.$store.dispatch('asyncNumIncrement');
                this.showDate = !this.showDate;
            }
        }
    }
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .hello > p {
        color: aqua;
    }
</style>

mapMulations
  • 作用

简化 mutations 的引用

  • 实现

将 mutations 中的方法映射到组件的 methods 中

mapActions
  • 作用

简化 actions 的引用

  • 实现

将 actions 中的方法映射到组件的 methods 中

常搭配使用的其他框架/工具/插件

WebPack
介绍
  • 说明

一个前端模块化打包(构建)工具

  • 功能
  1. 依赖处理

可以 让模块更容易复用、避免全局注入导致的冲突、避免重复加载或加载不需要的依赖

  1. 代码合并

项目的模块化 以及 模块彼此之间的互相引用 都会导致文件分散问题,WebPack可以将分散的模块集中打包形成一个大文件,这样还能减少Http的链接请求次数,且打包时还会进行代码压缩,从而优化代码体积

  1. 各种插件

WebPack可以将一些浏览器不能直接运行的扩展语言,如SASS,LESS等转为可以识别的CSS, 且使用babel插件还可以将ES6+语法转为浏览器可以识别的ES5-语法

  • 工作原理
  1. 代码分析

WebPack会分析代码,找到require, import, define等关键字,然后将其替换为对应模块的引用

  1. 任务处理

WebPack会将项目视为一个整体,只要给定一个主文件(index.js),WPack就会从这个文件开始,找到项目所有的依赖文件,并使用loaders处理,最后打包成一个浏览器可识别的 js 文件

  • 四个核心概念
  1. entry(入口)

明确要打包哪个文件

  1. output(出口)

明确打包到哪里

  1. loader(加载器)

WebPack默认只能加载 js,通过 loader 可以加载其他文件

  1. plugin(插件)

处理加载器无法完成的功能

使用步骤
  1. 先进行项目初始化
npm init //逐步执行并生成,注意项目名称不能有汉字,也不能叫webpack
npm init -y //快速生成
  1. 安装 WebPack
npm i webpack webpack-cli -D
/*
 * -D:只需在开发阶段使用
 * webpack-cli:提供了一些可以在终端中使用的命令
 */
  1. 创建待打包文件,如 main.js
  2. 编写打包脚本

在项目初始化中生成的package.json中添加脚本内容

"script": {
    "build": "webpack main.js"
}
/*
 * 可以配置```mode```选项,它有两个取值
 * development:开发环境
 * production:生产环境(默认 & 会进行代码压缩)
 */
"script": {
    "build": "webpack main.js --mode production/development"
}
  1. 执行打包
npm run build //打包生成的目录为 dist,目录中就含有 main.js 文件
Vue的两种安装模式
ES6 转 ES5
  • 准备工作
cd demo
npm init -y
npm i webpack webpack-cli -D
npm i jquery
package.json
    "scripts":  { 
        "build": "webpack ./src/main.js --mode development"
    }
md src/
cd src
copy nul index.html
type nul main.js
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>隔行变色</title>
</head>
<body>
<ul>
    <li>***********</li>
    <li>***********</li>
    <li>***********</li>
    <li>***********</li>
    <li>***********</li>
</ul>
<script src="./main.js"></script>
</body>
</html>
  • src/main.js
import $ from 'jquery' //import 是 es6 语法
$('ul > li:odd').css("color", "hotpink");
  • 转换

直接查看 index.html 报错,浏览器无法识别 import 语句
执行:npm run build

  • 替换

index.html 引用 dict 的 main.js,此时再打开不报错并有效果了

  • babel
  • 说明

webpack自身只能处理 import/export 这些模块化语法,对于其他 js 新语法,则需要使用 babel 来转换

  • 安装

安装方式一

npm i babel-core babel-loader@7 -D
/*
 * babel-core:babel的核心包
 * babel-loader:用于加载 js 文件,并将其代码内容交给 babel-core 处理并解析为 es5- 的 js 文件
 */

安装方式二

npm i babel-preset-env babel-preset-stage-2
/*
 * babel-preset-*:指定解析什么版本的语法
 * babel-preset-env:能够解析 es2015、16、17、18 这几个标准的 js 语法
 * babel-preset-stage-2:可以解析暂时未被采纳为标准的语法,如:
 *     'abc'.padStart(10, '6'):表示在原有字符串'abc'的基础上,将字符数扩充到10个,不足部分在'abc'前面用6补充
 * babel-polyfill和babel-plugin-transform-runtime:也是用来做兼容处理的
 */
  • .babelrc

需要在根路径创建这个文件,作为 babel 的配置文件

{
  "presets": ["env", "stage-2"]
}

然后,可以在 main.js 中编辑些浏览器不支持的 es6 语法做校验

let obj = {
    name: "xiaoming",
    age: 12
}
let o = { ...obj }; // edge浏览器不支持
webpack.config.js

如,对于打包命令的编写,可以在 package.json 中编写命令

/*
 * webpack 入口 --output 出口
 */
webpack ./src/main.js --output ./dist/app.js --mode development

此外,如果指写明 webpack,则会自动查找并执行根目录中默认的脚本文件webpack.config.js

// package.json
"scripts": {
  "build": "webpack"
}

// webpack.config.js
const path = require('path');
module.exports = {
    //指定入口
    entry: path.join(__dirname, './src/main.js'),
    //指定出口
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'app.js'
    },
    //指定模式
    mode: 'development'
};

执行npm run build即可

插件:html-webpack-plugin
  • 作用

可以根据指定的模板文件(index.html),自动生成一份新的 index.html,并注入到 dist 目录中,这个新的 index.html 会自动引入入口文件 main.js,即无需在 index.html 中做任何引入了

  • 使用
//1. 安装
npm i html-webpack-plugin -D

//2. 在 webpack.config.js 中进行引入
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: path.join(__dirname, './src/main.js'),
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'app.js'
    },
    mode: 'development',
    plugins: [
        new htmlWebpackPlugin({
            template: path.join(__dirname, './src/index.html')
        })
    ]
};
插件:webpack-dev-server
  • 作用

为使用 webpack 打包提供一个服务器环境,它会将打包好的文件放到该服务器中,并自动开启服务,同时监控 src 中的源文件变化,然后在服务器中同步这些变化,并自动刷新浏览器

  • 使用
// 1. 安装
npm i webpack-dev-server -D

// 2. 配置
/* 命令行方式配置 :注意和 webpack 版本适配*/
/*
 * --open:自动刷新浏览器
 * --hot:热更新,主要用在 css 上
 */
"scripts": {
    "build": "webpack", //发布用
    "dev": "webpack-dev-server --open --port 56789" //开发用
}

/* webpack.config.js方式配置 */
// package.json
"scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
}
// webpack.config.js
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: path.join(__dirname, './src/main.js'),
    output: {
        path: path.join(__dirname, './dist'), //会自动创建dist目录
        filename: 'app.js'
    },
    mode: 'development',
    plugins: [
        new htmlWebpackPlugin({
            template: path.join(__dirname, './src/index.html')
        })
    ],
    devServer: {
        open: true,
        port: 56789
    }
};
处理非 js 文件
  • 处理 css

项目结构

src/
src/assets
src/assets/demo.css
src/index.html
src/main.js

demo.css
ul {
    list-style: none;
}

main.js
import $ from 'jquery'
import './assets/demo.css'

$('ul > li:odd').css("color", "hotpink");

安装
npm i style-loader css-loader -D
/*
 * css-loader:用于读取 css 文件内容,并放到一个模块中
 * style-loader:用于创建 style 标签,将模块中的内容加载进来,然后再将该标签插入到页面中
 */

引入
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: path.join(__dirname, './src/main.js'),
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'app.js'
    },
    mode: 'development',
    plugins: [
        new htmlWebpackPlugin({
            template: path.join(__dirname, './src/index.html')
        })
    ],
    devServer: {
        open: true,
        port: 56789
    },
    module: {
        rules: [
            {
                //test表示正则匹配,整个代码表示如果文件后缀为.css,用'style-loader'和'css-loader'这两个loader去处理
                //读取顺序:从后向前读取,即先读取css-loader,再读取style-loader
                test: /\.css$/, use: ['style-loader', 'css-loader']
            }
        ]
    }
};

热更新:--hot
即局部更新,如在 css 文件中更新了背景,这时页面也只会更新该背景
  • 处理 less
npm i less -g //less-loader需要依赖less
npm i style-loader css-loader less-loader -D

use: ['style-loader', 'css-loader', 'less-loader']
  • 处理 图片

安装

npm i url-loader file-loader -D

配置

/*
 * 默认会将图片转为 base64 格式(大图不适合用base64处理),
 * 将图片文件转换为base64编码并载入浏览器能够减少http请求数,
 * 但是增大了js或html文件的体积,
 * limit表示在多少字节内进行base64编码,默认10000byte,大约10kb
 * 另外如果使用base64:url-loader发挥作用
 * 否则:file-loader发挥作用(将图片名称进行了md5编码)
 */
{
    test: /\.(jpg|gif|png)$/, use: ["url-loader?limit=100000\""]
}
//或者
{
    test: /\.(jpg|gif|png)$/, use: {
        loader: 'url-loader',
        options: {
            limit: 100000
        }
    }
}

demo.css

ul {
    list-style: none;
    background: url("./demo.png");
}
  • 处理 字体图标
1.拷贝iconfont文件到assets中
2.import .../iconfont.css
3.使用<i class="iconfont icon-zan"></i>
项目打包及其优化
  • dist

打包好的文件都在 dist 目录中,需要注意的是其中的 index.html虽然是入口页面,但是不能直接打开,需要以服务文件的方式去访问,如可以使用 http-server

  • 打包目录说明
vendor: "第三方插件目录"
app.js: "对应源文件 main.js"
manifest: "主要用于处理依赖关系"
map文件: "便于查找错误,因为打包的代码经压缩后,浓缩为一行,而通过这些 map 文件,可以定位到具体的出错位置 "
index.html: "项目入口页面"
  • 打包优化
  • 优化一:降低 app 模块的大小

app.js是首先打开的入口加载文件,其大小决定了首屏加载速度,所以需要尽可能减少在 app.js 中引入其他模块,并且可以使用懒加载的方式引入

//原来
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/login/Login.vue'
import Home from '../components/home/Home.vue'
import Users from '../components/users/Users.vue'
import Roles from '../components/roles/Roles.vue'
import Rights from '../components/rights/Rights.vue'
import Category from "../components/category/Category.vue";
import Goods from "../components/goods/Goods.vue"

Vue.use(VueRouter);

//优化后
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/login/Login.vue'
const Home = () => import('../components/home/Home.vue'); //将组件定义为异步组件
const Users = () => import('../components/users/Users.vue');
const Roles = () => import('../components/roles/Roles.vue');
const Rights = () => import('../components/rights/Rights.vue');
const Category = () => import("../components/category/Category.vue");
const Goods = () => import("../components/goods/Goods.vue");
//-----------------------------只需要登录页,其他的重定向即可
const router = new VueRouter({
    routes: [
        //重定向
        {path: '/', redirect: '/login'},
        {path: '/login', name: 'login', component: Login},
        {path: '/home', name: 'home', component: Home, children: [
                {path: '/users/:page?', name: 'users', component: Users},
                {path: '/roles', name: 'roles', component: Roles},
                {path: '/rights', name: 'rights', component: Rights},
                {path: '/categories', name: 'categories', component: Category},
                {path: '/goods', name: 'goods', component: Goods},
                {path: '/goods-add', name: 'goods-add', component: GoodsAdd}
        ]}
    ]
});
//-----------------------------
  • 优化二:降低 vendor 体积大小

vendor的体积较大,所以可以将本地一些依赖换成在线服务器地址
如 vue 和 依赖vue-router

//1. index.html中进行引入
<script src="cdn地址"></script>

//2.build/webpack.base.conf.js中添加 externals 配置项(在resolve前面)
externals: {
    /*
     *key: 包名(import导包时from跟着的即为包名)
     *value: 插件的全局变量名(可以查看cdn在线文件中的function(e, t),第一个参数为window,看到e.V**,即大写开头的即为全局变量名称)
     */
    vue: "Vue",
    axios: "axios",
    'vue-router': "VueRouter",
    'element-ui': "ELEMENT"
}
Axios
说明
  • 简介

一个发送ajax的工具库,基于Promise,是对ajax的封装,可以在浏览器或node中使用

  • 安装
npm i axios
  • 引入
//node
const axios = require('axios');

//浏览器
<script src="**/axios.js"></script>
语法
axios.get/post/delete/put/patch(url [, config, data]).then(res => { //... });
/**
 * then:请求成功时会调用此方法,参数res为请求获得的数据
 * config:非必填,是个对象
 *        params:请求参数,是个对象,get请求时会用到
 *        headers:请求头,是个对象
 * data:非必填,请求体对象,如post请求时会用到
 */
Vue整合
//main.js
Vue.prototype.$axios = axios; //方便调用
//此外还可以在main.js中配置拦截器,以统一设置token之类的
插件
vue-quill-editor:富文本编辑器
element-tree-grid:树形结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值