Vue

一. Vue

1.1. 官方文档

vue官网

1). 一位华裔前Google工程师(尤雨溪)开发的前端js库
2). 作用: 动态构建用户界面
3). 特点:
	* 遵循MVVM模式
	* 编码简洁, 体积小, 运行效率高, 移动/PC端开发
	* 它本身只关注UI, 可以轻松引入vue插件和其它第三库开发项目
4). 与其它框架的关联:
	* 借鉴angular的模板和数据绑定技术
	* 借鉴react的组件化和虚拟DOM技术
5). vue包含一系列的扩展插件(库):
	* vue-cli: vue脚手架
	* vue-resource(axios): ajax请求
	* vue-router: 路由
	* vuex: 状态管理
	* vue-lazyload: 图片懒加载
	* vue-scroller: 页面滑动相关
	* mint-ui: 基于vue的组件库(移动端)
	* element-ui: 基于vue的组件库(PC端)
	
基本使用:
	
1). 引入vue.js
2). 创建Vue实例对象(vm), 指定选项(配置)对象
	el : 指定dom标签容器的选择器
	data : 指定初始化状态数据的对象/函数(返回一个对象)
3). 在页面模板中使用{{}}或vue指令
复制代码

1.2. 编程范式

命令式编程 => 声明式编程

1.3. Vue的MVVM

View ViewModel(Vue) Model

  • View —> DOM层,视图,模板页面
  • ViewModel —> 视图模型(Vue实例),数据绑定和DOM监听
  • Model —> 模型,数据层

1.4. Mustache

Mustache语法(双大括号):支持变量也支持表达式

1.5. Vue对象的选项

1.51. el
指定dom标签容器的选择器
Vue就会管理对应的标签及其子标签
复制代码
1.52. data
对象或函数类型
指定初始化状态属性数据的对象
vm也会自动拥有data中所有属性
页面中可以直接访问使用
数据代理: 由vm对象来代理对data中所有属性的操作(读/写)
复制代码
1.53. methods
包含多个方法的对象
供页面中的事件指令来绑定回调
回调函数默认有event参数, 但也可以指定自己的参数
所有的方法由vue对象来调用, 访问data中的属性直接使用this.xxx
复制代码
1.54. computed
包含多个方法的对象
对状态属性进行计算返回一个新的数据, 供页面获取显示
一般情况下是相当于是一个只读的属性
利用set/get方法来实现属性数据的计算读取, 同时监视属性数据的变化
如何给对象定义get/set属性
	在创建对象时指定: get name () {return xxx} / set name (value) {}
  	对象创建之后指定: Object.defineProperty(obj, age, {get(){}, set(value){}})
  	
实例的 computed 中,包含 get 方法和 set 方法(不常用)。
计算属性会进行缓存,如果多次使用时,计算属性只调用一次。(watch 监视单个,cumputed 监视多个)
复制代码
1.55. watch
包含多个属性监视的对象
分为一般监视和深度监视
    xxx: function(value){}
	xxx : {
		deep : true,
		handler : fun(value)
	}
另一种添加监视方式: vm.$watch('xxx', function(value){})
复制代码

1.6. Vue内置指令

指令API

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式(v-for 除外),指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

v-text/v-html: 指定标签体
	* v-text : 当作纯文本
	* v-html : 将value作为html标签来解析
v-if v-else v-show: 显示/隐藏元素
	* v-if : 如果vlaue为true, 当前标签会输出在页面中
	* v-else-if : 表示 v-if 的“else if 块”。可以链式调用。
	* v-else : 与v-if一起使用, 如果value为false, 将当前标签输出到页面中
	* v-show: 就会在标签中添加display样式, 如果vlaue为true, display=block, 否则是none
v-for : 遍历
	* 遍历数组 : v-for="(person, index) in persons"   
	* 遍历对象 : v-for="value in person"   $key
v-on : 绑定事件监听
	* v-on:事件名, 可以缩写为: @事件名
	* 监视具体的按键: @keyup.keyCode   @keyup.enter
	* 停止事件的冒泡和阻止事件默认行为: @click.stop   @click.prevent
	* 隐含对象: $event
