vue笔记

项目搭建

一.Vue

  1. Vue (读音 /vjuː/,类似于 view),不要读错。

  2. Vue是一个渐进式的前端框架,什么是渐进式的呢? VUE全家桶

    1. 渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统。比如Core+Vue-router+Vuex+axios,也可以满足你各种各样的需求。
  3. Vue的特点和Web开发中常见的高级功能:

    1. 解耦视图和数据
    2. 双向数据绑定
    3. 可复用的组件
    4. 前端路由技术
    5. 状态管理
    6. 虚拟DOM

注意:学习Vue框架,必须严格遵循他的语法规则,Vue代码的书写思路和以前的JQuery完全不一样。所以,在项目中使用Vue之前,必须先学习Vue的基本语法规则。

二.语法格式

1. v-bind指令

<div id="app">
    <!-- 
        v-bind: 指令==>  简写  :    是Vue提供给我们的一个标签属性指令
        在标签属性中,想要使用data中的数据的话,需要在标签属性名前面加上 v-bind:  或者 :
     -->
    <a v-bind:href="url1">百度一下</a>
	<a :href="url2">淘宝一下</a>
</div>
new Vue({
        el:'#app',
        data :{
            url1 : 'http://www.baidu.com',
            url2 : 'http://www.taobao.com'
        }
    })

2.v-on指令

/*  v-on:   简写  @     ==>事件指令
   	语法: @事件类型='事件处理程序'*/
  1. 修饰符

    1. .stop —> 可以阻止事件冒泡,相当于调用 event.stopPropagation()
    2. .prevent —> 可以阻止事件默认事件, 相当于调用 event.preventDefault()
    3. {keyCode | keyAlias} — 值当事件是从特定键触发是才触发回调
    4. .native ---- > 监听组件根元素的原生事件
    5. .once -----> 只触发一次回调
    <button @click.stop="btnclick">按钮</button>  // 可以阻止事件冒泡
    <input type="submit" value="提交" @click.prevent="submitclick">  // 可以阻止事件的默认事件
    <input @keyup.enter="onEnter">   // 键修饰符, 键别名
    <input @keyup.13="onEnter">      // 键修饰符, 键代码
    <button @ click.once=""></button>  // 点击回调只会触发一次
    <button @click.stop.prevent="btnclick"></button>
    
  2. 传参问题

    1. 如果该方法不需要额外参数,那么方法后的()可以不添加
    2. 如果方法中本身中有一个参数,那么会默认将原生事件event参数传递进去
    3. 如果需要同时传入某个参数和event时,可以通过 $event 传入事件

3.Vue控制类名

<!-- :class的值可以是一个对象,键名就是类名,值就是布尔值,为true则有这个类,为false则没有这个类 -->
<div :class="{current:bool1,active:bool2}">文字1</div>
<!-- :class的值可以是一个三元运算符,通过条件的结果来确定有没有这个类 -->
<div :class="bool3 ? 'current' : '' ">文字2</div>
<!-- :class的值可以是一个数组,每一个元素都可以是一个字符串,都是类名(少用) -->
<div :class="['current','active']">文字3</div>

4.Vue控制行内样式

<div style="font-size: 20px;">文字1</div>
<!-- :style="" 的值可以是一个对象,key就是属性名,值就是属性值 -->
<div :style="{color:'red',fontSize:'30px'}">文字2</div>
<!-- 可以使用三元表达式把它写活 -->
<div :style="{color:bool1 ? 'green' : 'red' ,fontSize :'50px'}">文字3</div>
<!-- 跟数组,跟跟对象的区别不大,唯一不同的是可以跟多个对象(很少用) -->
<div :style="[{color : bool2 ? 'pink' : 'red'},{fontSize : '40px'}]">文字4</div>

5.v-if 和 v-show指令

v-if 指令控制标签的显示和隐藏,后面跟一个布尔值,true为显示,false为隐藏(会对元素进行DOM删除)
v-if  v-if-else  v-else  if -- else 中间不能跟其他标签,不然后面的就不会再进行判断
v-show 只是对样式层面上的显示和隐藏,同样是跟一个布尔值,区别是不会对元素进行DOM删除
如果需要对一个元素频繁的进行显示和隐藏就用 v-show ,这样子对浏览器的性能要好一点

6.v-for指令

<!-- v-for 遍历数组 -->
<ul>
    <!-- arr是一个数组,item是数组的每一个元素,index是数组每一个元素对应的索引(下标) -->
    <li v-for="(item,index) in arr">{{index}}. {{item}}</li>
</ul>

<!-- v-for 遍历对象 -->
<ul>
    <!-- obj 是一个对象,item是一对象的每一个值, key是对象的每一个键 -->
    <li v-for="(item,key) in obj">{{key}} : {{item}}</li>
</ul>

<!-- v-for 遍历数字 -->
<ul>
    <li v-for="item in 6">{{item}}</li>
</ul>

7.双向数据绑定

  1. 修饰符

    1. lazy: 可以让数据在失焦或者回车的时候才会更新
    2. number: 默认输入框输入的不管是字母还是数字,都会被当做字符串进行处理,number修饰符可以让输入框中输入的内容自动转成数字类型
    3. trim: 可以让过滤内容左右的两边的空格
    <input type="text" v-model.lazy="val">
    <input type="text" v-model.number="val">
    <input type="text" v-model.trim="val">
    多个修饰符继续点
    <input type="text" v-model.number.trim.lazy="val">
    
<div id="app">
    <!-- 
        v-model 专门给表单元素使用
        注意点: 1. v-medol的值是data中属性名,这个属性的值,可以理解为表单元素的value的值
        2. 一旦用户输入了数据,实际上是在修改了 val 的值,所以页面上引用到val的地方全部发生改变
    -->
    <input type="text" v-model="val">
    <p>{{val}}</p>

    <!-- 用户选择其实是在改变 selval的值,改变后, 就隐形了p标签的内部内容 -->
    <select v-model="selVal">
        <option value="bj">北京</option>
        <option value="gz">广州</option>
        <option value="sh">上海</option>
    </select>
    <p>{{selVal}}</p>
</div>
new Vue({
    el :'#app',
    data :{
        val:'初始值',  // 这是一个初始值
        selVal:'gz'
    }
})

8.其他一些指令

  1. v-text : 和{{}}差不多
  2. v-html : 可以解析HTMl标签
  3. v-pre : 让浏览器不解析{{}}
  4. v-cloak : 在夹杂的过程让用户不会 看到{{}}语法
    1. css样式 : [v-cloak]{display : none}
<div id="app">
    <p>{{str1}}</p>
    <!-- v-text 跟{{}}差不多 -->
    <p v-text="str1"></p>
    <!-- v-html 可以解析标签 -->
    <p v-html="str1"></p>
    <!-- v-pre 可以让浏览器不解析胡子语法 -->
    <p><span v-pre>{{num}}</span> 的值为: {{num}}</p>

    <!-- v-cloak  在网速比较慢的时候让用户看不见胡子语法 -->
    <p v-cloak>{{str1}}</p>
