Vue基础学习

第一章

vue01-初体验

  • 原生js,jquey:编程范式:命令式编程; vue:编程范式:声明式编程
  • 还有一种编程范式:面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    {{message}}
</div>
<script type="text/javascript">
	var vm = new Vue({
		el:'#app', //用于管理要挂载的元素
		data:{//定义数据
			message:'hello'
		}
	});
</script>
<style type="text/javascript">
</style>
</body>
</html>

vue02-列表展示

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
	<ul>
		<li v-for="item in movies">{{item}}</li>
	</ul>
</div>
<script type="text/javascript">
	var vm = new Vue({
		el:'#app',
		data:{
			movies:['movie1','movie2','movie3','movie4']
		}
	});
</script>
<style type="text/javascript">
</style>
</body>
</html>

vue03-计数器案例

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
    <h2>
        当前计数:{{counter}}
    </h2>
    <button @click="increase">//@click等同于v-on:click
        +
    </button>   
    <button @click="decrease">
        -
    </button>

</div>
<script type="text/javascript">
	const app = new Vue({
       el:'#app',
       data:{
           counter:0, 
       },
       methods:{
           increase(){//increase(){} 等同于 increase:function(){}
               this.counter++
           },
           decrease(){
               this.counter--
           }
       }
    });
</script>
<style type="text/javascript">
</style>
</body>
</html>

Vue中的MVVM

View层:

  • 视图层
  • 在我们前端开发中,通常就是DOM层
  • 只要作用是给用户展示各种信息

Model层:

  • 数据层
  • 数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据

ViewModel层

  • 视图模型层
  • 视图模型层是 View 和 Model 沟通的桥梁
  • 一方面它实现了 Data Binding,也就是数据绑定,将 Model 的改变实时地反应到 View 中
  • 另一方面它实现了 DOM Listener,也就是 DOM 监听,当 DOM 发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的 Data

创建Vue实例传入的Options

目前掌握的:

  • el:String|HTMLElement 类型,决定之后 Vue 实例会管理哪个 DOM
  • data:Object|Function 类型,Vue实例对应的对象
  • methods:{[key:string]:funciton} 类型,定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用
  • 方法一般和实例挂钩,函数则一般在类的外面

Vue的生命周期

  • 生命周期:事物从诞生到消亡的过程

  • Vue 生命周期:new Vue() 时源码中会对传入的 Options 进行解析,如果写了对应的方法,例如created(){} 就会在对应 created 的生命周期中回调 created 函数

  • beforeCreate (){} //在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

  • created(){} //在实例创建完成后被立即调用。
    在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

  • beforeMount(){} //在挂载开始之前被调用:相关的渲染函数首次被调用

  • mounted(){} //el 被新创建的 vm.$el 替换, 挂载成功

  • beforeUpdate(){} //数据更新时调用

  • updated(){} //组件 DOM 已经更新, 组件更新完毕,因为数据更新后会重新挂载,所以实现了响应式

Mustache 语法

  • 释义:胡须
  • 双大括号语法
  • Mustache 语法中不仅仅可以直接写变量,也可以写一些简单的表达式

v-xx指令

  • v-once:<h2 v-once>{{message}}</h2> 只展示 message 初值,不会随值的改变而改变显示
  • v-html:<h2 v-html="url">{{message}}</h2> url:'<a href="http://www.baidu.com">百度一下</a>' 可以展示原生html控件
  • v-text:和 mustache 很相似,<h2 v-text="message"></h2> ,同样可以展示文本,但是不经常使用,因为不够灵活
  • v-pre:<h2 v-pre>{{message}}</h2> ,会原封不动地显示{{message}}
  • v-cloak: cloak(斗篷)<h2 v-cloak>{{message}}</h2> , 当vue解析之前,他有属性v-cloak,解析值完成才去除这个属性 <style>[v-cloak]{display:none}</style>,这样在解析vue卡住时也不会显示原本的{{message}}而是什么也不显示

v-bind

  • 除了某些内容需要用Mustache语法绑定,某些属性我们通常也希望动态绑定
  • 基本用法:<a v-bind:href="url"/>
  • 语法糖:也就是简写方式<a :href="url"/>
  • 动态绑定class(1):对象语法<h2 :class="{类名1:boolean,类名2:boolean}">123<h2>,当 boolean 为 true 时,为该对象添加对应的类
  • 动态绑定类时,还能用原生的写法,会合并类<h2 class="类名3" :class="{类名1:boolean,类名2:boolean}">123<h2>
  • 还能用数组语法绑定class,<h2 :class="[变量,'类名']">{{message}}</h2>,也能同时使用原生写法,也能使用 methods 中的函数getClass(){return{'active','line'}}
  • 动态绑定Style:<h2 :style="{css属性名:'属性值'}">{{message}}</h2>,注意属性值的单引号
  • 动态绑定Style数组语法:<h2 :style="[color,font]">{{message}}</h2>,其中 color 和 font 为对象color:{color:'red'}

计算属性

  • computed 性能更高,因为只会执行一次,有缓存,而 methods 中的每次调用都会执行

  • 计算属性的基本使用:参照同名 html 文件

  • 计算属性的复杂使用:参照同名 html 文件,里面有for循环的两种使用方式和reduce的基本使用

  • 计算属性的 setter 和 getter:在使用mustache语法时本质上是computed对象的某个属性的getter方法,具体参照同名 html 文件

  • 计算属性和 methods 的对比:如果一个值要经过操作来获取到,并且要获取多次,使用 computed 比使用 methods 更加合适

块级作用域(ES6/块级作用域.html)

  • let/var 的区别:有无作用域
  • ES5之前因为 if 和 for 都没有块级作用域的概念,所以在很多时候,我们都必须借助于 function 的作用域来解决应用外面变量的问题
  • ES6中加入了 let ,它有 if 和 for 的块级作用域

ES6-const修饰符

  • 定义常量的修饰符,定义的常量不可再次赋值
  • 使用注意:const a= 1;之后 a=30是错误的,const a也是错误的,必须赋值
  • 优先使用 const,需要变量时才用 let
  • 如果使用 const 修饰对象,则指向的对象不能修改,但是属性的值可以修改

对象字面量的增强写法

  • 字面量:用来为变量赋值时的常数量,例如:const obj={} '{}'就是字面量

  • 用法1:属性的增强写法

    const name = 'john'                    
    const age = 18
    ES5                                        ES6增强写法
    const obj={               --->             const obj={
        name:name                                  name
        age:age                                    age
    }                                          }
    
  • 用法2:函数的增强写法

es5
const obj={
    eat:function(){
    }
}
----------------------------
es6
const obj={
    eat(){

    }
}

事件监听-v-on

  • 缩写:@
  • 事件监听时如果不需要传入参数,可以省略(),例如<button @click="func">
  • 如果函数需要传参,但是没有传参,而且写了小括号,则形参为 undifined,例如<button @click="func()">

函数为:func(name){console.log(name)}

  • 如果需要传参但是未传参,且没写小括号,则 vue 默认将浏览器生成的 event 事件对象作为参数传入方法

  • 如果不仅需要 event,同时需要其他参数,则写法为:

  • <button @click="func(123,$event)">

  • 修饰符的使用:

    • stop:div 层中的 button 的事件添加.stop例如

    • <div @click = "divClick">
          <button @click.stop="btnClick">按钮</button>
      </div>
      
    • prevent:点击提交时不会自动提交表单到百度,而是进入submitClick

    • <form action="www.baidu.com">
          <input type="submit" value="提交" @click.prevent="submitClick">
      </form>
      
    • :监听某个键盘的键帽,例如监听 Enter 键的

    • <input type="text" @keyup.enter="keyUp">
      
    • native:使用自定义组件时用到

    • <test @click="testClick"></test>          //无效
      <test @click.native="testClick"></test>   //有效
      
    • once:只回调一次,只会执行一次 btnClick

    • <button @click.once="btnClick">
          按钮
      </button>
      

条件判断

v-if
  • v-if=“true” 则显示该控件,为 false 则不显示

  • v-if,v-else-if,v-else 使用示例:

  • <h2 v-if="x>3">我是v-if</h2>
    <h2 v-else-if="x>2">我是v-else-if</h2>
    <h2 v-else>我是v-else</h2>
    
  • 登录切换小案例小问题:在切换登陆类型时,输入框中的值不会清空

  • 问题产生原因:真实 DOM 渲染到页面时,中间会经过虚拟DOM,Vue 在进行对虚拟 DOM 渲染时,出于性能考虑,会尽量复用已经存在的元素,而不是重新创建新的元素,案例中两块类似的部分时互斥的,所以会这样,增加 key(不同的key) 即可解决

  • 详见同名文件夹下html文件

v-show
  • v-show 用法与 v-if 非常相似,同样决定一个元素是否渲染
  • 区别:v-if 为 false 时对应元素则不存在 DOM 中,而 v-show 仅仅将元素的 display 属性设为 none
  • 是否显示切换频繁是选择 v-show,只有一次切换时选用 v-if

循环遍历

v-for
  • 可以循环数组,优先 value,其次 index
  • 可以循环对象,优先循环 vaue,其次 key,最后 index
  • 推荐使用过程中添加 key,这和虚拟 DOM 中的 Diff 算法有关
  • 当渲染完成一个列表后,如果往中间插入一个新元素,在有 key 时,会找到位置直接插入。但是在没有key时,则会将该原本位置的值改为要插入的值,将之后的改为之前被改掉的,以此类推,性能较低。a-b-c 在 a 后面插入d,则将 b 变成 d,将 c 变成 b,然后最后插入 c,加上:key="item"即可,index 不能,因为 index 会变化(插入一个数后),key 的作用是为了高效地更新虚拟 DOM
  • 详见Day02循环遍历下的响应式方法