v-bind : 强制绑定解析表达式  
	* html标签属性是不支持表达式的, 就可以使用v-bind
	* 可以缩写为:  :id='name'
	* :class
	  * :class="a"
		* :class="{classA : isA, classB : isB}"
		* :class="[classA, classB]"
		* :class="classes()"
	* :style
		:style="{key(属性名) : value(属性值)''(防止解析成变量)}"
v-model
	* 双向数据绑定
	* 自动收集用户输入数据
	* 原理v-bind,v-on:input
    * 修饰符lazy,number,trim。event.target.value
v-slot 
	* 提供具名插槽或需要接收 prop 的插槽
ref : 标识某个标签
	* ref='xxx'
	* 读取得到标签对象: this.$refs.xxx
v-once :
    * 元素和组件只渲染一次,不会随着数据的改变而改变。 
v-pre :
    * 跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
v-cloak :
    * [v-cloak] { display: none; } 占位
复制代码

1.7. 自定义指令

1.71. 注册全局指令
Vue.directive('my-directive', function(el, binding){
  el.innerHTML = binding.value.toUpperCase() // 转换为大写
})
复制代码
1.72. 注册局部指令
// 放在Vue实例中
directives : {
  'my-directive' : function(el, binding) {
      el.innerHTML = binding.value.toUpperCase() // 转换为大写
  }
}
复制代码
1.73. 使用指令
<div v-my-directive='xxx'>
复制代码

1.8. 数组中哪些方法是响应式的

  • push() 添加元素到数组最后,可以是多个
  • pop() 删除数组中最后一个元素
  • shift() 删除数组中第一个元素
  • unshift() 在数组最前面添加元素,可以是多个
  • splice() 作用:删除/插入/替换元素
  • sort()
  • reverse()
  • 通过索引值改变数组,不能响应式改变界面

1.9. Vue 生命周期和响应式原理

1.91. 生命周期

Image

主要的生命周期函数(钩子)
	created() / mounted(): 启动异步任务(启动定时器,发送ajax请求, 绑定监听)
	beforeDestroy(): 做一些收尾的工作
复制代码
1.92. 响应式原理

Image.png

Image [2].png


2.0. 过渡动画

利用vue去操控css的transition/animation动画

模板: 使用<transition name='xxx'>包含带动画的标签
css样式
	.fade-enter-active: 进入过程, 指定进入的transition
	.fade-leave-active: 离开过程, 指定离开的transition
	.xxx-enter, .xxx-leave-to: 指定隐藏的样式
编码例子 
	/* 可以设置不同的进入和离开动画 */
	/* 设置持续时间和动画函数 */
    .xxx-enter-active, .xxx-leave-active 
      transition: opacity .5s
    }
    .xxx-enter, .xxx-leave-to {
      opacity: 0
    }
    
    <button @click="isShow=!isShow">toggle</button>
    <transition name="xxx">
      <p v-if="show">hello</p>
    </transition>

CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。

.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
复制代码

2.1. 过滤器

1). 定义过滤器
	Vue.filter(filterName, function(value[,arg1,arg2,...]){
	  // 进行一定的数据处理
	  return newValue
	})
2). 使用过滤器
	<div>{{myDate | filterName}}</div>
	<div>{{myDate | filterName(arg)}}</div>
复制代码

2.2. 插件

2.21. 自定义插件
/**自定义 Vue 插件 */ 
(function () {
    const MyPlugin = {}
    MyPlugin.install = function (Vue, options) {
        // 1. 添加全局方法或属性 
        Vue.myGlobalMethod = function () {
            alert('Vue 函数对象方法执行') }
        // 2. 添加全局资源
        Vue.directive('my-directive', function (el, binding) {
            el.innerHTML = "MyPlugin my-directive " + binding.value })
        // 3. 添加实例方法 
        Vue.prototype.$myMethod = function () {
            alert('vue 实例对象方法执行') 
        } 
    }
    window.MyPlugin = MyPlugin
})()