</div>
new Vue({
        el : '#app',
        data : {
            str1 : '<strong>我是一个strong标签</strong>',
            num : 10
        }
    })
[v-cloak]{
            display: none;
        }

9.Vue中的MVVM

  1. Model层:

    数据层,数据可能是我们自定定义的数据,或者是从网络请求下来的数据;

  2. View层

    视图层,在前端里就是我们常说的DOM层,主要作用是给用户展示各种信息;

  3. ViewModel层:

    视图模型层,是View层和Model层沟通的桥梁;一方面它实现了数据绑定(Data Binding),将Model的改变实时反应到View中;另一方面它实现了DOM监听,当DOM发生改变可以对应改变数据(Data)

10. Vue中的计算属性

  1. computed 使用: 当一个值受到其他值的影响,就可以写到计算属性中
  2. 在Vue中写在computed中的这些方法被看成属性,不需要调用,只需要写名字就行执行(不需要小括号)
  3. 计算属性有缓存效果.普通方法没有缓存效果,比较消耗性能
  4. 传参问题,计算属性返回一个函数就可以向计算属性传递参数

11.使用:value和@input代替v-model

v-model 本质上包含了两个操作:

  1. v-bind 绑定input元素的value属性

  2. v-on 指令绑定input元素的input事件

    <input type="text" v-model="textVal"/>
    <!-- 等同于 -->
    <input type="text" v-bind:value="textVal" v-on:input="textVal = $event.target.value"/>
    

13.过滤器

  1. 局部过滤器

    <div id="app">
        <!-- num ===> 是我们的数据   RMBFormat ===> 是过滤器的名字    -->
        <h1>价格为:{{num | RMBFormat}}</h1>
    </div>
    
    // 过滤器的作用: 让数据以一定的格式渲染到页面上展示,这个格式是可以复用的
    filters: {
        RMBFormat(value){  // value 接收  |  前面的值 
            return '¥' + value.toFixed(2) + '元';
        }
    }
    
  2. 全局过滤器

    // 全局过滤器(其他组件都可以使用,因为一次就只能创建一个格式,所以不用加 s )
    Vue.filter('RMBFormat', value=>{   // value 接收 | 前面的数据
        return '¥' + value.toFixed(2)
    })
    

14.本地存储

  1. localStorage

    1. 除非是主动删除,不然是不会自动删除的
    2. 默认是以字符串的形式保存
    3. 一般浏览器存储的大小是5M
    4. 5M = 1024 * 5kb
    // 本地存储localStorge数据(是以键值对的形式存储)
    localStorage.setItem('name','Vue');
    localStorage.setItem('age',7);
    
    // 获取localstorage数据(是以键名的方式获取)
    console.log(localStorage.getItem('name'));
    
    // 删除localStorage数据
    localStorage.removeItem('name');      // 指定删除
    localStorage.clear();     // 会清空所有本地储存的数据
    
  2. sessionStorage

    1. 关闭浏览器会自动清空数据
    2. 一般浏览器存储的大小是5M
    // 临时存储浏览器关闭之后会清空
    sessionStorage.setItem('name','Vue');   // 临时存储
    sessionStorage.setItem('age',7);
    
    console.log(sessionStorage.getItem('name'));   // 获取临时存储的数据
    
    // sessionStorage.removeItem('name');    // 指定删除临时存储的数据
    sessionStorage.clear();   // 删除全部的临时储存的数据
    
  3. 本地存储注意的点

     let obj = {
         name: "Vue",
         age: 7,
         job: 'web开发'
     }
     // 保存引用类型数据(对象,数组),需要用JSON.stringify()转成字符串储存
     localStorage.setItem('obj',JSON.stringify(obj)); 
    // 获取的使用再使用JSON.parse()转成对象,这样子才可以使用 . 语法来获取 
    console.log(JSON.parse(localStorage.getItem('obj')));
    let myobj = JSON.parse(localStorage.getItem('obj'));
    console.log(myobj.name);
    console.log(myobj.age);
    
  4. cookie

    1. 4.1. cookie
      1. 网站中,http请求是无状态的。也就是第一次登陆成功(发送请求),第二次请求服务器依然不知道是哪一个用户。这时候的cookie就是解决这个问题的,第一次登陆后服务器返回数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求,浏览器自动会把上次请求存储的cookie数据自动带上给服务器,服务器根据客户端的cookie来判断当前是哪一个用户。cookie存储有大小限制,不同浏览器不一样,一般是4kb,所以cookie只能存储小量数据。
      2. 4kb = 4 * 1024 byte (字节) = 4 * 1024 * 8 bit(位)
      3. token: 用户登录凭证,服务端返回给浏览器(前后端分离项目,基本都是发送ajax请求)
    2. 4.2 session
      1. session和cookie的作用有点类似,也是存储用户相关信息。不同的是cookie存储在浏览器,而session存储在服务器。

15.深入双向数据绑定

  1. vue框架特点:双向数据绑定与组件化开发

  2. 深入双向数据绑定

    1. Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。Vue里面是怎么做到的的呢?其实就是使用了Object.defineProperty 把Vue内的属性全部转成 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
    2. Object.defineProperty 实现了对象劫持这个功能
    3. vue双向数据绑定原理:
      1. 借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定

    语法:

    Object.defineProperty(obj, prop, desc)

    1. obj 需要定义属性的当前对象
    2. prop 当前需要定义的属性名
    3. desc 属性描述符

    数据属性:

    通过Object.defineProperty()为对象定义属性,有两种形式,分别为数据描述符,存取描述符,下面分别描述两者的区别:

    1. value 表示它的默认值
    2. writable 如果为true标识可以被修改,如果为false标识不能被修改(默认值为false)
    3. configurable 描述属性是否配置,以及可否删除,可以认为是总开关 默认值 false(不可删除)
    4. enumerable 描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)
    <input type="text" id="ipt">
    <p id="pp"></p>
    
    <script>
        // 原生实现双向数据绑定
        // 原理:借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式来实现双向绑定数据
        let obj = {};
    	let val = '初始值';
    Object.defineProperty(obj,'inpVal',{
        get(){
            return val;
        },
        set(newValue){
            // 当obj.iptVal发生改变的时候,界面中涉及到的数据也都要发生改变
            // 利用了Vue的思想,数据一改,界面上的数据也会发生改变
            ipt.value = newValue;
            pp.innerHTML = newValue;
        }
    })
    // 将val的值给到页面上面的两个元素
    ipt.value = obj.inpVal;
    pp.innerHTML = obj.inpVal;
    
    // 注册事件
    ipt.addEventListener('input',function(e){
        // 修改obj.inpVal的值
        obj.inpVal = e.target.value;   // 修改了inpVal的值,触发了set()
    })
    </script>
    