书籍购物车案例

  • 细节1:价格格式如果想要显示效果为 ¥后面加上两位小数,则如果都写成:
  • {{‘¥’ + book.price.toFixed(2)}} 虽然在显示价格的时候可行,但是在显示总价的时候,要采用这样的格式时还需要这样写,复用性较差。
  • 解决方案一:编写methods:{getPrice(price){return ‘¥’ + price.toFixed(2)}}
  • 更好的解决方案二:编写过滤器filters:{showPrice(price){return ‘¥’ + price.toFixed(2)}}

高阶函数的使用

filter
  • filter 中的回调函数有一个要求:必须返回一个 boolean 值
  • true:当返回 true 时,函数内部会自动将这次回调的 n 加入到新的数组中
  • fasle:当返回 false 时,函数内部会过滤到这次的 n
const nums=[10,20,111,120,135,45,60]
//低阶写法
let newNums = []
for(let item of nums){
    if(item<100){
        newNums.push(item)
    }
}

//高阶写法
let newNums = nums.filter(function(n){
    return n<100
})
可以简写成
let newNums = nums.filter(n=>n<100)
map
//低阶写法
let new2Nums = []
for(let item of newNums){   
    new2Nums.push(item*2)
}

//高阶写法
let new2Nums = newNums.map(function(n){
    return n*2
})
可以简写成
let newNums = newNums.map(n=>n*2)
reduce
  • 对数组中所有内容进行汇总
const nums=[1234]
let total = nums.reduce(function(priValue,n){
    return privalue+n
},0)
第一次:因为写了初始值为0,所以preValue:0    n:1
第二次:将第一次返回的值也就是 1 进行加处理,所以preValue:1  n:2
第三次:preValue:3    n:3
第三次:preValue:6    n:4
最后返回 6 + 4 也就是 10

综合使用:

const nums=[1234]
let total = nums.filter(n=>n<3).map(n=>n*2).reduce((pre,n)=>pre+n,0)
这就是一个典型的函数式编程

v-model

  • 绑定的控件比如是 input 当 input 中的值改变时,绑定的值也会改变,绑定的值改变时,input 中的值同样也会改变

  • 在 input 中,可以使用:value 和 @input 实现双向绑定,因为 v-model 是一个语法糖,它的背后本质上是包含了 v-bind 和 v-on 两个指令

  • v-model 和 radio 结合使用 用 value 就代表了单选框是否被选中,两个单选框要绑定同一个值

  • v-model 和 checkbox 结合使用 只有一个时用布尔值就能控制是否选中,多个时用数组控制

  • v-model 和 select 结合使用,用字符串表示选中一个的情况,用 multiple 加数组表示选中多个的情况

值绑定

  • input 的值从网络获取或在 data 中定义,通过 v-bind 动态绑定值,就是值绑定
  • input 最好和 label 联用,通过 label 的 for 和 input 的 id 来绑定
  • <input type="text" v-for="item in oraginalLetters"/>{{item}} 不知道为什么是一种错误的写法,会报错 item 未定义

v-model的修饰符

v-model.lazy
  • 默认情况下,v-model 时在 input 事件中同步输入框中的数据
  • 也就是说,一旦数据改变,对应的 data 中绑定的数据就改变
  • lazy 修饰符可以让数据在 input 输入框失去焦点或者按下回车时才更新
v-model.number
  • 默认情况下输入的数字和字母都当作字符串处理
  • number 修饰符可以让输入框的内容自动转成数字类型
v-model.trim
  • 过滤输入框内容左右两边的空格

组件化

  • 将一个页面拆分成一个个小的功能块,每个功能块完成属于自己的这部分独立的功能,整个页面的管理和维护就变得非常容易
  • 组件化是 Vue.js 中的重要思想
    • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
    • 任何的应用都会被抽象成一棵组件树
  • 组件的使用分为三个步骤
    1. 创建组件构造器 Vue.extend(),传入 templete 作为自定义组件的模板
      1. templete:后面使用==``==,因为这个不仅能代表是字符串,还能换行
    2. 注册组件 Vue.component(),将组件构造器注册为组件,并起名(全局组件)
    3. 使用组件 :在 Vue 实例的作用范围内使用组件
全局组件和局部组件
  • 使用 Vue.component()是是注册全局组件
  • 在 Vue 实例中注册组件就是全局组件,const app = new Vue({componens:{cpn:cpnC}})
父子组件
  • 在一个组件中注册一个组件,被注册组件就是子组件
  • Vue 实例本身也可以看成一个组件
  • 爷组件挂载的实例中不能直接使用孙子组件,得注册孙子组件,因为当孙子组件注册到子组件中时,Vue 已经将孙子组件编译好了,相当于把孙子组件的构造器的 templete 复制到了子组件中使用孙子组件标签的地方,所以 Vue 实例压根不认识这个子组件中的孙组件,当然不能直接使用孙子组件的标签
组件的语法糖注册形式
  • 注册全局组件直接Vue.component('cpn',{template:...})
  • 注册局部组件:components:{'cpn':{template:...}}
  • 本质就是省去了调用 Vue.extend() ,直接用一个对象来代替
组件模板抽离写法
  1. 使用
组件细节
  • 组件不能之间访问 Vue 实例中的数据
  • 组件是一个单独功能模块的封装,这个模块有属于自己的 HTML 模板,也应该有属于自己的数据 data
  • 组件对象也有 data 属性,methods 属性…
    • data 属性必须是一个函数,并且 return 一个对象,在对象中存储数据
为什么组件内的 data 必须是一个函数
  • 在每次通过 data 函数返回一个对象时,都是新创建一个对象然后返回,所以创建组件实例时,每个实例都是不同的 data,但是如果组件注册时不用函数的写法,也就是说在创建组件实例时,使用的data 都是组件注册时data{}这个对象,是同一个对象,三个实例使用同一个对象明显不符合开发需求,非要使用同一个对象就 const 一个对象,然后组件注册时 return 这个对象

  • 总结一下就是如果组件注册时 data 不是函数而是对象,创建的组件实例就都是使用同一个 data 对象了

父子组件的通信
  • 在开发中,往往一些数据需要从上层传递到下层,在一个页面从服务器请求到大量数据后,其中一部分数据需要子组件去进行展示,这是不会让子组件去发送网络请求,而是通过父组件将数据传递给子组件
  • 父子组件通信:
    1. 通过props传子组件传递数据
    2. 通过事件向父组件发送消息
  • 父传子-字符串数组:props 数组中的元素为字符串的写法,但是相当于变量,在调用子组件处加上v-bind:props 数组中的元素="父组件的变量"

    示例:

    <div id="app">
        <cpn :cmessage="message"></cpn>
    </div>
    <template>
        <div>
            <h2>
                {{cmessage}}
            </h2>
       </div>
    </template>
    //子组件(Vue实例中注册的组件)
    const cpn={
    	template:'#cpn',
    	props:['cmessage']
    }
    //父组件(Vue实例)
    const app = new Vue({
     	el:'#app',
    	data:{
    		message:'你好啊!',
    		movies:['aaa','bbb','ccc']
    	},
    	components:{
    		cpn//ES6字面量增强写法,即'cpn':{xxx}
    	}
    })
    
  • 父传子-对象:修改上面 const 的 cpn

    const cpn = {
    	template:'#cpn',
    	props:{
    		cmessage:{
    			type:String(也可以为自定义类型,也可以是多种类型,使用数组即可表示)
    			required:true(是否在使用该组件时必须传这个值)
    			default:''(没传的话使用这个默认值)
    		},
    		cmovies:{
                //当需要传递的值为对象或数组时,默认值必须为函数,然后return指定的类型
                type:Array
                default(){
                    return []
                },
                //也可以自定义验证传递的值
                Validator(value){
                    return ['aaa','bbb'].indexOf(value) != -1
                }
            }
    	}
    }
    
    • 当 props 中要接收的父组件的值的变量名为驼峰写法时,父组件在传值时应该将例如 :cMovies 改为 :c-movies
  • 子组件传值给父组件

    • 在子组件中:通过 this.$emit(‘自定义事件’,传参)来触发事件
    • 在父组件中:通过 v-on 来监听子组件事件
  • 父子组件通信案例中,可以用 v-model 与 watch 属性来实现两块区域的值绑定,也可以不使用语法糖,用 v-bind 和 v-on 来实现双向绑定和数据监听

父子组件的访问方式
  • 父访问子:
    • $refs :为子组件加上 ref 属性后可以引用对应引用名的子组件,
    • $children: 只能通过数组加下标来访问对应子组件,所以当子组件过多时不方便管理,不常使用
  • 子访问父:
    • $parent:访问父组件,可能为组件,也可能为 Vue 实例,但是获取父组件的 data 中值时可能会出现混乱所以不常用
    • $root: 直接获取根组件,也就是 Vue 实例
slot(插槽)
  • 插槽的目的是为了让我们原来的设备具有更多的扩展性,组件的插槽同理
  • 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽
  • 创建组件模板时写好空然后在使用子组件的之间加上要加入的就会自动渲染进去
  • 创建组件模板时如果在 slot 标签中写了控件,则该控件为默认值,使用组件时什么都不写就会渲染默认值
  • 使用组件时在中间写了多个控件会将所有控件都传入插槽
具名插槽
  • 在组件的模板的 slot 标签中加上 name="xx",在使用组件并往插槽中插值时加入 slot="xx"
编译作用域
  • 模板中使用的变量取决于模板的作用域,例如在<div id="app></div>中使用的变量就在挂载了#app的 Vue 实例中
  • 官方准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
作用域插槽
  • 父组件替换插槽的标签,但是内容由子组件来提供
  • 在子组件模板中的插槽中通过 :transtionData="要传给父组件的值"传值给父组件,然后在父组件是用子组件并且填充插槽时在填充的内容外部加上,然后在插槽中就可以通过 引用名.transtionData 来使用子组件传过来的值
  • join函数,可以将数组的元素转换成字符串然后通过符号拼接['a','b'].join(' - ') = a - b

前端模块化