复制代码
2.22. 页面使用插件
<div id="demo"> 
    <!--使用自定义指令--> 
    <p v-my-directive="msg"></p> 
</div> 

<script type="text/javascript" src="../js/vue.js"></script> 
<script type="text/javascript" src="vue-myPlugin.js"></script> 
<script type="text/javascript"> 
    //使用自定义插件 
    Vue.use(MyPlugin)
	let vm = new Vue({
        el: '#demo',
        data: {
            msg: 'atguigu' 
        } 
    })
    //调用自定义的静态方法 
    Vue.myGlobalMethod() 
	//调用自定义的对象方法 
	vm.$myMethod() 
</script>
复制代码

2.3. 数据代理(MVVM.js)

1.通过一个对象代理对另一个对象中属性的操作(读/写)
2.通过vm对象来代理data对象中所有属性的操作
3.好处: 更方便的操作data中的数据
4.基本实现流程
	1). 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符
	2). 所有添加的属性都包含getter/setter
	3). 在getter/setter内部去操作data中对应的属性数据
复制代码

2.4. 模板解析(compile.js)

1.模板解析的关键对象: compile对象
2.模板解析的基本流程:
	1). 将el的所有子节点取出, 添加到一个新建的文档fragment对象中
	2). 对fragment中的所有层次子节点递归进行编译解析处理
    	* 对表达式文本节点进行解析
    	* 对元素节点的指令属性进行解析
        	* 事件指令解析
        	* 一般指令解析
  	3). 将解析后的fragment添加到el中显示
3.解析表达式文本节点: textNode.textContent = value
  	1). 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1
  	2). 从data中取出表达式对应的属性值
  	3). 将属性值设置为文本节点的textContent
4.事件指令解析: elementNode.addEventListener(事件名, 回调函数.bind(vm))
    v-on:click="test"
  	1). 从指令名中取出事件名
  	2). 根据指令的值(表达式)从methods中得到对应的事件处理函数对象
  	3). 给当前元素节点绑定指定事件名和回调函数的dom事件监听
  	4). 指令解析完后, 移除此指令属性
5.一般指令解析: elementNode.xxx = value
  	1). 得到指令名和指令值(表达式)
  	2). 从data中根据表达式得到对应的值
  	3). 根据指令名确定需要操作元素节点的什么属性
        * v-text---textContent属性
        * v-html---innerHTML属性
        * v-class--className属性
  	4). 将得到的表达式的值设置到对应的属性上
  	5). 移除元素的指令属性
复制代码

2.5. 数据劫持–>数据绑定

1.数据绑定(model==>View):
	1). 一旦更新了data中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新(更新)
2.数据劫持
	1). 数据劫持是vue中用来实现数据绑定的一种技术
	2). 基本思想: 通过defineProperty()来监视data中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
3.四个重要对象
	1). Observer
		* 用来对data所有属性数据进行劫持的构造函数
      	* 给data中所有属性重新定义属性描述(get/set)
      	* 为data中的每个属性创建对应的dep对象
    2). Dep(Depend)
      	* data中的每个属性(所有层次)都对应一个dep对象
      	* 创建的时机:
        	* 在初始化define data中各个属性时创建对应的dep对象
        	* 在data中的某个属性值被设置为新的对象时
      	* 对象的结构
	        {
	          id, // 每个dep都有一个唯一的id
	          subs //包含n个对应watcher的数组(subscribes的简写)
	        }
		* subs属性说明
			* 当一个watcher被创建时, 内部会将当前watcher对象添加到对应的dep对象的subs中
			* 当此data属性的值发生改变时, 所有subs中的watcher都会收到更新的通知, 从而最终更新对应的界面
	3). Compile
		* 用来解析模板页面的对象的构造函数(一个实例)
		* 利用compile对象解析模板页面
		* 每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
		* complie与watcher关系: 一对多的关系
	4). Watcher
      	* 模板中每个非事件指令或表达式都对应一个watcher对象
      	* 监视当前表达式数据的变化
      	* 创建的时机: 在初始化编译模板时
      	* 对象的组成
			{
	          vm,  //vm对象
	          exp, //对应指令的表达式
	          cb, //当表达式所对应的数据发生改变的回调函数
	          value, //表达式当前的值
	          depIds //表达式中各级属性所对应的dep对象的集合对象
	                  //属性名为dep的id, 属性值为dep
			}
		
	5). 总结: dep与watcher的关系: 多对多
		* 一个data中的属性对应对应一个dep, 一个dep中可能包含多个watcher(模板中有几个表达式使用到了属性)
		* 模板中一个非事件表达式对应一个watcher, 一个watcher中可能包含多个dep(表达式中包含了几个data属性)
		* 数据绑定使用到2个核心技术
			* defineProperty()
			* 消息订阅与发布