三.组件化

  1. 什么是组件化

    面对复杂问题的处理方式,把问题拆解成很多个能处理的小问题,再将其放在整体中,会发现大的问题也会迎刃而解。

    而组件化的思想也类似:

    1. 如果我们实现一个页面结构和逻辑非常复杂的页面时,如果全部一起实现会变得非常复杂,而且也不利于后续的维护和迭代功能。
    2. 但如果我们这时候把页面分成一个个小的功能块,每个功能块能完成属于自己这部分独立的功能,那么整个页面之后的维护和迭代也会变得非常容易。

    组件化开发的优势:可维护性高 可复用性高

  2. Vue组件化思想

    1. 组件化是Vue重要的思想
      1. 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
      2. 任何的应用都会被抽象成一颗组件树。
    2. 组件化思想的应用开发:
      1. 有了组件化的思想,我们在之后的开发中就要充分的利用它。
      2. 尽可能的将页面拆分成一个个小的、可复用的组件。
      3. 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
  3. 全局组件

    1. 通过Vue.component('组件名称', {}),通过这个方法注册的都是全局组件,也就是他们再注册之后可以用在任何新创建的Vue 实例挂载的区域内。
  4. 局部组件

    1. 通过 Vue.component 方式注册的组件,称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加
  5. 那组件如果要使用data定义自己属性保存数据要怎么做呢?

    1. 组件对象也有一个data的属性(也有methods等属性,下面我们有用到)
    2. 只是这个data属性必须是一个函数,而且函数返回一个对象 ,对象保存着数据
  6. 为什么data在组件中必须是一个函数呢?

    1. 原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

1. 组件之间的通讯

1.1父传子
  1. 在组件中,使用选项props来声明需要从父级接收到的数据。
  2. props的值有两种方式:
    1. 字符串数组,数组中的字符串就是传递时的名称。
    2. 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
<div id="app">
    <cnp :cstars="stars" :cmessage="message"></cnp>
</div>

<template id="cnp">
    <div>
        <ul>
            <li v-for="item in cstars">{{item}}</li>
        </ul>
        <h2>{{cmessage}}</h2>
    </div>
</template>
// 子组件
// 父传子(props属性)
const cnp1 = {
    template:'#cnp',
    // 1.以数组的形式(用的比较少)
    // props:['cstars','cmessage'],
    // 2. 以对象的形式传递
    props:{
        // 2.1 类型限制
        // cstars:Array,
        // cmessage:[String,Array],

        // 2.2 提供一些默认值,以及必传值
        cmessage:{
            type:String,
            default:'啊啊啊啊啊', 
            required: true
        },
        // 类型是对象或者数组时,默认值必须是一个函数
        cstars:{
            type:Array,
            default(){
                return []
            }
        }
    }
}

// 父组件
new Vue({
    el:'#app',
    data:{
        stars:['杨幂','白百何','迪丽热巴','赵丽颖'],
        message:'你好啊',
    },
    components:{
        cnp:cnp1
    }
})
1.2 子传父

父组件向子组件传递数据,通过自定义事件

<body>
    <!-- 父组件模板 -->
    <div id="app">
        <cnp @itemclick="cnpclick"></cnp>
    </div>

    <!-- 子组件模板 -->
    <template id="cnp">
        <div>
            <button v-for="item in categories"
                    @click="btnclick(item)">
                {{item.name}}
            </button>
        </div>
    </template>
</body>

</html>
<script>
    // 子组件
    const cnp1 = {
        template: '#cnp',
        data() {
            return {
                categories: [
                    { id: 'aaa', name: '热门推荐' },
                    { id: 'bbb', name: '手机数码' },
                    { id: 'ccc', name: '家用家电' },
                    { id: 'ddd', name: '电脑办公' },
                ],
            }
        },
        methods:{
            btnclick(item){
                // 向父组件传递数据
                // this.$emit('事件名称',参数)
                this.$emit('itemclick',item)
            }
        },
    }
    // 父组件
    new Vue({
        el: '#app',
        data: {

        },
        components: {
            cnp: cnp1
        },
        methods:{
            cnpclick(item){
                console.log(1111,item);
            }
        },
    })
</script>
1.3 兄弟之间的通讯
<body>
    <div id='app'>
        <child1></child1>
        <child2></child2>
    </div>
    <!-- 
         兄弟组件的传值问题的解决方案
            1. 子传父,父传子(比较麻烦)
            2. Vuex 状态管理
            3. 中央事件总线(一个空的Vue对象)
      -->

    <template id="tml1">
        <div>
            <button :style="{backgroundColor:bgc}">按钮1</button>
        </div>
    </template>

    <template id="tml2">
        <div>
            <button @click="change">按钮2</button>
        </div>
    </template>
    <script>
        let bus = new Vue();  // 1. 创建一个空Vue对象
        let child1 = {
            template: '#tml1',
            data() {
                return {
                    bgc: 'pink',
                }
            },
            created() {
                // 组件创建完毕时就会执行这里的代码
                // 步骤2 通过bus来开启事件监听,监听有没有其他组件来修改我的bgc数据
                // 定义了一个自定义事件,提供可修改bgc的方案
                // bus.$on("事件名称", 事件触发时候要执行的函数)
                bus.$on('changebgc',val=>{
                    this.bgc = val;
                })
            }
        }

        let child2 = {
            template: '#tml2',
            data(){
                return {
                    bgc :'blue'
                }
            },
            methods:{
                change(){
                    // 3. 通过bus触发自定义事件
                    bus.$emit('changebgc',this.bgc)
                }
            }
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                child1, child2
            }
        })
    </script>
</body>
1.4 多层级之间的通讯
  1. 父级组件中: 父节点的组件可以通过 provide 方法, 对其子孙组件共享数据
export default {
    data(){
        return {
            color: 'red'   // 1. 定义'父组件'要向 '子孙组件' 共享的数据
        }
    },
    provide(){  // 2. 在 provide 函数 return 的对象中, 包含了要向 子孙组件 共享的数据
        return{
            color: this.color
        }
    }
}
  1. 子孙节点通过 inject 数组, 接收父级节点向下共享的数据
<template>
    <h1>
        子孙组件 ---- {{ color }}
    </h1>
</template>

<script>
    export default {
     	// 子孙自减, 使用 inject 接收父节点向下共享的 color 数据, 并在页面上使用
        inject: ['color']
    }
</script>

2.插槽

  1. 匿名插槽: slot 标签没有name属性的就是匿名插槽

  2. 具名插槽

    1. 在默认情况下,不展示(不渲染)子组件中标签中的内容
    2. 如果想展示子组件中的标签中的内容,需要在子组件中模板中使用 slot 标签来接收
    3. slot 标签出现的位置,就是这些内容出现的位置
    4. slot 标签属性可以用来和上面标签的 slot 属性做对应,标签标签 可以放到对应的slot放到对应的slot的位置上
    5. 有name属性的slot标签成为具名插槽
    6. <slot name="myslot"></slot>
  3. 插槽作用: 提高组件复用性,提高组件的扩展性

  4. 作用域插槽

    1. 作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。
  5. 作用于插槽的四种写法

    <div id='app'>
        <!-- 第一种写法: 直接写在标签上面 -->
        <child>
            <button slot="btn" slot-scope="scope">{{scope.mymessage}}</button>
        </child>
    
        <!-- 第二种写法: 写在template标签上面 -->
        <child>
            <template slot="btn" slot-scope="scope">
                <div>
                    <p>{{scope.mynum}}</p>
                    <button>{{scope.mymessage}}</button>
                </div>
            </template>
        </child>
    
        <!-- 第三种写法:指令写法 -->
        <child v-slot:btn="scope">
            <button>{{scope.mymessage}}</button>
        </child>
    
        <!-- 第四种写法: -->
        <child>
            <template v-slot:btn="scope">
                <div>
                    <P>{{scope.mynum}}</P>
                    <button>{{scope.mymessage}}</button>
                </div>
            </template>
        </child>
    </div>  
    
    <template id="tmp">
        <div>
            <h1>我是组件</h1>
            <slot name="btn" :mynum="num" :mymessage="message"></slot>
        </div>
    </template>
    <script>
        let child = {
            template:'#tmp',
            data(){
                return {
                    num:99,
                    message:'按钮'
                }
            }
        }
        new Vue({
            el:'#app',
            data:{
    
            },
            components:{
                child
            }
        })
    </script>
    