JavaScript原始语法
  • 网页开发早期,js 只作为一种脚本语言,做一些简单的表单验证或动画实现等,直接写在
CommonJS
  • 模块化有两个核心:导入和导出
  • CommonJS 的导出:module.exports={}
  • CommonJS 的导入:let {falg,sum} = require('./aaa.js'),这其实是简便的写法,原始写法是:let aaa= require('./aaa.js'); let falg = aaa.flag
ES6
  • 导出:先在引用 js 文件的 script 标签中定义 type 为 module
    • 然后在 js 文件导出 export{flag,sum}
    • 也可以直接导出 export let num = 1,导出函数与类同理export function num(){},export class Person{}
    • **export default:**设置导出默认值,因为是默认值所以只能有一个,此时导入可以import 自定义名字 from './aaa.js',以上两处导出方式必须同名且需要{}
  • 导入:
    • 在使用的文件中导人 import {flag} from './aaa.js'
    • 统一全部导入:import * as moduleA from './aaa.js',可通过 moduleA .xx 来取想要的

webpack

认识 webpack
  • 从本质上讲,webpack 时一个现代的 JavaScript 应用的静态模块打包工具
    • 模块:
      • 例如前端使用模块化的规范,但是浏览器无法识别,webpack 可以将其转化成浏览器可以识别的
      • 通过模块化开发完项目后,处理模块间的各种依赖以及整合,webpack 可以帮助我们进行模块化开发,并且帮助我们处理模块间的依赖关系
      • 不仅仅是 JavaScript 文件,我们的 CSS、图片、json 文件等等在 webpack 中都可以被当作模块来使用
    • 打包:
      • 将 webpack 中的各种资源模块进行打包合并成一个或多个包(Bundle)
      • 打包过程中对资源进行处理,比如压缩图片,将 scss 转成 css,将 ES6 语法转成 ES5 语法,将 TypeScript 转成 JavaScript 等等操作
  • 和 gult 对比:
    • 核心是 Task
    • 可以配置一系列的 task,定义要处理的事务(例如 ES6、ts 转化,图片压缩)
    • 之后让 gulp 一次执行 task,实现流程自动化
    • 所以 gulp 也被称为前端自动化任务管理工具
    • 只有在工程依赖非常简单,甚至没有用到模块化的概念时才使用 gulp
    • 总结:gulp 更强调前端流程自动化,webpack 更强调模块化开发管理,文件压缩合并、预处理等是它附带的功能
webpack 的安装
  • webpack 依赖于 node 环境,node 环境为了可以正常执行很多代码,必须包含各种依赖的包,而包的管理不可能通过手动去管理,所以会使用 npm(node packages manager)工具
  • 安装 webpack 首先需要安装 Node.js,查看 node 版本:node -v
  • 全局安装 webpack:npm install webpack@3.6.0 -g
  • 局部安装 webpack:npm install webpack@3.6.0 --save-dev
webpack 的起步
  • src:源码文件夹,开发的东西都放入其中
  • dist:distribution(发布),放打包后的文件
    • 通过npm init 创建 package.json,这是 npm 包管理的文件
    • 在 src 下创建 mathUtil.js 写入方法后通过 CommonJS 规范导出
    • 在 src 下创建 main.js (项目入口文件),在其中导入 mathUtil.js 中的函数并使用,打印结果
    • 在终端通过 webpack 将 main.js 打包成浏览器可以识别的 bundle.js 并放在 dist 文件夹下,指令如下:webpack ./src/main.js ./dist/bundle.js
    • 最后在 src 下的 index.html 引入 bundle.js 即可
webpack 的配置
  • 上述的方法每次打包都需要编写入口和出口文件,所以用配置的方式更简便
  • 在文件根目录下创建 webpack.config.js,引入 node 中的 path:const path = require('path')
  • 然后导出该配置文件,导出出口和入口:
module.exports={
  entry:'./src/main.js',
  output:{
    path:path.resolve(__path,'dist'),
    filename:'bundle.js'
  }
}
  • path.resolve(String pathA,String pathB)相当于依次执行,cd pathA,cd pathB
  • 此时直接在终端运行 webpack 就会自动打包
  • 但是直接使用 webpack 会默认使用全局的,例如当我们从 github 上下载一个项目时,需要使用的 webpack 的版本可能就和全局的不一样,此时需要在 package.json 中增加脚本,即在 scripts 增加代码:"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" },,例如增加 build 代替直接使用 webpack,为了使用本地的,相对应的需要进行本地安装:npm install webpack@3.6.0 --sava-dev,此时只需要运行 npm run xx,例如我们编写的 build,即 nom run build 就会执行打包动作
loader 的使用
  • 开发中不仅需要 js 的代码处理,还有加载 css,图片,也包括 ES6 代码转换成 E5,将 .vue 转成 .js 等

  • 对于 webpack 本身的能力来说,不支持这些转化

  • 此时就需要给 webpack 扩展对应的 loader

  • 使用步骤:

    1. 通过 npm 安装要使用的 loader
    2. 在 webpack.config.js 中的 modules 关键字下进行配置
  • webpack 在打包时根据入口找依赖的文件然后打包,所以在 main.js 中直接 require('./css/normal.css')即可

  • 将 css 文件作为模块需要下载对应的 loader

    • npm install css-loader@3.0.0 --save-dev
    • npm install style-loader@3.0.0 --save-dev
  • 下载完成后需要在 webpack.config.js 中配置 module:

  • module: {
    	    rules: [
    	      {
    	        test: /\.css$/i,
    	        use: ["style-loader", "css-loader"],
    	      },
    	    ],
    	  },
    
  • 先使用 style 再使用 css是因为 webpack 在使用多个 loader 时从右往左读取

  • css-loader:加载 css 文件

  • style-loader:将样式添加到 DOM 中

  • less 文件的处理:

    • 先编写 special.less 文件

    • 在 main.js 中引入它,并使用 document.writeln('<h2>你好</h2>')打印一行字

    • 下载 less-loader 和 less

    • 同理在 webpack.config.js 中配置 module 中的 rules

    • module: {
      		rules: [{
      				test: /\.css$/i,
      				use: ["style-loader", "css-loader"],
      			},
      			{
      				test: /\.less$/i,
      				loader: [
      					// compiles Less to CSS
      					'style-loader',
      					'css-loader',
      					'less-loader',
      				],
      			},
      		],
      	},
      
    • 最后执行 npm run build

  • img 文件的处理:

    • 在 normal.css 中使用 test.jpg 作为背景图

    • 下载 url-loader 0.5.9

    • webpack.config.js 中配置使用规则

    • module: {
      		rules: [
      			{
      				test: /\.css$/i,
      				use: ["style-loader", "css-loader"],
      			},
      			{
      				test: /\.less$/i,
      				loader: [
      					// compiles Less to CSS
      					'style-loader',
      					'css-loader',
      					'less-loader',
      				],
      			},
      			{
      				test: /\.(png|jpg|gif|jpeg)$/i,
      				use: [{
      					loader: 'url-loader',
      					options: {
      						//当加载的图片,小于 limit 时。会将图片编译成base64字符串形式
      						//当加载的图片,大于 limit 时。需要使用file-loader模块进行加载
      						limit: 13000,
      						name:'img/[name].[hash:8].[ext]'
      					},			
      				}]
      			}
      		],
      	},
      
    • 当文件大小超出 limit 时,需要使用 file-loader ,直接下载即可 0.11.2

    • 但是生成的图片路径不是我们打包的 dist 文件夹下的,所以只需要在 webpack.config.js 的 output中加入路径

    • output: {
      		path: path.resolve(__dirname, 'dist'),
      		filename: 'bundle.js',
      		publicPath:'dist/'
      	},
      
    • 但是生成的图片名过长且不知道含义

    • 所以在 package.config.js 中配置了 name

      • img:要打包到的文件夹
      • name:获取图片原来的名字,放在该位置
      • hash:8:为了防止图片名称冲突,依然使用 hash,但是只保留 8 位
      • ext:使用图片原来的扩展名
  • ES6 语法处理:

    • 下载 babel:npm install --save -dev babel-loader@7 babel-core babel-preset-es2015

    • 配置:

    • {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env']
              }
            }
          }
      
    • 然后直接 npm run build会发现 bundle.js 中的 const 都变成了 var

webpack 中配置 Vue
  • 安装 vue 的三种方式:1.直接下载引用; 2.CDN 引入; 3.npm 安装

  • 选用第三种:npm install vue --save

  • vue有两种版本,会默认使用一下第一种

    • runtime-only:代码中,不可以有任何 template
    • runtime-compiler:可以有 template,因为有 compiler 编译
  • 此时在 package.config.js 中修改使用版本

  • //与 module同级
    resolve:{
    		//别名的意思,让vue别使用runtime的only版本使用compiler版本
    		alias:{
    			'vue$':'vue/dist/vue.esm.js'
    		}
    	}
    
  • el 和 template区别
  • 在 main.js 中的 Vue 实例中定义 template 属性,,将想要展现在 index.html 的内容写入,在 index.html 编写被挂载的 div 即可,加载时会自动把首页中的 div 替换为 template 中的内容,即将 template 替换 el
  • 也可以将 template 抽离出来定义成组件的形式,例如:const app={template:``,data(){return{}}},然后将 template 赋值为 ‘’,并注册 app 组件
  • 可以更进一步将组件的定义写在 js 文件中,在 main.js 中引入并使用
  • 最后可以更进一步,直接写成 vue 文件