4.双向数据绑定
	1). 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
	2). 双向数据绑定的实现流程:
      	* 在解析v-model指令时, 给当前元素添加input监听
      	* 当input的value发生改变时, 将最新的值赋值给当前表达式所对应的data属性
复制代码

二. 组件化

1.1. 组件化开发

  • 提供一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
  • 任何的应用都会被抽象成一颗组件树。
1.11. 注册组件基本步骤
  1. 创建组件构造器 Vue.extend()方法
  2. 注册组件 Vue.component()
  3. 使用组件 Vue实例的作用范围内使用组件
  <div id="app">
    <cpnc></cpnc>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    // 1. 创建组件构造器对象
    let cpn = Vue.extend({
      template: `
        <div>
          <h2>zs</h2>
          <p>历史</p>
        </div>
      `
    })
    // 2. 注册组件
    Vue.component('cpnc', cpn)

    const app = new Vue({
      el: "#app",
      data: {

      }
    })
  </script>
复制代码
1.12. 全局组件,局部组件
  <div id="app">
    <cpnc></cpnc>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    // 1. 创建组件构造器对象
    let cpn = Vue.extend({
      template: `
        <div>
          <h2>zs</h2>
          <p>历史</p>
        </div>
      `
    })
    // 2. 注册组件 (全局组件)
    // Vue.component('cpnc', cpn)

    const app = new Vue({
      el: "#app",
      data: {
        // 局部组件
		components: {
            // cpnc 使用组件的标签名
            cpnc: cpn
        }
      }
    })
  </script>


// 组件中的数据为函数,返回一个对象,增强复用性
复制代码
1.13. 父组件,子组件
1.14. 父子组件的通信
  1. 通过props向子组件传递数据(驼峰式的prop作为特性时,v-bind:后的属性需要转为 message-prop (短横线隔开))
<!-- 向子组件传递数据为 数组和对象时,default值为函数 -->
<!-- 子组件 -->
<template>
    <div id="container">
        {{msg}}
    </div>
</template>
<script>
export default {
  data() {
    return {};
  },
  props:{
    msg: String
  }
};
</script>
<style scoped>
#container{
    color: red;
    margin-top: 50px;
}
</style>


<!-- 父组件 -->
<template>
    <div id="container">
        <input type="text" v-model="text" @change="dataChange">
        <Child :msg="text"></Child>
    </div>
</template>
<script>
import Child from "@/components/Child";
export default {
  data() {
    return {
      text: "父组件的值"
    };
  },
  methods: {
    dataChange(data){
        this.msg = data
    }
  },
  components: {
    Child
  }
};
</script>
<style scoped>
</style>

复制代码
  1. 通过事件向父组件发送消息(this.$emit(“事件名”),参数)

    <!-- 子组件 -->
    <template>
        <div id="container">
            <input type="text" v-model="msg">
            <button @click="setData">传递到父组件</button>
        </div>
    </template>
    <script>
    export default {
      data() {
        return {
          msg: "传递给父组件的值"
        };
      },
      methods: {
        setData() {
          this.$emit("getData", this.msg);
        }
      }
    };
    </script>
    <style scoped>
    #container {
      color: red;
      margin-top: 50px;
    }
    </style>
    
    
    
    <!-- 父组件 -->
    <template>
        <div id="container">
            <Child @getData="getData"></Child>
            <p>{{msg}}</p>
        </div>
    </template>
    <script>
    import Child from "@/components/Child";
    export default {
      data() {
        return {
          msg: "父组件默认值"
        };
      },
      methods: {
        getData(data) {
          this.msg = data;
        }
      },
      components: {
        Child
      }
    };
    </script>
    <style scoped>
    </style>
    复制代码