四.webpack

从本质上来说,webpack是一个静态模块打包工具

要想让我们写好的模块化代码在各式各样的浏览器上能做到兼容,就必须借助于其他工具;而webpack的其中一个核心就是让我们可以进行模块化开发,并帮我们处理模块间的依赖关系。不仅仅是Javascript文件,我们的css、图片、json文件等在webpack中都可以当作模块来使用,这就是webpack的模块化概念。

  1. gulp和webpack

Gulp侧重于前端开发的 整个过程 的控制管理(像是流水线),我们可以通过给gulp配置不同的task(通过Gulp中的gulp.task()方法配置,比如启动server、sass/less预编译、文件的合并压缩等等)来让gulp实现不同的功能,从而构建整个前端开发流程。

gulpfile.js

var gulp = require('gulp');
var uglify = require('gulp-uglify'); //压缩代码

// 压缩js
gulp.task('uglify',function(){
    var combined = combiner.obj([
        gulp.src('src/scripts/**/*.js'), //需要压缩的js文件路径
        sourcemaps.init(),
        uglify(), //压缩js
        sourcemaps.write('./'),
        gulp.dest('dest/scripts') //生成的js文件的目录
    ]);
});

//默认任务
gulp.task('default',['uglify']);

Webpack有人也称之为 模块打包机 ,由此也可以看出Webpack更侧重于模块打包,当然我们可以把开发中的所有资源(图片、js文件、css文件等)都可以看成模块,最初Webpack本身就是为前端JS代码打包而设计的,后来被扩展到其他资源的打包处理。Webpack是通过loader(加载器)和plugins(插件)对资源进行处理的。

五.创建项目

1. 创建项目准备工作

Node版本

Vue CLI 需要Node.js 8.9 或者更高版本(推荐使用 12.11.0)。你可以使用nvmnvm-window来管理电脑上面的Node版本。

安装Vue CLI脚手架的包:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

安装之后,你就可以在命令行中访问 vue 命令。你可以通过简单运行 vue,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。

你还可以用这个命令来检查其版本是否正确 (4.x):

vue --version
vue -V

如果安装比较慢,可以把下载源切换成淘宝的源:

npm 对应的淘宝下载源设置:

//切换taobao镜像源
npm config set registry https://registry.npm.taobao.org/
// 查看下载源
npm config get registry

yarn 对应的淘宝下载源设置:

//切换taobao镜像源
yarn config set registry https://registry.npm.taobao.org/

// 查看下载源
yarn config get registry

2. 初始化项目

运行以下命令来创建一个新项目:

vue crate 文件名
vue create hello-world

对于每一项的功能,此处做个简单描述:

  • TypeScript 支持使用 TypeScript 书写源码。
  • Progressive Web App (PWA) Support PWA支持
  • Router 路由
    Vuex 状态管理
    CSS Pre-processors 支持 CSS 预处理器。
    Linter / Formatter 支持代码风格检查和格式化。
    Unit Testing 支持单元测试。
    E2E Testing 支持 E2E 测试。

如果你决定手动选择特性,在操作提示的最后你可以选择将已选项保存为一个将来可复用的 preset

~/.vuerc

被保存的 preset 将会存在用户的 home 目录下一个名为 .vuerc 的 JSON 文件里。如果你想要修改被保存的 preset / 选项,可以编辑这个文件。

在项目创建的过程中,你也会被提示选择喜欢的包管理器或使用淘宝 npm 镜像源以更快地安装依赖。这些选择也将会存入 ~/.vuerc

3.项目结构

node_modules
public // 静态资源文件
    |-favicon.ico
    |-index.html
src // 项目源代码,书写代码的地方
    |-assets
    |-App.vue
    |-main.js
.browserslistrc    // 浏览器相关支持情况
.eslintrc.js       // 代码相关支持情况
.gitignore         // Git忽略文件
babel.config.js    // babel配置ES语法 转换
package-lock.json  // npm安装依赖库的具体信息
package.json       // npm依赖库版本信息
postcss.config.js  // css相关转换
README.md          // 项目说明
vue.config.js      // Vue及webpack配置项

vue.config.js

vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。

这个文件应该导出一个包含了选项的对象:

// vue.config.js
module.exports = {
  // 选项...(例如:)
  lintOnSave: false	// 关闭eslint
}

4 . “@/”路径提示配置

安装 Path Intellisense插件

打开设置 - 首选项 - 搜索 Path Intellisense - 打开 settings.json ,添加:

"path-intellisense.mappings": {
     "@": "${workspaceRoot}/src"
 }

在项目 package.json 所在同级目录下创建文件 jsconfig.json

{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "allowSyntheticDefaultImports": true,
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"]
        }
    },
    "exclude": [
        "node_modules"
    ]
}

最后重启打开即可

六.常用修饰符

vue中常用的修饰符分三种,分别是事件修饰符、按键修饰符和表单修饰符。

一. 事件修饰符

  1. .stop 阻止事件冒泡(*)

  2. .prevent 阻止默认事件(*)

  3. .prevent.stop 阻止默认事件的同时阻止冒泡

  4. .once 阻止事件重复触发(once与stop不能一起使用,否则再次触发事件,依然会冒泡)(*)

<div class="big" @click="big">
    <div class="small" @click.stop="small">
      <a href="#" @click.stop.prevent>百度一下</a>
    </div>
    <button @click.once="btn">按钮</button>
</div>

二.按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<input v-on:keyup.enter="submit">
<input v-on:keyup.13="submit">

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

  • .enter (*)
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

三.系统修饰键

可以用

实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  1. .ctrl (实测结果:由于mac系统 ctrl+鼠标左键 = 右键 这个功能,所以mac上无法实现)

  2. .alt

  3. .shift

  4. .meta (meta在win上代表⊞键,在mac上代表⌘键)

<!-- Alt + C -->
<input v-on:keyup.alt.67="doSomething">

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

四.表单修饰符

  1. .trim : 去掉表单字符串的前后空格
  2. .number : 把表单里面的字符串转成数字类型
  3. .lazy : 失焦或者回车的时候再加载,节约了浏览器性能的损耗