plugin 的使用
  • 插件通常是对现有框架进行扩展

  • webpack 的插件就是对 webpack 现有功能的各种扩展,例如打包优化,文件压缩等等

  • loader 主要用于转换某些类型的模块,是一个加载器,转换器

  • plugin 是插件,是对 webpack 本身的扩展,是一个扩展器

  • 使用过程:安装并配置

  • 添加版权的插件:bannerPlugin

    • 在 webpack.config.js 中引入 webpack const webpack = require('webpack')'
    • 在 module.exports 中加入 plugins 属性:plugins:[ new webpack.BannerPlugin('最终版权归aaa所有') ]
  • Html-webpack-plugin:自动生成 index.html(可以指定模板来生成),还会将打包的 js 文件,自动通过 script 标签插入到 body 中

    • 先下载:npm install --save-dev html-webpack-plugin

    • 然后在 webpack.config.js 中定义并使用

    • const HtmlWebpackPlugin = require('html-webpack-plugin')
      plugins:[
      		new webpack.BannerPlugin('最终版权归aaa所有'),
      		new HtmlWebpackPlugin({
      			template:'index.html'
      		})
      	]
      
    • 此时不需要配置 publicPath,index.html 模板中也不需要引入 bundle.js

  • uglifyjs-webpack-plugin:对打包的 js 文件进行压缩(去空格、注释,定义简单变量名)

    • 下载:npm install --save-dev uglifyjs-webpack-plugin@1.1.1
搭建本地服务器
  • webpack 提供了一个可选的本地开发服务器,合格本地服务器基于 node.js 搭建,内部使用 express 框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果

  • 需要安装 npm install --save-dev webpack-dev-server@2.9.3

  • devServer 也是 webpack 的一个选项:

    • devServer:{
      		contentBase:'./dist',//为哪一个文件夹提供本地服务,默认是根文件夹
      		inline:true//页面是否要实时刷新
          	//port:配置端口号
          	//historyApiFallback:在 SPA 页面中,依赖 HTML5 的 history 模式
      	}
      
  • 此时不能直接运行 webpack-dev-server,因为在终端运行这个命令会默认从全局找,要从本地找需要在 package.json 中定义 scripts:"dev":"webpack-dev-server",在后面加上 --open 可以在运行 npm run dev 时直接打开网页

  • 此时执行 npm run dev 在更改文件时,浏览器会自动刷新更改的内容,无需 npm run build