1.15. 父子组件的访问方式
  1. 父组件访问子组件:使用childrenchildren或refs(reference引用) (对象类型,默认空对象,添加ref属性使用)
  2. 子组件访问父组件 $parent
1.16. 注册组件语法糖
	// 全局组件注册语法糖
	Vue.component('cpn1',{
	  template: `
        <div>
          <h2>zs</h2>
          <p>历史</p>
        </div>
      `
	})
	// 局部组件注册语法糖
    const app = new Vue({
      el: "#app",
      data: {
        // 局部组件
		components: {
            // cpnc 使用组件的标签名
            cpnc: {
      		  template: `
        	    <div>
          		  <h2>zs</h2>
          		  <p>历史</p>
       			</div>
      		  `
    	  }
        }
      }
    })
复制代码
1.17. 模板组件的分离写法
  1. script标签,类型必须是text/x-template
  2. template标签
  // 模板的分离写法
  <script type="text/x-template" id="cpn">
    <div>
      <h2>zs</h2>
      <p>历史</p>
    </div>

  </script>	

  <template id="cpn">
    <div>
      <h2>zs</h2>
      <p>历史</p>
    </div>
  </template>
  
  <script>
   const app = new Vue({
     el: "#app",
     data: {},
     components: {
       cpnc: {
         template: "#cpn"
       }
      }
    })
  </script>	
  
复制代码

1.2. 组件化高级

1.21. 插槽

slot 让封装的组件更具有扩展性

1.22. 具名插槽
<cpn>
   <h3 slot="button">张三</h3>
</cpn>

<slot name="button"><button>按钮</button></slot>

复制代码
1.23. 编译的作用域
1.24. 作用域插槽

父组件替换插槽的标签,但是内容由子组件来提供。

 <div id="app">
    <cpn>
      <div slot-scope ="slot">
        <span v-for="item in slot.data">{{item}}</span>
      </div>
    </cpn>
  </div>


  <template id="cpn">
    <div>
      <slot :data="num"></slot>
    </div>
  </template>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({

      el: "#app",
      data: {},
      components: {
        cpn: {
          template: "#cpn",
          data() {
            return {
              num: [1, 2, 3, 4]
            }
          }
        }
      }
    })
  </script>
复制代码

1.3. 模块化开发

1.31. 模块化规范
最基础的封装:在匿名函数内部,定义一个对象。
给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。
最后将这个对象返回,并且在外面使用了一个MoudleA接受。

------------------------------------------------------

模块化所解决的问题:
	1.模块作用域: 安全的包装一个模块的代码--不污染模块外的任何代码
	2.模块唯一性: 唯一标识一个模块--避免重复引入
	3.模块的导出: 优雅的把模块的API暴漏给外部--不增加全局变量
	4.模块的引入: 方便的使用所依赖的模块


目前模块化的解决方案
	1.CommonJS -- Node.js
	2.AMD -- RequireJS
	3.CMD -- SeaJS
	4.UMD
	5.ES6 Module

------------------------------------------------------

CommonJS
一个单独的JS文件就是一个模块,每一个模块都是一个单独的作用域
定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴漏出来的API
如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖
如果引入模块失败,那么require函数应该报一个异常
模块通过变量exports来向外暴漏API,exports只能是一个对象,暴漏的API须作为此对象的属性。
CommonJS 例子
//a.js
exports.foo = function() {
  console.log('foo')
}

//b.js
var a = require('./a.js')
a.foo()
CommonJS 缺点
只能在服务端(Node.js)使用, 不能在浏览器直接使用
模块是同步加载的, 如果加载过慢会阻塞进程