<input type="text" v-model.trim="txtVal">
<input type="number" v-model.number.lazy="t2Val">

五.watch

一. watch的作用

  1. 可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。

  2. 简单点说,就是实时监听某个数据的变化

二. 普通监听

watch:{
    num(newVal,oldVal){  // 接收两个参数,newVal  和 oldVal
        // 监听的属性发生改变时执行这里的代码
        console.log('num发生改变的时候执行这里的代码');
        console.log(newVal,oldVal);
    }
}

三. 立即监听

如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

immediate需要搭配handler一起使用,其在最初绑定时,调用的函数也就是这个handler函数

watch:{
    num:{	// 立即监听:页面一刷新的时候就会执行
        handler(newVal,oldVal){
            console.log('页面一刷新就会执行');
            console.log(newVal,oldVal);
        },
        immediate:true   // 执行了立即监听
    }
} 

四.深度监听

当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听

注意:

1、如果监听的数据是一个对象,那么 immediate: true失效;

2、一般使用于对引用类型的监听,深度监听,如监听一个Object,只要Object里面的任何一个字段发生变化都会被监听,但是比较消耗性能,根据需求使用,能不用则不用。

3、因为上面代码obj是引用数据类型,val, oldVal指向一致,导致看到的结果一样。

 data () {
     return {
         obj:{
             name:'Vue',
             age:7
         }
     }
 },
     watch:{
         obj:{
             // 深度监听:这只是监听obj的对象,不能监听里面的属性,加了deep 属性才会能监听到里面的值,新值和老值都是一样的
             handler(){
                 console.log('深度监听');
                 console.log(this.obj.age);
             },
                 deep:true   // 实行了深度监听
         }
     }

五.Watch与Computed的区别

  1. watch中的函数是不需要调用的,computed内部的函数调用的时候不需要加()
  2. watch(属性监听),监听的是属性的变化,而computed(计算属性),是通过计算而得来的数据
  3. watch需要在数据变化时执行异步或开销较大的操作时使用,而对于任何复杂逻辑或一个数据属性,在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性computed。
  4. computed 属性的结果会被缓存,且computed中的函数必须用return返回最终的结果
  5. watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;

1. Watch与Computed的使用场景

  1. computed

    1. 当一个结果受多个属性影响的时候就需要用到computed
    2. 最典型的例子: 购物车商品结算的时候
  2. watch

    1. 当一个数据的变化需要有额外操作的时候就需要用watch
    2. 搜索数据
  3. 总结:

    1. 一个值的结果受其他值的影响,用computed
    2. 一个值的变化将时刻影响其他值,用watch

六.ref 和 $refs

vue中获取页面里的某个元素(标签或组件),可以给它绑定ref属性,有点类似于给它添加id名

<div>
    <!-- ref 标签属性  和 $ refs 对象 -->
    <!-- ref 是标签/子组件  的标签属性.类似于起了一个id -->
    <p ref="op">我是一个P标签</p>
    <button @click="btnClick">按钮</button>

    <Child ref="ch"/>
</div>
methods:{
    btnClick(){
        console.log(this.$refs);  // 获取到了所有拥有 ref鼠标的标签对象
        console.log(this.$refs.op);   // 获取到了ref的值是op的标签
        this.$refs.op.style.color="pink";  // 可以通过this.$refs修改标签的样式
        this.$refs.op.innerHTML='我是新的p标签';
        
        console.log(this.$refs.ch.num);  // 直接获取到了子组件的值(但是很消耗性能)
    }
}

七. Vue生命周期

一. 什么是生命周期:

  1. 从Vue创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。
  2. **生命周期钩子函数:**就是生命周期事件的别名

二. 8 个声明周期

  1. 初始
    1. **beforeCreate:**实例刚刚在内存中被创建出来,此时还没有初始化 datamethods 属性。
    2. created:实例已经在内存中创建好,此时 datamethods 已经创建好,此时还没有开始编译模板
  2. 挂载
    1. **beforeMount:**此时已经完成了模板编译,但是还没有挂载到页面中;
    2. **mounted:**这个时候已经把编译好的模板挂载到页面指定的容器里;

beforeMount: data 的数据可以访问和修改,而且此时的模板已经编译好了,还没有更新到页面中

mounted: 此时编译的模板更新到页面中了

  1. 更新(视图)
    1. **beforeUpdate:**状态更新之前执行此函数,此时的 data 中的数据是最新,但是界面上显示的还是旧的,因为此时还没有开始重新渲染DOM节点;
    2. **updated:**实例更新完毕之后调用此函数,此时data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了;

beforeUpdate: 此时修改输入框的内容,data中的数据已经更新了,但是页面上显示的还是旧数据;

**updated:**此时 data 的内容已经更新,页面显示的也是最新的数据。

  1. 销毁
    1. **beforeDestroy:**实例销毁之前调用,在这一步,实例让然完全可用。
    2. **destroyed:**实例销毁后调用,调用后,Vue实例指向的所以东西会被解绑,所有的事件监听器会被移除,所有的子实力也会被销毁。
<template>
    <div>
        <p ref="op">{{num}}</p>
        <button @click="add">按钮</button>
    </div>
</template>

<script>
export default {
    data () {
        return {
            num:20
        }
    },
    methods:{
        add(){
            this.num++;
        }
    },
    // 初始化阶段  beforeCreate 和 created
    beforeCreate(){  // 这个在组件对象创建之前执行,  数据,方法和标签都不能获取到
        console.log('1.1==> beforeCreate ', this.num, this.add,this.$refs.op);
    },
    created(){      // 这个在组件对象创建之后执行,  数据,方法可以获取到, 标签获取不到
        // 一般在这里执行异步操作(ajax请求),请求成功后拿到数据更新数据
        console.log('1.2==> created ', this.num, this.add,this.$refs.op);
    },
    // 挂载节点  beforeMount 和 mounted
    beforeMount(){  // 这个在组件对象挂载之前执行,  数据,方法可以获取到, 标签获取不到
        console.log('2.1==> beforeMount ', this.num, this.add,this.$refs.op);
    },
    mounted(){      // 这个在组件对象挂载之后执行,  数据,方法和标签都可以获取到
        // 如果异步操作(ajax)放在这个函数里面,页面会渲染两次
        console.log('2.1==> beforeMount ', this.num, this.add,this.$refs.op);
    },
    // (视图)更新阶段
    beforeUpdate(){   // 视图更新之前执行
        console.log('3.1==> beforeUpdate ', this.num, this.add,this.$refs.op);
    },
    updated(){    // 视图更新之后执行
        console.log('3.2==> update ', this.num, this.add,this.$refs.op.innerHTML);
    },
    
    // 销毁阶段(在子组件中)
    beforeDestroy(){  // 组件销毁之前执行
        // 回收和清理的工作,比如清除定时器,清除全局事件
        console.log('执行了4.1beforeDestroy');
        clearInterval(this.timer);
    },
    destroyed(){   // 组件销毁之后执行
        console.log("执行了4.2destroyed");
    }
}
</script>

八.mixins(混入)