webpack 配置文件分离
  • 安装合并文件的 webpack-merge:npm install --save-dev webpack-merge

  • 将公共部分,生产环境部分以及开发环境部分分为三个文件,在生产与开发中分别引入公共部分。

  • 在生产与开发中都引入 webpack-merge 并使用它合并公共部分与当前部分

  • 以 dev.config.js 为例:
    const WebpackMerge = require('webpack-merge')
    const baseConfig = require('./base.config')
    module.exports = WebpackMerge.merge(baseConfig,{
    	devServer:{
    		contentBase:'./dist',
    		inline:true
    	}
    })
    
  • 修改 package.json 使用自定义的配置文件 :"build": "webpack --config ./build/prod.config.js",

  • 由于目录的更改,修改出口路径配置output: { path: path.resolve(__dirname, '../dist'), filename: 'bundle.js', //publicPath:'dist/' },

Vue CLI(脚手架)

介绍和安装
  • CLI 是 Command-Line Interface,翻译为命令行界面,但是俗称脚手架
  • Vue CLI 是一个官方发布 vue.js 项目脚手架
  • 使用 vue-cli 可以快速搭建 Vue 开发环境以及对应的 webpack 配置
Vue CLI 使用前提-Node,webpack
  • NPM 的全称是 Node Package Manager
  • 是一个 NodeJs 包管理和分发工具,已经成为了非官方发布 Node 模块(包)标准
Vue CLI 的使用
  • 安装 Vue 脚手架:npm install -g @vue/cli
  • 拉取脚手架2:npm install @vue/cli-init -g
  • 脚手架2创建项目:vue init webpack my-project
  • 脚手架3创建项目:vue create my-project
  • 安装不成功时可能需要清除 npm-cache,可以使用命令行,也可以直接删除C:\Users\asus\AppData\Roaming\npm-cache
Vue CLI2初始化项目过程
  • Project name:项目名字,默认和文件夹名相同(不能包含大写)

  • Project description:项目描述

  • Author:作者(默认根据全局的 .gitconfig 中的 user)

  • Vue build:使用 runtime+compiler 或 runtime-only

  • Install vue-router?:是否安装全家桶之一,路由

  • Use ESLint to lint your code:是否对 ES 代码(js代码)进行限制,使得 js 代码很规范,不规范就报错,例如 const name ='sky' 等号右边没空格就会报错

  • Set up unit tests:单元测试

  • Setup e2e tests with Nightwatch:端到端测试,安装 Nightwatch 利用 selectnium 或 webdriver 或 phantomjs 等进行自动化测试的框架

  • Should we run npm install for you after the project has been created

    • Yes,use NPM
    • Yes,use Yarn

    使用 npm 还是 yarn js包管理工具

  • 扩展点:node 可以直接运行 js 代码,他是用 C++ 编写,核心之一是 v8 引擎,平时我们的 js 需要转换为字节码文件,然后在浏览器上运行。而 v8 引擎可以直接将 js 文件转为二进制文件,然后运行。这就可以让我们不使用浏览器作为底层的支撑来运行 js 代码。例如编写 test.js 文件,在终端直接输入 node test.js 就会执行其中的代码

  • 自动生成的项目目录:

    • build:webpack 相关配置文件
    • config:环境参数,里面的 index.js 中可以根据 useEslint 来判断是否使用 ESlint
    • node_modules:依赖的 node 相关对的模块
    • src:写代码的地方
    • static:静态资源,例如 src 中的的图片文件会根据大小决定是否转换成 base64 字符串文件,但是 static 中的文件会原封不动上传到浏览器
    • .babelrc:ES6 代码相关转化的配置文件
    • .editorconfig:对代码做一些统一(缩进风格,缩进大小,编码,换行,自动在最后一行插入新的一行 【建议用这种风格】,自动清除空格…)
    • .eslintignore:某些代码中编写不规范可以忽略
    • .gitignore:某些东西不需要上传到服务器
    • .eslintrc.js:代码检测配置相关
    • .postcss:进行 css 转化时配置的东西,一般不需要修改
    • index.html:html 模板,打包时根据该模板创建 html 打包到 dist
    • package.json:node 中的包的相关东西
    • package-lock.json:node_modules 中的依赖版本可能和 package.json 中的不一致。因为 package.json 中写的版本号例如是:^3.0.0,此时是小版本可变的版本,例如 3.0.1;~3.0.0是中小版本可变的,例如 3.1.1。这时需要这两个文件间的映射关系,这就是 package-lock.json
    • README.md:写一些文档相关的东西
runtime-compiler 和 runtime-only 的区别
  • 只有 main.js 有区别,only 性能更高,代码量更少
    • 其中 compiler 中创建 vue 实例时,使用了 template 属性,会先将 template 保存,然后分析为 ast(abstract syntax tree抽象语法树),然后使用 rander 函数渲染为 vdom(虚拟DOM),最后转换为真实的 DOM,也就是 UI
    • 而 only 直接使用 render ,然后…
    • render 中的 h 函数其实是 createElement(‘标签’,{标签属性},[标签内容])
      • 普通用法:createElement('h2',
      • {class:'box'},
      • ['hello world',createElement('button',['按钮'])])
      • 传入组件对象:const cpn = {template:,data(){return{...}}}
      • 然后直接 createElement(cpn)
    • 那么 .vue 文件中的 template 是由谁处理的:vue-template-compiler,再 main.js 中直接打印引入的 Vue 会发现,他已经是一个 Object 对象,且拥有 render 属性
  • 因此,所有 vue 文件其实都不包含 template,也就不需要 runtime-compiler,直接用 only 即可
Vue CLI3创建目录和目录结构
  • 和 vue-cli2的不同:
    • cli3 是基于 webpack4 打造的,cli2 基于 webpack3
    • cli3 的设计原则是“0配置”,移除了配置文件根目录下的 build 和 config 目录
    • cli3 提供了 vue ui 命令,提供了可视化配置,更加人性化
    • 移除了 static 文件夹,新增了 public 文件夹,并将 index.html 移动到 public 中
  • 创建项目步骤:
    • Please pick a preset(请选择一个预配置)
      • default(babel,eslint)
      • Manually select features(手动选择特性)√
    • 空格选择/取消自己想要的特性
      • Babel √
      • TypeScript
      • Progressive Web App(PWA)Support(先进的 Web App)
      • Router
      • Vuex
      • CSS Pre-processors
      • Linter/Formatter
      • Unit/Testing
      • E2E Testing
    • Where do you prefer placing config for Babel,PostCSS ,ESlint etc.某些配置文件是更喜欢用怎样的方式存放
      • In dedicated config files 用一个独立的文件存放 √
      • In package .json 放在package .json 这个文件
    • Save this as a preset for future projects?(y/N),是否保存本次配置为将来可选配置。.vuerc中可以删除保存的配置
    • Pick the package manager to use when install dependencies:
      • Use Yarn
      • Use NPM √
  • 项目目录:
    • node_modules:依赖相关
    • public:静态资源
    • src:编写代码处
    • .browserslistrc:浏览器配置
    • .gitignore:共享忽略
    • babel.config.js:ES6 代码转换配置
    • package.json:包中相关的东西
    • package-lock.json:版本映射
    • postcss.config.js:css转化
    • README.md:文档相关
配置文件的查看和修改
  • 修改配置方式一:执行vue ui然后操作

  • 修改配置方式二:在 node_modules/@vue/cli-service 中有 webpack.config.js ,同级的 lib/config下有具体的配置文件

  • 修改配置方式三:当前目录创建文件 vue.config.js,自己 module.exports={}

  • 自定义配置(起别名)

箭头函数
  • 只有一个传参时可以省略传参外的括号
  • 代码块只有一句时可以省略{}
  • 使用场景:把一个函数作为参数调用一个函数时使用箭头函数比较多
  • 箭头函数中的 this 引用的是最近作用域中的 this,也就是向外层一层层查找 this,直到有 this 的定义

Vue-Router

vue-router基本使用
  • vue-router 是 Vue.js 官方的路由插件,它和 vue.js 是深度集成的,适合用于构建单页面应用

    • 基于路由和组件,路由用于设定访问路径,将路径和组件映射起来
    • 在 vue-router 单页面应用中,页面的路径的改变就是组件的切换
  • 路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动

  • 路由器提供了两种机制:路由和转送

    • 路由:决定数据包从来源到目的地的路径
    • 转送:将输入端的数据转移到合适的输出端
  • 路由中有一个非常重要的概念叫路由表

    • 路由表本质就是一个映射表,决定了数据包的指向
  • 后端渲染:以前网页通过类似 jsp ,php 开发,前端发送请求的 url,后端在服务器直接通过该 url 对应的 html + css + java 渲染成网页,然后返回前端渲染完毕的网页

  • 后端路由:此时可以发现,每个 url 对应了一个网页,而处理 url 和页面之间映射关系的是后端,因此,这种关系就是后端路由

    • 也个页面有自己对应的网址,也就是 URL
    • URL 会发送到服务器,服务器会通过正则对该 URL 进行匹配,并且最后交给一个 controller 进行处理
    • controller 会进行各种处理,最终生成 HTML 或者数据,返回给前端
    • 这就完成了一个 IO 操作
    • 上述操作就是后端路由
  • 后端路由优点:

    • 当我们页面中需要请求不同的路径的内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户端
    • 这种情况下渲染好的页面,不需要单独加载任何的 js 和 css,可以直接交给浏览器展示,这样也有利于 SEO 的优化
  • 后端路由的缺点:

    • 一种情况是整个页面的模块由后端人员来编写和维护的
    • 另一种情况是前端开发人员如果要开发页面,需要通过 PHP 和 Java 等语言来编写页面代码
    • 而且通常情况下 HTM 代码和数据以及相应的逻辑会混在一起,编写和维护都是非常糟糕的事情
  • 前后端分离阶段:后端只负责提供数据,不负责任何阶段的内容

    • 随着 Ajax 的出现,有了前后端分离的开发模式
    • 后端只提供 API 来返回数据,前端通过 Ajax 获取数据,并且可以通过 JavaScript 将数据渲染到页面中
    • 这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上
    • 并且当移动端(ios/Android)出现后,后端不需要进行任何处理,依然使用之前的一套 API 即可
    • 目前很多的网站依然采用这种模式开发
    • 前端渲染:首先是发送页面的 url 到静态资源服务器,该服务器返回 url 对应的 html+css+js ,而浏览器执行 js 代码时,向提供 API 接口的服务器发送 url,这时该服务器返回相应的数据,前端的浏览器根据数据进行例如创建 div 的操作,完成整个页面的渲染,这就是前端渲染
    • 总结:浏览器中显示的网页中的大部分内容,都是由前端写的 js 代码在浏览器中执行,最终渲染出来的网页,此时就没有前后端路由的概念了
  • 单页面富应用阶段:

    • 其实 SPA 最主要的特点就是在前后端分离的基础上加了一层前端路由
    • 也就是前端来维护一套路由规则
    • 整个网页只有一个 html 页面
    • 只有一个 html 文件,故而只有一个 url,此时根据这个 url 向静态服务器请求资源,只有一套对应的 html+css+js,包含了整个应用需要的全部数据 。而当我们在 这个html 中点击某个按钮展示新的界面时,新界面也有一个对应的 url,而前端会有一种技术支撑能够使得该 url 从请求到的那套数据中抽取自己所需要的那部分。这种管理 url 与对应页面的映射关系的技术支撑就是前端路由
    • 前端路由的核心就是就是:改变 URL,但是页面不进行整体的刷新
  • url 的 hash

    • 也就是锚点(#),本质上是改变 window.location 的 href 属性
    • 我们可以通过直接赋值 location.hash 来改变 href,但是页面不发生刷新
  • html5 的 history.pushState(栈结构),当 history.pushState({},'','aaa(url)')两次不同 url时,此时 history.back(), 会返回 bbb 的前一个也就是 aaa,就是栈的后进先出,这两个操作相当于入栈和出栈

  • history.replaceState({},’’,‘home(url)’),这时就不是栈操作,而是直接替换,无法点击返回按钮

  • history.go(-1) = history.back(),因为 go(-1)表示弹出栈中几个元素,正数表示入栈操作

  • history.forword()=history.go(1)

安装和使用 vue-router
  • 安装 npm install vue-router --save

  • 大体使用步骤

    1. 导入路由对象,并且调用 Vue.use(VueRouter)
    2. 创建路由实例,并且传入路由映射配置
    3. 在 Vue 实例中挂载创建的路由实例
    • 具体步骤如下:

      1. 要想使用路由首先导入路由:import VueRouter from 'vue-router'
      2. 待会使用这个路由插件需要用到 Vue.use()安装插件,那就得先导入 Vue: import Vue from 'vue'
      3. 可以让 Vue 使用插件了:Vue.use(VueRouter)
      4. 准备工作做完就可以创建路由实例了:const router = new VueRouter({routes})
      5. 创建肯定要配置路由和组件的映射关系:const routes = [],用字面量家强写法加到步骤4之中
      6. 创建完了 index.js 就该使用了,他想使用你就得导出:export default router
      7. 可以导入了:import router from './router',导入目录的话默认会找该目录的 index.js,所以就不需要写成 import router from './router/index.js'
      8. 再使用字面量增强写法使用不就好了:
      new Vue({
        el: '#app',
        router,//<==
        render: h => h(App)
      })
      
  • 路由映射配置和呈现:

    • 创建路由组件(组件/页面):随便建两个组件导出
    • 配置映射关系:routes 就是用来给你配映射关系的,你用地址和组件来表示一对绑定关系:{path:'home',component:Home},别忘了先引入组件
    • 使用路径:(是 vue-router 中内置组件,会被渲染成)(根据当前路径,动态渲染出不同的组件),App.vue 中挂载了实例,所以用它作为显示的地方,直接用 router-link 标签表示你要跳转的组件,to 属性就写你配置的映射关系的 path:<router-link to="/home">Home</router-link>,光有这个他不知道你要显示在哪,所以用 router-view 标签来显示,看他的位置就行:<router-view></router-view>
      • 网页的其他内容和 router-view 同级
      • 路由切换时,切换的是 router-view 挂载的组件,其他内容不变
  • 路由的默认路径:按道理应该一进来就默认显示首页,而不是需要点击首页次才显示首页组件。

    • 会想到配置路由规则 path 为 ‘’,然后组件为首页,但是这样的话,首页的路径就是没有 /home 的,点击 Home 后进入到有 /home 的,一个组件配置了两个路径
    • 这时就可以使用重定向{path:'',redirect:'/home'}
  • 但是默认的哈希模式下的 url 总是有个#,感觉有点丑,所以用比较好看的 HTML5 的 history 模式,这只需要在配置路由对象的时候加上模式属性,改成 history

const router = new VueRouter({
  //5.配置路由和组件间的关系
  routes,
  mode:'history'
})
  • router-link 补充:

    • tag:它会默认被渲染成 a 标签,你可以加 tag 属性,决定它被渲染成什么控件:<router-link tag="button">
    • replace:默认是入栈出栈的模式,所以切换组件后可以返回,你可以增加 replace 属性让他使用替换的模式,就不能返回
    • active-class:比如修改 tag 为按钮后,选中的按钮会有一个 class 为 router-link-active,你可以修改你想要的被选中的样式,但是你可能会觉得这个类名太长,你可以<router-link active-class="active">这样来修改类名,但是如果要修改的 router-link 很多,就可以直接配置在路由中 :linkActiveClass:'active'
  • 通过代码跳转路由:你可能会想直接 history.pushState(),但是这样绕过路由来实现有点不是人,首先定义一个 button(你会发现这对应了上述的 tag 属性,你可以添加类,所以也对应了 active-class 属性),这时你添加点击事件,事件代码:this.&router.push('path'),就可以跳转到该路径对应组件,你会发现里面的 path 对应了 to 属性,而 push 对应了默认的入栈出栈模式,那么大胆猜测会有:this.$router.replace('path'),实验发现确实有,这就对应了模式

  • 动态路由:

    • 在某些情况下,一个页面的 path 路径可能是不确定的,比如我们进入用户界面路径为/user/{用户id},这种 path 和 component 的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)
    • 要想实现,先建组件 User,然后配置路由映射,此时用特殊的写法:path:'/user/:userId',这个 userId,到时候可以通过 KaTeX parse error: Expected 'EOF', got '&' at position 197: …ed 属性,返回 `this.&̲route.params.us…route 代表当前活跃的路由(一个页面打开时总不会同时有两个 url)
  • vue-router打包文件的解析:

    • 打包程序后,发现有一个 index.html,statci 下有全部 css 压缩到一个文件的文件,还有三个 js 文件,其中 :

      • app:当前应用程序开发的所有代码(业务代码)
      • mainfest:为打包的代码做底层支撑,里有我们用到了很多不同模块化规范的导入导出,这里面就打包了类似这种复杂的处理的底层的支撑,有个_webpack_requore_函数,有空读一下
      • vendor(提供商,第三方):例如 vue/vue-router/axios
  • 路由懒加载的使用:

    • 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。因为所有业务代码都会被压缩到一个 js 中,当你向静态服务器请求时,如果 js 文件过大,你的网页就会出现短暂的空白,这样用户的体验很不好
    • 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了,即,用什么拿什么,用到时才加载,这时候使用路由懒加载就可以了
    • 路由懒加载主要就是将路由对应的组件打包成了一个个 js 代码块,只有在这个路由被访问的时候,才加载对应的组件
    • 懒加载的三种方式:
      • 结合 Vue 的异步组件和 Webpack 的代码分析:const Home = resolve => {require.ensure(['../components/Home.vue'],()=>{resolve(require('../components/Home.vue'))})}
      • AMD 写法:const About = resolve=>require(['../components/About.vue'],resolve)
      • 在 ES6 中,我们可以有更简便的写法来组织 Vue 异步组件和 Webpack 的代码分割:const User = ()=>import('../components/User.vue')
vue-router嵌套路由
  • 比如在 home 页面中,我们希望通过 /home/news 和 /home/message 访问一些内容,有子路径的感觉

  • 一个路径映射一个组件,访问这两个路径也会分别渲染这两个组件

  • 使用步骤:

    • 创建子组件,配置映射关系,此时需要用到 childeren 属性:

    • {
          path:'/home',
          component:Home,
          children:[
              {//首页默认显示新闻
                  path:'',
                  redirect:'news'
              }
              {
                  path:'news',
                  component:HomeNews
              }
          ]
      }
      
    • 在组件内部使用,例如:<router-link to="/home/news">

vue-router参数传递
  • 传递方式一:params(上面用过)
    • 配置路由时用固定的格式例如path:'/user/:id'
    • 使用 router-link 时也用对应的格式 :to="'/user/'+id"
    • 使用传递过来的参数时可以:this.$route.params.id
    • 传递后的路径为:/user/123
    • 传递一个简单的数据
  • 传递方式二:query的类型
    • 配置路由时用普通的格式:paath:'/user'
    • 传递的方式:对象中使用 query 的 key 作为传递的方式,:to="{path:'/user',query:{id:'123',name:'john'}}",此处的 query 就是下文的查询
    • 要想使用传递的参数,猜测同理:this.$route.query.id,实验证明没错
    • 传递后的路径为:/user?id=123&name=john
    • 可以传递对象
    • 使用此方式实现代码控制路由跳转:this.$router.push({path:'/profile',query:{id:'123',name:'john'}})
  • 统一资源定位符(URL):
    • 协议://主机:端口/路径?查询
    • scheme://host:port/path?query#fragment
vue-router-router 和 route 的由来
  • this.$router 就是我们在 router/index.js 中 export 的路由
  • this. r o u t e 就 是 当 前 活 跃 的 路 由 , 也 就 是 r o u t e r / i n d e x . j s 中 我 们 定 义 的 r o u t e s 中 的 当 前 活 跃 的 对 象 , 但 是 你 可 能 会 疑 惑 我 们 定 义 的 p a t h 和 c o m p o n e n t 怎 么 打 印 ‘ t h i s . route 就是当前活跃的路由,也就是 router/index.js 中我们定义的 routes 中的当前活跃的对象,但是你可能会疑惑我们定义的 path 和 component怎么打印 `this. routerouter/index.jsroutespathcomponentthis.route` 时没有打印出来,而是 fullPath、hash、meta…属性,因为这是它自动加进去的属性,你不需要操心
  • 功能实现的核心:Vue 的设计中有一点:所有的组件都继承自 Vue 类的原型,因此你会看到在 vue-router 的源码中会有 Vue.componnet(“RouterView”,View),这也就是一种注册全局组件的方法,这里面就体现的这个设计特点,所以我们能够在任何地方使用 ,router-link 同理
  • 那你根据这个可以推断,我们能在全局使用 this.$router 肯定也有类似的操作,没错,观看源码会发现有一个 Object.defineProperty(obj,'name','john') 方法,这就类似:let obj = {name:'john'},它在里面直接 Object.defineProperty(Vue.prototype,'$router',{xxx}),而括号中的 xxx 是 return 了一个this._routerRoot._router,继续找这是个啥会发现this._routerRoot = this//直接把vue实例给他,还有this._router = this.$options.router//Vue实例的option的router不就是main.js中你定义的Vue实例的router,也就是我们导出的router,虽然看着很怪异,但是实际上这两句合起来不就是 this._routerRoot._router = this.$options.router,那么最后也就变成了Vue.prototype.$router = router(我们定义并导出的router)
  • this.prototype
vue-router全局导航守卫
  • 我们在路由跳转的过程中,可能会想在这个过程中进行一些操作,比如我想在卡换组件时,更改浏览器的标题,这时我们会想到生命周期函数,比如在组件被创建时:created(){document.title="xxx"},但是这样的话岂不是意味着每个页面都要加一下生命周期函数,那么既然的都是路由的跳转,为什么不能监听一下路由的跳转,发现跳转时改成对应的标题,这时就可以使用全局导航守卫:router.beforeEach((to,from,next)=>{document.title=to.matched[0].meta.title next()}),这里面的 to 就是你要跳转的路由,那你当然也得配置路由,(next()必须调用,调用之后才能进入下一个钩子,否则你就卡在了快要跳转的状态,跳不过去),例如:

  • {
        path: '/profile',
        component: Profile,
        meta:{
          title:'档案'
        }
      }
    
  • 为啥用 matched[0] 而不是直接 meta,因为当你使用了嵌套路由的时候,比如:/user/news,你要显示的是首页才对,但是这时你没给它配过 title,所以用 matched[0] 也就是父路由,肯定不会错

  • meta(元数据):描述数据的数据

  • beforeEach:前置钩子(钩子就是回调,其实他更希望被称为前置守卫),在路由跳转前执行对应代码

  • 但是如果是后置钩子,比如 afterEach,不需要主动调用 next() 函数

  • 上面在 router/index.js 编写的其实是全局守卫,此外还有:

    • 路由独享的守卫:比如你想只在跳转到 user 前进行一些操作,你可以在配置他的路由的地方:

      • {
            path:'/user',
            component:User,
            beforeEnter:((to,from,next)=>{})//用法和全局守卫一致
        }
        
    • 组件内的守卫:比如 Home 页面,在其中直接:

      • export default {
            name: 'Home',
            beforeRouteEnter(to, from, next){
              console.log('首页我来了')
              next()
            }
          }
        
keep-alive
  • 当我们浏览 home 中的 message 时,跳转到其他路由后再回来,他就又变成了默认的 home 中的 news,因为路由跳转时,当前组件被销毁,回来时重新渲染,不想这样咋办?用 keep-alive

  • keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

  • router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存

  • 当你使用了 keep-alive 后,你就多了两个类似生命周期的函数:

    • activated:当路由处于活跃状态后调用,你可以理解为跳转到该路由对应的组件时,此时 this.$route.path为当前组件的路由
    • deactivated:当路由不活跃时调用,也就是已经到其他组件的页面了,那么此时的 this.$route.path既然是记录当前活跃的路由,必然也变成了其他路由,假如路由跳转为(A=>B),则当前这个是B路由
  • 那么究竟能如何实现当你浏览/home/messages时跳转到例如/user,再回到 /user 后依然是在 message 下,

    • 首先自然不必多说,我这组件总不能每次都销毁吧,App.vue 中 keep-alive 来:<keep-alive><router-view/></keep-alive>

    • 但是吧,我每次点击 Home 时,其实是跳转到 /home,然后重定向到/news,那我得把重定向去掉,我在每次路由要活跃时手动路由跳转离开时的路径,第一次来的时候默认是 /news:

    • activated(){
            this.$router.push(this.path)
          },
      
    • 那我每次离开怎么记录离开时的路径呢,前面讲了 deactivated 不行啊,那不是也刚学了路由守卫吗,组件内路由守卫整起:

    • beforeRouteLeave(to,from,next){
            this.path = this.$route.path
            next()
          }
      
    • 或者我用一个全局变量来记录,在 Home 创建和销毁前同理操作,等我学了 Vuex再说吧

    • 那么能不能我想要让某些组件还是会正常创建和销毁呢,这时可以使用 keep-alive 的 exclude 属性,比如 user 和 profile 给我排除掉:<keep-alive exclude="User,Profile">,那这个 User 又是哪里来的啊?这里难得地用上了你 export 这个组件时的 name 属性

TabBar案例:

样式:
  • 我们的样式可能需要很多,所以我们抽取公共样式 base .css,但是怎么让我们的项目都使用这个样式呢?我们首先想到了 main.js 作为打包的入口,在其中引用就好了,但是这样有点乱,既然是样式,App.vue 又作为全局的底层 vue,何不在 App.vue 中的 style 下引入(注意格式,注意结尾的分号):@import ./assets/css/base;

  • 我们总不能直接在 App.Vue 写 html,不然你学什么 vue,建一个 TabBar 组件。中间使用插槽来存放一个个的导航按钮,那把导航按钮也封装成 TabBarItem,里面有图标和文字,所以用两个具名插槽占位

  • 但是此时又有一个问题:你点击这个标签时,按道理它应该会变成活跃的标签图片。我初步设想是通过图片的 src 的动态绑定,点击标签时就改为活跃的图片,但是这样的话别人如何使用我所定义的 tabbaritem 呢?视频中是再增加一个具名插槽,你动态决定是否显示,那么如何实现动态显示呢,在 TabBarItem 中定义一个是否活跃的变量,两张图片互斥,正好用 v-ifv-else,变量还能决定选中标签时的字体的样式变化,但是直接在 slot 中写 v-if:class,会有当你使用插槽时,你定义的东西把原本定义的插槽的全部东西都替换的风险,所以我们用一个 div 包裹我们的插槽来实现

  • 然后实现路由跳转:首先按钮的点击事件的监听肯定是放在 TabBarItem 中,此时通过代码的形式实现路由跳转,但是 path 的值应该如何获取是一个问题,此时想到了父子组件传值,通过 props:<tab-bar-item path="/path">

  • 路由跳转时,对应的活跃的标签的样式并未改变,这时我想到了在 activated 属性中将 this.isActive 的值改为 true,默认为 false,但是并未生效,又想到通过组件内路由守卫beforeRouteEnter 来更改 this.isActive ,结果仍未生效。且以上两种方法都不报错。最后根据视频中的 computed 属性来完成:isActive(){return this.$route.path.indexOf(this.path) >= 0}

  • 字体颜色的改变也不能写死,需要封装成可以根据用户的选择改变颜色的组件,不然封装得不够彻底,应该能让别人动态决定,那就把字体要改变的颜色也用父子组件传值的形式传过去,那么此时也不能使用动态绑定 class 了,改为使用动态绑定 style:style:{color:activeStyle},而 activeStyle 用一个计算属性去返回,根据是否是当前活跃状态来判断返回用户传入的颜色或是没有样式:

  • activeStyle(){
        return this.isActive?{color:this.activeColor}:{}
    }
    
  • 但是这样把对于组件的具体使用全部放在 App.js 有点不够好,所以把对于 TabBar 组件的使用的部分代码抽离出来写成 MainTabBar.vue 文件,然后在 App.js 中注册组件并使用即可

  • 起别名:webpack.config.js 中有个 resolve 属性可以处理一些东西,其中可以通过 alias 属性来为文件起别名,例如:alias: {'assets':resolve('src/assets')},此时需要注意,在 html 中的控件的src 属性需要写成 ~assets,而在 js 代码中 import 时就可以直接使用了

Promise

Promise简介
  • Promise 是异步编程的一种解决方案

  • 何时需要:

    • 网络请求:当你点击什么进行网络请求时,如果是同步,此时你没请求完总不能你就什么也干不了吧,所以此时就需要异步(setTimeout就能模拟异步操作),先让你去执行下面的代码,等请求完了执行异步函数中的回调函数。

    • 但是当网络请求很复杂时,会出现回调地狱,比如通过 url1 加载 data1,对 data1 进行一系列操作以后,但是此时需要通过 data1 取出 url2,然后返回 data2,,对 data2 进行一些列操作以后,但是 data2 又需要取出 url3,返回 url3…这种情况下你的代码不易维护且不够优雅,你完全分不清你现在处理的代码是第几次的网络请求,你就无法对应的进行处理,这时就需要 Promise

    • 我们把你要异步执行的函数写进 Promise 回调的函数的方法体中

    • new Promise((resolve,reject)=>{
          setTimeOut(()=>{
              
          })
      })
      
    • 将要执行的函数的方法体替换为 resolve,这就是你的网络请求部分

    • new Promise((resolve,reject)=>{
          setTimeOut(()=>{
              resolve()
          })
      })
      
    • 然后在最外面加上 .then,在这里面继续回调函数,写上你第一次网络请求后要执行的代码,也就是第一次的异步函数的方法体

    • new Promise((resolve,reject)=>{
          setTimeOut(()=>{
              resolve()
          })
      }).then(()=>{
          console.log('aaa')
      })
      
    • 如果在你要执行的方法体中还有异步函数,那就继续创建新的 Promise,写入函数,方法体为resolve,然后最外面加上 .then,其中写入你要执行的原本函数的方法体

    • new Promise((resolve,reject)=>{
          setTimeOut(()=>{
              resolve()
          })
      }).then(()=>{
          console.log('aaa')
          return new Promise((resolve,reject)=>{
              setTimeout(()=>{
                  resolve()
              })
          })
      }).then{
          console.log('bbb')
      }
      
    • 执行步骤:new=>构造函数(1.保存了一些状态信息,2.执行传入的函数),在执行传入的回调函数时,会传入两个参数,resolve,reject,这两个函数本身也是函数,当请求成功,执行 resolve,resolve 中可以传参,然后 .then 中可以接收参数并处理,如果请求失败,也可以调用 reject 传参,然后让 .catch 来进行失败后的操作,这也是一种链式编程的思想,目前很流行

Promise的三种状态
  • 异步操作之后会有三种状态:
    • pending:等待状态,比如正进行网络请求,或者定时器没有到时间
    • fulfill:满足状态,当我们主动回调 resolve 时,就处于该状态,并且会回调 .then()
    • reject:拒绝状态,当我们主动回调 reject 时,就处于该状态,并且会回调 .catch()
  • Promise 还有一种写法就是把 then 和 catch 要处理的都放在 then 中,也就是在 .then() 中传入两个回调函数,第一个处理当 resolve 时,第二个处理当 reject 时
  • Promise 的链式调用,首先还是新建 Promise,但是如果你不需要 reject 你可以只传入 resolve,然后就是在其中进行网络请求,然后 .then() 进行处理语句,然后这时你可以不需要大费周章创建 新Promise 再 resolve,例如return new Primise(resolve=>{resolve(xxx)}),而是可以直接 return Promise.resolve(xxx),再简便一点,你还可以直接 return xxx,这本身就会被解析成一个 Promise 对象(好像是这么说,大概这意思),如果是 reject,同理 return Promise.reject(xxx),再简便一点就 throw xxx
  • Promise 的 all 方法,可以在多个异步请求都请求完成后再进行相关的操作,此时可以 Promise.all([Promise1,Promise2]).then(results=>{}),rusults 就是返回的所有结果,是一个数组对象

Vuex

Vuex简介
  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
  • 他采用集中式存储管理应用改的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
  • Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配 置的 time-travel 调试,状态快照导入导出等高级调试功能
  • 状态管理是什么?你可以将其看成把需要多个组件共享的变量全部存储在一个对象里面,然后,将这个对象放在顶层的 Vue 实例中,让其他组件可以使用,此时,多个组件可以共享这个对象中的所有变量属性,但是如果仅仅如此的话何必要 Vuex,因为 Vuex 能够让这个全局变量响应式
  • 何时需要 Vuex:
    • 比如用户的登陆状态,用户名称、头像。地理位置等,访问很多界面都需要 token,很多数据你得登陆以后才能访问
    • 比如商品的收藏按钮,购物车中的物品(你在很多界面都可以收藏、加购)
如何使用
  1. 老样子,先安装:npm install --save vuex
  2. 和 vur-router 类似,创建自己的文件夹 store(因为其实是使用 vuex 的 store),然后创建 index.js
  3. 同理引入 Vue 和 Vuex,然后 Use 一下(会自动执行 install),然后创建 store 实例并导出,最后再 main.js 中导入使用即可
  4. 设置时把全局变量写在 store 的 state 中,使用时只需要 $store.state.xx 即可。
  • 但是,修改时不能直接使用调用时的写法。
  • ​ Backend API
  • ​ ^
  • ​ I
  • ------------------>Actions------------------->
  • ^ v
  • ^ v
  • Vue components <<< state <<< mutations <------> Devtools
  • 状态管理图例如上,state 的显示肯定是在 Vue components 中,然后 Vue components 进行一些 Actions,去调用 store 中的 mutations 来改变 state,为什么要这么做呢,因为当多个组件使用同一个全局变量时,你如果直接在 Vue components 中改变 state 的属性的值,谁知道是谁改了,这就不易维护。所以需要通过 mutations 来改变,Devtools 就是 Vue 官方的浏览器插件,可以管理 Vue,比如记录谁更改了 state 的某个属性,那么 Actions 又是干嘛的,因为你的某些操作可能是异步的,如果你也直接用 mutations 的话他还是分不清是谁改了 state,所以你需要把异步操作写在 Actions 中,可以看到他对应的就是 Backend API,也就是调用后端 API 的操作,这不就是网络请求吗,也就是异步操作。
  • 简单使用 mutations:
    • 在其中定义方法,传参是 state,例如:mutations:{increment(state){state.counter++}},而在组件中使用该方法时并不是直接this.$store.mutations.increnet,而是通过 commit:this.$store.commit('increment')
Vue核心概念
  • State:

    • 单一状态树,也叫单一数据源。我们知道我们的个人信息比如户口、医疗、文凭、房产等信息被存在不同的系统,但是当你需要比如入户某个城市需要很多相关的信息,这是就得各处去取,这样就很低效,而且不方便管理,所以你可能会类似地创建多个 store,这是不可取的,官方建议你只创建一个,把所有想要存储的状态都存在同一个 store 的 state 中
  • Getters:有时候我们需要从 store 中获取一些 state 变异后的状态,这就类似于 Vue 实例中的 computed 属性,需要对数据进行一定处理后再展示,可以使用属性也可以使用函数 的写法,传参除了 state 也可以有 getters

    • getters:{
          //more21:state=>state.students.filter(item=>item.age>21),
          more21(state){
           return state.students.filter(item=>item.age>21)
          },
          more21Length:(state,getters)=>getters.more21.length
        },
      
    • 如果想要自定义返回大于多少岁的怎么做呢?返回函数:

    • moreAge(state){
            return function(age){
              return state.students.filter(s=>s.age>age)
            }
          }
      或用箭头函数
      moreAge:state=>(age)=>state.students.filter(s=>s.age>age)
      
  • Mutations:Vue 的 store 状态的唯一更新方式

    • Mutation主要包括两部分

      • 字符串的事件类型:type(可以简单理解为函数名)
      • 一个回调函数:handler,该回调函数的第一个参数就是 state
    • Mutaition 传递参数:传递的参数被称为 Mutations 的 payload(载荷)

    • 用法:

    • mutations:
      
      add(state,num){
            state.counter+=num
          }
      ====================================
      methods:
      
      add(){
            console.log(typeof this.inputNumber)
            this.$store.commit('add',parseInt(this.inputNumber))
          //还有一种提交风格:
          //this.$store.commit({
              //type:'add',
              //num:parseInt(this.inputNumber)
          //})
          //此时接收到的 num 会变成你传过去的这个对象,你得改成:state.counter+=num.num
          //此时的num其实写成payload更为合适
          }
      
    • Mutations响应规则:Vuex 的 store 中的 state 是响应式的,当 state 中的数据发生改变时,Vue 组件会自动更新,这就要求我们必须遵守一些 Vuex 对应的规则

      • 提前在 store 中初始化好需要的属性,这些属性会被加到响应式系统中,当响应式系统监听到属性的变化,会通知所有界面中用到该属性的地方,让界面发生刷新,但是如果你是为一个对象添加新的属性,例如:state.info['address']='Beijing',这样就无法响应式,想要实现方法如下,如下方法可以将这些属性添加到响应式系统中
        • 方法一:Vue.set(state.info(obj),'address'(property),'Beijing'(value))
        • 删除时:delete state.info.age是不可行的,需要 Vue.delete(state.info,'age')
        • 方法二:用新对象给旧对象重新赋值:state.info={...state.info,'address':'Beijing'}
    • Mutation常量类型:当你再 mutations 中定义多个方法时,你在组件中用到方法然后提交时,你需要花费大量时间去记住这些方法,多个文件来回切换来查看方法名称,还有可能写错,因此官方推荐你把方法名写成常量,导入时记得语法,修改 mutations 中的方法名时可以:[常量名](){},提交时也直接使用常量

    • index.js:
      import {ADD} from './mutation-type.js'
      [ADD](state,num){
        ...    
      },
          
      mutation-type.js
      export const ADD = 'add';
          
      App.vue
      import {ADD} from './store/mutation-type.js'
      this.$store.commit(ADD,parseInt(this.inputNumber))
      
  • Actions:mutations 中无法进行异步操作,因为 devtools 追踪不到,此时就需要 action,它类似于 mutation,但是是用来代替 mutation 进行异步操作的,比如你在 mutations 中使用了 setTimeout,此时你需要在把它写到 action 中,此时的方法的传参是 context,也就是上下文,可以理解为当前的 store 对象,然后别忘了,任何对 state 的状态的改变的都需要经过 mutataion,所以然后 commit 一下,此时别忘了组件中的调用也改成调用 action,方法为:this.$store.dispatch('methodName')

    • 但是如果你想知道异步操作完成没应该咋办,肯定是在 action 中的方法 commit 后通知一下,那么你可以传个参数给 action,该参数是方法,commit 后就使用该函数。

    • App.vue:
      this.$store.dispatch('aUpInfo',()=>console.log('异步完成'))
      
      action:
      aUpInfo(context,payload){
          context.commit('upInfo')
          payload()
      }
      
      
    • 如果此时要携带参数呢,那么你把传参改为对象不就好了

    • App.vue:
      this.$store.dispatch('aUpInfo',{
          message:'okok',
          success:()=>console.log('异步完成')
      })
      
      action:
      aUpInfo(context,payload){
          context.commit('upInfo')
          console.log(payload.message)
          payload.success()
      }
      
    • 但是这样好像不太优雅,那么你想到了什么,没错,Promise!我直接返回 Promise,你会好奇 .then 呢,那么我既然返回了 promise,组件中调用的方法不就接收到了,他不就可以 .then 吗。

    • action:
      aUpInfo(context,payload){
            // setTimeout(()=>{
            //   context.commit('upInfo')
            //   console.log(payload.message)
            //   payload.success()
            // })
            //来个更优雅的
            return new Promise((resolve,reject)=>{
              setTimeout(()=>{
                context.commit('upInfo')
                console.log(payload)
                resolve('我已ok')
              })
            })
          }
      
      App.vue:
      this.$store.dispatch('aUpInfo','我是消息').then(res=>{
          console.log(res)
      })
      
  • Modules:Vue 使用单一状态树,那么意味着很多状态都会交给 Vuex 来管理,当应用复杂时,store 对象有可能变得非常臃肿,为了解决这个问题,Vuex 允许我们将 store 分割成模块,也就是 Module,而每个模块也能够拥有自己的 state,mutations,getters,actions 等

    • 那么如何获取 modules 中的 state 中存储的状态呢,其实 modules 最终也是被存入 state:

      • 例如:
        modules:{
          a:{
            state:{
              name:'john'
            }
          }
        }
        
        此时在组件中获取:
        this.$store.state.a.name
        
    • 那么如何在 modules 中使用它的 mutations,其实和在 store 中使用一样,但是方法名字别重名,commit 时优先在 store 的 mutataions 中寻找

    • 那么如何使用 modules 中的 getters,和使用 mutations 一样,第二个传参可以是 getters,那么如果你想要在 module 中用 store 中存储的属性咋办,你可以传第三个参数 rootState 就可以取出来用了

    • actions 使用肯定也类似,但是此时的传参 context 不太一样,只能 commit 到自己模块里面的 mutations,而且打印 context 发现还有rootGetters

      • 还有一种 ES6 的对象的解构的写法:func({state,commit,rootState}){...},说到这里不得不提到这个解构写法得用法,比如有一个 obj:const obj = {name:'john',age:18},此时你想要用变量来取出它的属性时可以:const{name,age} = obj,这样就可以根据对应的名字取到,与顺序无关
vuex-store文件夹的目录组织
  • 我们可能会在 index.js 中写太多代码,那我到时候比如要在 actions 中加个方法就很麻烦,所以我们会抽取一下其中的内容
  • 通常情况下我们把 state 还是留在 index 中,const 一个 state,然后 const store 时用字面量加强写法写进去
  • 然后我们把 mutations、actions、getters 都抽取出来,export default{} 出去
  • modules 我们需要新建一个 modules 文件夹,然后在其中写你的模块文件,index 中导入使用即可

网络请求封装(axios)

网络模块选择
  • Ajax:配置和调用都太麻烦,直接舍弃
  • Jquery-Ajax:为了进行一个网络请求而引用 1W+ 行的代码有点得不偿失
  • Vue-resourse:官方在 Vue1.x 时推出的,在 Vue2.0 退出后就不再更新维护了
  • axious:Vue 的作者也很推荐
    • 在浏览器中发送 XMLHttprequests 请求
    • 在 node.js 中发送 http 请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求和响应数据…
axios框架的基本使用
  • 找个地方导入:import axios from 'axios',然后直接:axios({url:'xxx'})
  • 如果要带参请求可以:axios({url:'xx',params:{name:'john'}}),到时候 url 就是 utl?name=john
axios发送并发请求
  • 类似 Promise 的多个异步请求的处理方式,axios 也有 all 方法:axios.all([axios(),axios()]).then(results=>{}),这里的 results 也是数组,但是如果你想直接拆分也可以,将 results=>{} 替换为:axios.spread((res1,res2)=>{})
axios全局配置
  • 请求的 url 可能前部分是固定的,可以抽取成 baseUrl

  • 请求头相关信息,超时时间等配置一般也会多次使用

  • 以上所说公共配置可以配置如下:

  • axios.defaults.baseURL = 'http://123.207.32.32:8000'
    axios.defaults.timeout = 5000
    
  • 那么究竟我们可以配置哪些东西呢,这里列举出常用的(具体参照官网):

    • 请求地址:url:'/user'
    • 请求方式:method:'get'
    • 请根地址:baseURL:'http://www.mt.com/api'
    • 请求前的数据处理:transformRequest:[function(data){}]
    • 请求后的数据处理:transformResponse:[function(data){}]
    • 自定义的请求头:headers:{'x-Requested-With:':'XMLHttpRequest'}
    • URL查询对象:params:{id:12}
    • 查询对象序列化函数:paramsSerializer:function(params){}
    • 请求体(request body):data:{key:'aa'}
    • 超时设置:timeout:1000
    • 跨域是否带 token:withCredentials:false
    • 自定义请求处理:adapter:function(resolve,reject,config){}
    • 身份验证信息:auth:{uname:'',pwd:'12'}
    • 响应的数据格式 json/blob/document/arraybuffer/text/stream:responseType:'json'
axios的实例
  • 不一定所有请求都是使用你配置的全局配置,比如我们可能请求某些数据时不是使用你配置的 baseURL,所以创建 axios 的实例,就可以实现不同请求不同配置

  • //创建实例
    const instance1 = axios.create({
        baseURL:'http://123.207.32.32:8000',
        timeout:5000
    })
    //使用
    instance1({
        url:'/home/data',
        params:{
            type:'pop',
            page:1
        }
    }).then(res=>{
        console.log(res)
    })
    
axios的模块封装
  • 如果我们都是在每个用到 axios 的地方直接导入使用 axios,那么如果 axios 不再维护你就必须在每个文件都修改,但是如果进行封装,在每个文件都导入你封装过的 axios,那么到时候修改你封装的文件即可

  • 首先创建网络目录 network,既然 axios 用来做网络请求,那么就创建 request.js

  • import axios from 'axios'
    
    export function request(config){
        const instance = axios.create({
            baseURL:'http://123.207.32.32:8000',
            timeout:5000
        })
        //它本身就返回 Promise,所以直接 return 即可
        return instance(config)
    }
    
    //其他导出方式,就不修改使用方法了,自行体会
    //其他方案1
    export function request(config){
      return new Promise((resolve,reject)=>{
        const instance = axios.create({
            baseURL:'http://123.207.32.32:8000',
            timeout:5000
        })
        instance(config).then(res=>{
          resolve(res)
        }).catch(err=>{
          reject(err)
        })
      })
    }
    
    //其他方案2,其实这里传入成功和失败回调函数,仔细一想就像是Promise的resolve和reject
    export function request(config,success,failure){
        const instance = axios.create({
            baseURL:'http://123.207.32.32:8000',
            timeout:5000
        })
        instance(config).then(res=>{
          success(res)
        }).catch(err=>{
          failure(err)
        })
    }
    
    //使用处
    import {request} from './network/request'
    request({
      url:'/home/multidata'
    }).then(res=>{
      console.log(res);
    })
    
axios的拦截器使用
  • 请求和响应的成功和失败都可以拦截,可以依次进行相关处理
  • 请求拦截:instance(axios实例).interceptors.request.use(成功回调函数,失败回调函数),成功回调函数会获取你传入的 config,失败则是相应的错误。成功时记得把获取到的 config 返回出去,即:return config,那么何时我们需要用到请求拦截呢?
    • 比如需要对 config 中的一些信息进行处理再请求
    • 比如每次网络请求都希望有一个请求的图标显示在界面中
    • 某些网络请求(比如登录(token)),必须携带一些特殊信息
  • 响应拦截也是同理,它的成功回调函数获取到的是接口返回的数据,但是还存在一些例如 config,headers 等信息,所以你在响应成功拦截时,return res.data,这样每次网络请求的响应数据就是我们要的 data 而没有多余的数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值