------------------------------------------------------

ES6 Module 基本用法
定义模块

//变量, module.js
export var bar = 'bar'

// 函数, module.js
export function foo(){}

// 统一导出&重命名, module.js
var bar = 'bar'
function foo(){}
export { bar as myBar, foo }

// 默认导出, module.js
function foo(){}
export default foo

------------------------------------------------------

ES6 Module 基本用法
引用模块

// 从模块中导入指定对象, 支持重命名, main.js
import { foo, bar as myBar } from './module.js'

// 从模块中导入默认对象(名称可跟原名称不一样)
import myFoo from './module.js'

// 执行模块, 但不导入任何值
import './module.js'

// 整体导入
import * as myModule from './module.js'
注意: ES6 import导入的模块都是原模块的引用

ES6 Module 基本用法
	浏览器使用: 在入口JS文件加上type="module"就可以在该文件内使用ES6 Module 语法
	<script src="https://juejin.cn/post/scripts/main.js" type="module"></script>
复制代码

三. Vue CLI

CLI:Command-Line-Interface 翻译为命令行界面, 但是俗称脚手架.

Vue CLI是一个官方发布 vue.js 项目脚手架.

使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.

1.1. 安装Vue脚手架

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

升级:
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli

复制代码

Vue CLI >= 3 和旧版使用了相同的 vue 命令,所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能,你可以全局安装一个桥接工具:

Vue CLI3的版本
npm install -g @vue/cli-init
# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同

# CLI2初始化项目
vue init webpack my-project

# CLI3初始化项目
vue create my-project
复制代码

1.2. Vue CLI2详解

image.png

1.21. 目录结构详解

image.png

1.3. runtime-only和runtime-compiler

image.png

1.31. render和template

image.png

1.32. Vue程序运行过程

图片1.png

  • runtime-compiler:template -> ast(抽象语法树) -> render ->vdom -> UI
  • runtime-only:render ->vdom -> UI

1.4. build和dev

npm_run_build.png

npm_run_dev.png

1.5. 修改配置:webpack.base.conf.js起别名

image.png

1.6. Vue CLI3

  • vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
  • vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目
  • vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
  • 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
1.61. 项目配置

image.png

1.62. 目录结构详解

image.png

1.63. 自定义配置:起别名

image.png

四. Vue-Router

路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动. — 维基百科

1.1. 后端路由和前端路由

  • 后端渲染/后端路由
  • 前后端分离
  • SPA/前端路由

后端渲染: 服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示。比如:jsp页面

前端渲染: 浏览器中显示的网页中的大部分内容,都是由前端写的 js 代码在浏览器中执行,最终渲染出来的网页。(后端返回JSON数据,前端利用预先写的html模板,循环读取JSON数据,拼接字符串,并插入页面。)

后端路由: 浏览器在地址栏中切换不同的 url 时,每次都向后台服务器发出请求,服务器响应请求,服务器渲染好整个页面, 并且将页面返回给客户端。

前端路由: 核心-改变URL,但是页面不进行整体的刷新。

1.2. SPA页面

SPA:单页面富应用,整个网页只有一个html页面。

SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.

前端路由的核心是改变URL,但是页面不进行整体的刷新。

1.3. URL的hash