mixins就是定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改。同一个生命周期,混入对象会比组件的先执行。

1、导出mixins

在src下创建 mixins/index.js,写入:

export const MixinsFn = {
    created() { 
        console.log("这是mixins触发的created")
    }
}

2、引用mixins

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
<script>
import { MixinsFn } from '@/mixins/index.js'
export default {
  created(){
    console.log("这是about触发的created")
  },
  mixins: [MixinsFn]
}
</script>

总结: 我们会发现,mixins中的createdabout中的created 优先执行。

九.keep-alive

<keep-alive>是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

KeepAlive用于处理组件缓存。有什么用呢?想象一个业务场景:

在Login页面填写完手机号,发现未注册,需要跳去Register页面完成注册,再返回Login页面填写密码,进行登录,此时如果在Login页面没有保存好手机号,那么跳回来时,用户又得重新输入手机号。为了解决这样的需求,我们需要借助keep-alive。

这里,我们新创建两个页面, You.vueMe.vue ,并且都实现累加功能。

1、修改App.vue

// 将原本的:
<router-view/>

// 修改为:
<keep-alive>
  <router-view/>
</keep-alive>

这样,我们实现了整个项目中每个页面的缓存,显然我们不想这么做,我们只想针对某些页面,这时,我们修改一下:

<!-- 放在keep-alive标签中的组件就会有缓存效果   没有套上keep-alive就不会有缓存效果 -->
<keep-alive>
    <!-- router-view 占位符,将来被某个组件代替  被谁代替,看router/index.js -->
	<router-view v-if="$route.meta.isKeep" />
</keep-alive>
<router-view v-if="!$route.meta.isKeep" />
/* 
	如果页面需要需要缓存,在router/index.js的routes下面这只一个meta属性,给meta属性设置一个变量,
	如果为真就会进第一个,就会有缓存效果,如果变量为false就会进下面,没有缓存效果
*/

注意:

<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。

那这里的 $route.meta.keepAlive 存在于哪里呢?

2、Router中的meta

这是写在路由文件中的字段,我们去 router/index.js 中,将我们要缓存的 Me.vue 页面加上meta:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
    {
      path: '/me',
      name: 'Me',
      component: () => import('../views/Me.vue'),
      meta: {
        isKeep: true   // 这是判断页面是否需要缓存的依据
      }
    },
    {
      path: '/you',
      name: 'You',
      component: () => import('../views/You.vue'),
      meta:{
          isKeep: false
      }
    }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

在有缓存的页面会有两个生命周期函数(这两个生命周期函数只有在有缓存的页面才会有)

// 激活和取消激活的生命周期函数,是有keep-aline组件独有的
  activated(){
    console.log("被激活(显示出来)的时候执行这里的代码");
  },
  deactivated(){
    console.log("被取消激活(隐藏起来)的时候执行这里的代码");
  }

十.vuex

官方定义: Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

​ 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。

一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。

一. 使用场景

普通的父传子和子传父,或是兄弟组件之间的互传值,都是两个组件之间的数据连接,但如果数据需要多组件共享,并且数据量庞大,那么就不适宜用中央事件总线来解决。此时,我们需要一个更加强大的,能够维护庞大数据的东西,它就是vuex,我们称之为:状态管理

二. 什么时候使用vuex

官方回答: Vuex可以帮助我们管理共享状态,并附带了更多的概念和框架,这需要对短期和长期效益进行权衡.如果您不打算打开大型单页应用,使用Vuex可能是繁琐冗余的,确实是如此___如果您的应用够简单,你最好不要使用Vuex,一个简单的 **store模式 **就足够您所需了,但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好的在组中外部管理状态,Vuex将会成为自然而然的选择,

引用Redux作者Dan Abramove的话说就是: Flux架构就像眼镜: 您自会知道什么时候需要它

很显然,如果你的项目比较简单,建议还是别强行使用vuex了。

另外,值得注意的点:vuex会随着页面刷新或关闭,将所有数据恢复至最初始的状态,所以它并不能替代localStorage。

三. state

vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。

我们来实现一个计数功能,但咱们把数据存放到vuex中的state,这里是专门用来存放组件共享的数据的。

四. getters

vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

五.mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

有一个必须注意的点,Mutations中不允许出现异步操作,它必须是一个同步函数

六.actions

Action 类似于 mutation,不同在于:

  1. Action 提交的是 mutations,而不是直接变更状态。
  2. Action 可以包含任意异步操作。

store/index.js文件

import Vue from 'vue'
import Vuex from 'vuex'
import changeNum from './changeNum'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    title: '我是组件',
    num: 10
  },
  getters: {
    dbnum(state) {
      return state.num*2;
    }
  },
  mutations: {
    addNum(state,payload){
      return state.num += payload;
    }
  },
  actions: {
    asyncAddNum(state,payload){
      setTimeout(()=>{
        state.commit('addNum',payload);
      },1000)
    }
  },
  modules: {
    changeNum
  }
})

组件中

<template>
  <div>
    <!-- <h2>{{ $store.state.title }}</h2> -->
    <h2>{{ title }}</h2>
    <h2>{{ $store.state.num }}</h2>
    <h2>{{ num }}</h2>
    <h2>{{ dbnum }}</h2>
    <button @click="changeNun">按钮1</button>
    <button @click="changeNum2">异步按钮2</button>
  </div>
</template>

<script>
export default {
  computed: {
    title() {
      return this.$store.state.title;
    },
    num() {
      return this.$store.state.num;
    },
    dbnum(){
        return this.$store.getters.dbnum;
    }
  },
  methods:{
      changeNun(){
          // commit(),专门调用store的mutations属性里面的方法
          return this.$store.commit('addNum',10);
      },
      changeNum2(){
          // dispatch(),专门调用store的actions属性里面的方法
          return this.$store.dispatch('asyncAddNum',20)
      }
  }
};
</script>

七. 辅助函数

获取单个数据或触发某个方法比较容易,我们直接拿到和触发就行,但如果要获取的数据和触发的方法很多个,我们就比较麻烦了,这时候我们需要借用辅助函数。比如,我们刚刚只写了累加,现在再补充一个递减。然后来看看辅助函数怎么用

组件中

<template>
    <div>
        <!-- 辅助函数 -->
        <h2>{{ title }}</h2>
        <h2>{{ num }}</h2>
        <h2>{{ dbnum }}</h2>
        <button @click="changeNum">按钮</button>
        <button @click="asyncAddNum(20)">异步按钮2</button>
    </div>
</template>

<script>
import {mapState , mapGetters, mapMutations,mapActions} from 'vuex';
export default {
    computed:{
        ...mapState(['title','num']),
        ...mapGetters(['dbnum'])
    },
    methods:{
        ...mapMutations(['addNum']),
        ...mapActions(['asyncAddNum']),
        changeNum(){
            this.addNum(10);
        }
    }
}
</script>

八. module

假设我们把累加单独抽出来作为一个模块,在store下新建一个 changeNum/index.js 文件

export default {
    namespaced:true,   //	命名空间,为true时,可以在store中把当前模块文件夹名称(changeNum),当作模块名使用
    state: {
        title: '我是组件',
        num: 10
    },
    getters: {
        dbnum(state) {
            return state.num * 2;
        }
    },
    mutations: {
        addNum(state, payload) {
            return state.num += payload;
        }
    },
    actions: {
        asyncAddNum(state, payload) {
            setTimeout(() => {
                state.commit('addNum', payload);
            }, 1000)
        }
    },
}

把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:

import Vue from 'vue'
import Vuex from 'vuex'
import changeNum from './changeNum'   // 引入

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    changeNum
  }
})

组件中

<template>
    <div>
        <h1>{{ title }}</h1>
        <h1>{{ num }}</h1>
        <h2>{{ dbnum }}</h2>
        <button @click="change">按钮</button>
        <button @click="asyncAddNum(20)">异步按钮</button>
    </div>
</template>

<script>
import {mapState , mapGetters ,mapMutations, mapActions} from 'vuex'
export default {
    computed:{
        ...mapState({
            // 变量名: state=> state.文件名.变量名
            'title': state=>state.changeNum.title,    // module中state值的获取方法和getters等不太一样,需要写函数形式
            'num' : state=> state.changeNum.num
        }),
        ...mapGetters({
            'dbnum' : 'changeNum/dbnum'     // 文件名/函数名
        })
    },
    methods:{
        ...mapMutations({
            'addNum' : 'changeNum/addNum'
        }),
        ...mapActions({
            'asyncAddNum' : 'changeNum/asyncAddNum'
        }),
        change(){
            this.addNum(10)
        }
    }
}
</script>

九. 拆分写法

实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js 进行拆分:

state.js

export default {
    num1: 0,
    title: '标题'
}

mutations.js

export default {
    cutNum(state, payload) {
        state.num1 -= payload;
    }
}

actions.js

export default {
    AsyncCutNum({ commit }, payload) {
        setTimeout(() => {
            commit('cutNum', payload)
        }, 300)
    }
}

modules.js

import add from './add'
export default {
    add
}

最后,在 store/index.js 中:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import modules from './modules'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations,
  actions,
  modules
})

十. vuex数据在 路由守卫中获取

router/index.js文件中: (vuex的数据是用辅助函数写的)

import store from "@/store" ;   // 引入store数据

# store.state.文件名.state里面的变量名
store.state.navList.navListArr  // 获取vuex state里面的数据 

#store._mutations["文件名/mutations里面的函数名"][索引](传入的参数)
store._mutations["navList/changeNavListArr"][0](res.data.menu)  // 修改vuex里面的数据

十一. vue-router

一. SPA

  • Single Page Application,中文:单页应用。
  • 以下两个链接请用浏览器的手机模拟器打开:
  • 单页应用有很多,比如:https://m.huxiu.com/
  • 非单页应用也是以往的开发习惯,比如:https://36kr.com/

二. 什么是路由

路由器是用来做什么的

  • 路由是决定数据包从来源到目的地的路径;
  • 将输入端的数据转移到合映射表适的输出端;
  • 路由中最重要的概念就是路由表:路由表的本质就是一个映射表,决定了数据包的指向

三. 后端路由

  1. 早期的网站开发整个HTML页面是由服务器来渲染的。服务器将渲染好的对应的HTML页面返回给客户端进行展示;
  2. 但是一个网站包含很多页面,那服务器是怎么处理的呢?
    1. 每个页面都有对应的网址,也就是URL
    2. URL会发送给到服务器,服务器会通过正则对该URL进行匹配,最后交给Controller进行处理
    3. Controller进行处理,最终生成HTML或者数据,然后返回给前端。
    4. 这其实就是服务器的一个IO操作,这其实就是对后端对路由的解析
  3. 后端渲染的好处,相对于发送ajax请求拿数据,可以提高首屏渲染的性能,也有利于SEO的优化;
  4. 后端路由的缺点:
    1. 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码
    2. 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情

四. 前端路由:

记录路径和视图(组件)的对应关系

前后端分离阶段:

  • 随着Ajax的出现,有了前后端分离的开发模式;
  • 后端只提供API来返回数据(json,xml),前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中
  • 这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上
  • 并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可
  • 目前很多的网站依然采用这种模式开发

单页面应用阶段:

  • 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由
  • 也就是前端来维护一套路由规则

前端路由的核心是什么呢?

  • 改变URL,但是页面不进行整体的刷新

五. 前端路由的规则

  1. URL的hash

    • URL的hash也就是锚点(#), 本质上是改变window.location的href属性
    • 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
    location.hash = '/aaa';
    location.hash = '/bbb';
    
  2. HTML 5 的 history模式

    • history接口时HTML5新增的,它有5种模式改变URL而不刷新页面
      • history.pushState(data, title, url)
      • history.replaceState(data, title, url)
      • **history.go(-1) **返回上一页
      • history.back() 等价于 history.go(-1)
      • history.forward() 等价于 history.go(1)

六. vue-router 的基本使用

  1. 认识路由

    • vue-router是Vue的官方路由插件,它和Vue是深度集成的,适合用于构建单页面应用 https://router.vuejs.org/zh/
    • vue-router是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来;在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
  2. 路由的使用

    src/router/index.js 文件中

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/about',
        name: 'About',
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        // ES6懒加载作用:让组件等到用户获取这个页面的时候才来加载,提高用户体验(解决第一次加载的时候白屏的情况)
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
      },
      {
        path: '/user',
        name: 'User',
        component: ()=> import('../views/User.vue')
      }
    ]
    
    const router = new VueRouter({
      mode: 'history',
      base: process.env.BASE_URL,
      routes
    })
    
    export default router
    

可以看到,我们有两种引入组件的方式。第一种比较能理解,第二种我们称之为“路由懒加载”。而这个懒加载中,有个 webpackChunkName,这东西我们称为魔法注释。

魔法注释的作用:

webpack在打包的时候,对异步引入的库代码(lodash)进行代码分割时,为分割后的代码块取得名字。

Vue中运用import的懒加载语句以及webpack的魔法注释,在项目进行webpack打包的时候,对不同模块进行代码分割,在首屏加载时,用到哪个模块再加载哪个模块,实现懒加载进行页面的优化。

当你 npm run build 之后,生成的js文件中,就能看到以魔法注释定义的js文件名。

七. 懒加载

当一个vue项目很大的时候,对于一些“暂时”用不到的组件,我们可以不进行加载,等到用到次组件时再加载。这样可以优化spa应用首次加载白屏情况,也给用户更好的体验。这就是vue路由懒加载。

懒加载的方式

// 方式一: 结合Vue的异步组件和Webpack的代码分析
const User = resolve => { require.ensure(['@/views/User.vue'], () => { resolve(require('@/views/User.vue')) }) };

// 方式二: AMD写法
const User = resolve => require(['@/views/User.vue'], resolve);

// 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import(/* webpackChunkName: "user" */ '../views/User.vue')

八.路由模式

vue中的路由默认时hash模式,使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,我们可以用路由的history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。