URL的hash也就是锚点(#), 本质上是改变window.location的href属性.

我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新.

location.href
"http://192.168.1.101:80/"
location.hash = '/foo'
"http://192.168.1.101:80/#/foo"
复制代码

1.4. HTML5的history模式

  • pushState
  • back
  • forward
  • replaceState
  • go
history.pushState({},'','/foo')
location.href
"http://192.168.1.101:80/foo"

history.replaceState({},'','/foo')
location.href
"http://192.168.1.101:80/foo"

"http://192.168.1.101:80/foo"
history.go(-1)
location.href
"http://192.168.1.101:80/"
history.go(1)
"http://192.168.1.101:80/foo"
复制代码

1.5. Vue Router

vue-router是基于路由和组件的

  • 路由用于设定访问路径, 将路径和组件映射起来.
  • 在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.

1.6. 安装和使用路由

第一步:导入路由对象,并且调用 Vue.use(VueRouter)
第二步:创建路由实例,并且传入路由映射配置
第三步:在Vue实例中挂载创建的路由实例

import Vue from 'vue' 
import VueRouter from 'vue-router' 

路由懒加载
const Home = () => import('../components/Home.vue')
const About = () => import('../components/About.vue')
const Message = () => import('../components/Message.vue')
const news = () => import('../components/news.vue')



Vue.use(VueRouter)

使用vue-router的步骤:
第一步: 创建路由组件
第二步: 配置路由映射: 组件和路径映射关系
第三步: 使用路由: 通过<router-link>和<router-view>

// 定义路由
const routers = [
	{
		path: '/',
		redirect: '/home'
	},
	{
		path: '/home',
		component: Home,
		
		// 嵌套路由
		children: [
		  {
		    path: 'message',
		    component: Message,
		  },
		  {
		    path: 'news',
		    component: News,
		  },
		  {
		    path: '',
			redirect: 'message'
		  }
		]
	},
	{
		path: '/about',
		component: About
	}
]

// 创建router实例
const router = new VueRouter({
  routers,
  
    // 使用HTML5的history模式
	mode: 'history'  
	
	// 修改active-class属性的默认类名(router-link-active)
	LinkActiveClass: 'active'  
})

// 导出router实例
export default router

// 挂载到vue实例
new Vue({
	el: '#app',
	router,
	render: h => h(APP)
})

<router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签.
<router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件.
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>处于同一个等级.
在路由切换时, 切换的是<router-view>挂载的组件, 其他内容不会发生改变.
复制代码

1.7. router-link 其他属性

1.8. 页面路由跳转

<button @click="linkToHome">首页</button>

<script>
	export default {
        name: 'App',
        methods: {
          linkToHome() {
              this.$router.push('/home')
          }
        }
    }
    
</script>
复制代码

1.9. 动态路由

1.91. params的类型
  • 配置路由格式: /router/:id
  • 传递的方式: 在path后面跟上对应的值
  • 传递后形成的路径: /router/123, /router/abc
//拿到用户名(userId是自己router中定义的路径)
{
  path: '/user/:userId',
  component: User
}

<router-link to="/user/123">用户</router-link>

<div>
  <h3>{{$route.params.userId}}</h3>
</div>

{{this.$route.params.userId}}
复制代码
1.92. query的类型
  • 配置路由格式: /router, 也就是普通配置
  • 传递的方式: 对象中使用query的key作为传递方式
  • 传递后形成的路径: /router?id=123, /router?id=abc
获取参数
{{$route.params}}

{{$route.query}}
复制代码
1.93. 两种传递参数的方式

传递参数方式一:

图片4.png


传递参数方式二: JavaScript代码

图片3.png

1.94. routeroute和router是有区别的
  • routerVueRouter实例,想要导航到不同URL,则使用router为VueRouter实例,想要导航到不同URL,则使用router.push方法

  • $route为当前router跳转对象里面可以获取name、path、query、params等

image.png

2.0. 路由的懒加载

作用:将路由对应的组件打包成一个个的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 Home = () => import('../components/Home.vue')
复制代码

2.1. 嵌套路由

2.2. 导航守卫

官网

  • vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
  • vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
首先, 我们可以在钩子当中定义一些标题, 可以利用meta来定义
其次, 利用导航守卫,修改我们的标题.

const routers = [
	{
		path: '/',
		redirect: '/home'
	},
	{
		path: '/home',
		component: Home,
		meta: {
		title: '首页'
		}
	},
	{
		path: '/about',
		component: About,
		title: '关于'
	}
]

const router = new VueRouter({
  routers,
})

router.beforeEach((to, from, next) => {
  window.document.title = to.meta.title;
  next()
})

export default router

导航钩子的三个参数解析:
to: 即将要进入的目标的路由对象.
from: 当前导航即将要离开的路由对象.
next: 调用该方法后, 才能进入下一个钩子.


复制代码

2.3. keep-alive

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

  • 两个属性:
    1. include – 字符串或正则表达,只有匹配的组件会被缓存
    2. exclude – 字符串或正则表达式,任何匹配的组件都不会被缓存

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

通过create声明周期函数来验证。

2.4. TabBar案例

  1. 如果在下方有一个单独的TabBar组件,你如何封装

    • 自定义TabBar组件,在APP中使用
    • 让TabBar出于底部,并且设置相关的样式
  2. TabBar中显示的内容由外界决定

    • 定义插槽
    • flex布局平分TabBar
  3. 自定义TabBarItem,可以传入 图片和文字

    • 定义TabBarItem,并且定义两个插槽:图片、文字。
    • 给两个插槽外层包装div,用于设置样式。
    • 填充插槽,实现底部TabBar的效果
  4. 传入 高亮图片

    • 定义另外一个插槽,插入active-icon的数据
    • 定义一个变量isActive,通过v-show来决定是否显示对应的icon
  5. TabBarItem绑定路由数据

    • 安装路由:npm install vue-router —save
    • 完成router/index.js的内容,以及创建对应的组件
    • main.js中注册router
    • APP中加入组件
  6. 点击item跳转到对应路由,并且动态决定isActive

    • 监听item的点击,通过this.$router.replace()替换路由路径
    • 通过this.$route.path.indexOf(this.link) !== -1来判断是否是active
  7. 动态计算active样式

    • 封装新的计算属性:this.isActive ? {‘color’: ‘red’} : {}

代码实现

image.png

五. Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。简单的讲就是把需要多个组件共享的变量全部存储在一个对象里面。

  • 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
  • Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

1.1. 单界面的状态管理

image.png

1.2. Vuex状态管理图例

image.png

1.3. Vuex核心的概念:

  • State

    State单一状态树
    
    State: {
      count: 0
    }
    复制代码
  • Getters

    /* 
    就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
    
    getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数 */
    
    const store = new Vuex.Store({
      state: {
        todos: [
          { id: 1, text: '...', done: true },
          { id: 2, text: '...', done: false }
        ]
      },
      getters: {
        doneTodos: state => {
          return state.todos.filter(todo => todo.done)
        }
      }
    })
    复制代码
  • Mutation

    Vuex的store状态的更新唯一方式:提交Mutation
    Mutation主要包括两部分:
    	1.字符串的事件类型(type)
    	2.一个回调函数(handler),该回调函数的第一个参数就是state。
    
    mutation的定义方式
    mutations: {
      increment(state){
       state.count++
      }
    }
    
    通过mutation更新
    increment(){
      this.$store.commit('increment')
    }
    
    在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数
    参数被称为是mutation的载荷(Payload)
    
    Mutation响应规则
    当给state中的对象添加新属性时, 使用下面的方式更新数据:
    方式一: 使用Vue.set(obj, 'newProp', 123)
    方式二: 用心对象给旧对象重新赋值
    
    Mutation常量类型 – 概念
    使用常量替代Mutation事件的类型.
    
    
    通常情况下, 不要再mutation中进行异步的操作
    主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
    复制代码
  • Action

    /* Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
    
    在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch,同样的, 也是支持传递payload
    
    */
    
    mutations: {
      increment(state){
       state.count++
      }
    },
    actions: {
      increment(context,payload) {
        setTimeout(()=>{
          context.commit('increment', payload)
        }, 3000)
      }
    }
    
    复制代码
  • Module

    /* 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
    
    为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
    */
    const moduleA = {
      state: () => ({ ... }),
      mutations: { ... },
      actions: { ... },
      getters: { ... }
    }
    
    const moduleB = {
      state: () => ({ ... }),
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        a: moduleA,
        b: moduleB
      }
    })
    
    store.state.a // -> moduleA 的状态
    store.state.b // -> moduleB 的状态
    复制代码

1.4. 项目结构

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享