history模式提供了对历史记录进行修改的功能,只是当它们执行修改时,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。history模式,会出现404 的情况,需要后台配置。

404 错误:

1、hash模式下,仅hash符号之前的内容会被包含在请求中,如 http://www.xxx.com, 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误;

2、history模式下,前端的url必须和实际向后端发起请求的url 一致,如http://www.xxx.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。

九. 路由跳转方式

我们可以使用 router-link 标签来实现跳转,如:

<div id="nav">
  <router-link to="/">Home</router-link> |
  <router-link to="/user">User</router-link>
</div>
<router-view/>

然后通过 router-view 来显示页面。router-link 最终会被渲染为a标签。

router-link 默认会被解析为a标签,如果想让它转换成其他的标签,就需要添加tag属性:

<router-link tag="li" to="/user">User</router-link>

此时,router-link 就被解析为li标签。

路由跳转方式

methods:{
    handleClick(){
      // 通过 $router对象的方式跳转
      this.$router.push('/user');
      this.$router.push({name:'About'});  // 通过name跳转
      this.$router.push({path:'/user'});

      // 带查询字符串的方式跳转  query传参
      this.$router.push({name:'User' , query:{page:1, size:8}});   // http://localhost:8081?page=1&size=8
      this.$router.push({path:'/user', query:{page:2, size:9}});

      // 传递参数 pathinfo参数
      this.$router.push('/user/999');    // this.$router.push('/路径/参数')  http://localhost:8081/user/999
      this.$router.push({name:'User',params:{id:999}});
      
      // this.$router.push({path:'/user' , params:{id:999}});   // 错误写法
    }
  }

// src/router/index.js 文件中
{
    path: '/user/:id',           // path:'/路径/:参数名'
    name: 'User',
    component: () => import(/* webpackChunkName: "about" */ '../views/User.vue')
  }

// 组件中获取参数
created(){
    // this.$route.params.参数名
    console.log(this.$route.params.id);  // 获取url的id的参数
    this.id = this.$route.params.id;     // 修改data定义的id的值
}

编程式导航中,使用name进行路径跳转,携带参数可以通过params和query,其中query会将参数携带在导航路径上,而使用path进行路径跳转,无法携带params,只能携带query。

十. 重定向 redirect

const routes = [
  {
    path: '/',
    redirect: '/home'		// 这就是路由的重定向,重新定义跳转路径
  },
  {
    path: '/home',    // 改成这个之后,原来的/就没有对用的组件了
    component: () => import('@/views/Home.vue')
  },
  
  ...  ...
  {
    path: '*',		// 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面   
    component: () => import('@/views/Page404.vue')
  }
]

十一.路由嵌套

const routes = [
  {
    path:'/',
    redirect:'/tab/category'   // 重定向
  },
  {
    path:'/tab',
    name:'Tab',
    component: () => import(/* webpackChunkName: "tab" */ '../views/Tab.vue'),
    // 路由嵌套
    children:[
      {
        path: '/tab/category',
        component: () => import(/* webpackChunkName: "category" */ '../components/Category.vue')
      },
      {
        path: '/tab/cart',
        component: () => import(/* webpackChunkName: "category" */ '../components/Cart.vue')
      },
      {
        path: '/tab/user',
        component: () => import(/* webpackChunkName: "category" */ '../components/User.vue')
      }
    ]
  }

十二. 关于重复点击同一个路由出现的报错问题解决

  1. vue-router降级处理(但不推荐)

    npm i vue-router@3.0.7
    
  2. 直接在push方法最后添加异常捕获,例如:

    this.$router.push('/user').catch(err=>{});
    
  3. 直接修改原型方法push(推荐)

    // 把这段代码直接粘贴到router/index.js中的Vue.use(VueRouter)之前
    const originalPush = VueRouter.prototype.push;
    VueRouter.prototype.push = function(location) {
      return originalPush.call(this, location).catch(err => {})
    };
    

十二. vue-axios

在Vue和React等大型项目中,我们常用的数据请求方式,就是Axios。Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

官网地址:http://www.axios-js.com/

一. 安装

yarn add axios
npm install  axios

二. 使用

// 引入
import axios from 'axios'

created(){
    // axios发送get请求,如果要携带参数,必须加上 params
    axios.get('/getdata',{
        params:{
            page:1,
            size:10
        }
    }).then(res=>{
        console.log(res);
        console.log(res.data);
    })

    // axios发送post请求
    axios.post('/postdata',{
        data:{
            num:123
        }
    }).then(res=>{
        console.log(res);
        console.log(res.data);
    })
}



axios.get(url[, config])
axios.post(url[, data[, config]])

// 直白一点表示:
// 发送get请求  带参数的话一定通过params传递,获取之后带在url后面
faaxios
  .get(url, {
    params: {}
  })
  .then(res=>{})
  .catch(err=>{})

axios
  .post('/user', {})
  .then(res=>{})
  .catch(err=>{})

三.解决跨域:代理请求

解决方案是创建一个 vue.config.js :

module.exports = {
    devServer: {
        proxy: "http://localhost:3000"
    }
}

十三. 图片的使用

<template>
    <div>
        <!-- 图片的引入 -->
        <img src="../assets/logo.png" alt="">
        <img src="@/assets/logo.png" alt="">

        <!-- 模块化引入图片 -->
        <img :src="img01" alt="">
        <img :src="img02" alt="">

        <!-- 做背景图片 -->
        <div class="box"></div>
        <!-- 行内样式图片 -->
        <div class="box1" :style="{background:`url(${img01}) no-repeat`}"></div>

        <div class="box1" :style="{background:bgc}"></div>
        
    </div>
</template>

<script>
import img01 from '@/assets/logo.png'   // ES6模块化
export default {
    data () {
        return {
            img01,
            img02:require('@/assets/logo.png'),  // common JS
            bgc:`url(${img01}) no-repeat`
        }
    }
}
</script>
 
<style lang="less" scoped>
    .box {
        width: 200px;
        height: 200px;
        border: 1px solid #000;
        background-image: url('../assets/logo.png');
    }
    .box1 {
        width: 300px;
        height: 300px;
        border: 1px solid #000;
    }
</style>

十四. $nextTick应用场景

在挂在阶段之前,我们是拿不到dom标签上及其数据的,如果我们想要在挂载阶段之前的函数中获取挂载后的数据,就需要使用$nextTick

$nextTick有两种基本使用方法:

// 1、CallBack形式
this.$nextTick(()=>{
  console.log(this.$refs.title.innerHTML);
})

// 2、Promise形式
this.$nextTick().then(()=>{
  console.log(this.$refs.title.innerHTML);
})

Comp1.vue中:

<template>
    <div>
        <p ref="op">文字</p>
    </div>
</template>

<script>
export default {
    data () {
        return {
        }
    },
    created(){
        // 在该阶段,我们获取不到元素
        console.log(this.$refs.op);  // undefined
        this.$nextTick(()=>{
            console.log(this.$refs.op.innerHTML); //文字
        })
        this.$nextTick().then(()=>{
            console.log(this.$refs.op.innerHTML);  //文字
        })
    }
}
</script>
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值