1. 邂逅VueUrlL
1.1 环境准备
-
使用WebStorm进行开发
-
安装Vue的方式
1.2 初体验
-
入门
- 写一段简单代码
<script src="../js/vue.js"></script> <div id="app">{{message}}</div> <script> // let(变量) const(常量) const app = new Vue({ //Vue传参数,对象类型,大括号里面的是对象的属性 el: '#app', //用于挂载要管理的元素 data: { //定于数据 message: '你好啊,李银河', } }) </script>
- 分析
浏览器先显示前面的数据,后面得到message的数据后再覆盖,响应式编程,可以实时改变数据
data属性也可能是来自网络,从服务器加载的
- 声明式编程和响应式编程
以上这种事声明式编程,类似于面向对象
元素js的做法是命令式编程,类似于面向过程,一步一步严格按照规则
- 创建div元素,设置id属性
- 定义一个变量叫message
- 将message变量放在前面的div元素中显示
-
列表展示
<div id="app">
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</div>
...
data: {
movies: ['星际穿越', '大话西游', '让子弹飞']
}
-
计数器
<div id="app"> 当前计数:{{count}} <br> <!-- <button v-on:click="count++">+</button>--> <!-- <button v-on:click="count--">-</button>--> <button @click="add">+</button> <button @click="sub">-</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { count: 0 }, methods: { add: function (){ console.log('add被执行'); this.count++ }, sub: function (){ console.log('sub被执行'); this.count-- } } }) </script>
1.3 相关知识
-
mvvm
在上节的计数器中演示各部分代码分别是什么成分
为了更方便理解,data可以单独写成一个对象
-
Vue中的options选项
- el:
- 类型:string | HTMLElement
- 作用:决定之后Vue实例会管理哪一个DOM
- data:
- 类型:Object | Function (组件当中data必须是一个函数)
- 作用:Vue实例对应的数据对象
- methods:
- 类型:{[key:string]:Function}
- 作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用
- el:
- 方法和函数:直接定义在外面的叫函数,定义在类里面的一般叫方法
-
Vue的生命周期
在运行vue函数的过程中,会依照生命周期执行里面的生命周期函数
2. 基本语法
2.1 插值操作(文本中)
- mustache语法,就是所谓的{{}}
- v-once:表示元素和组件只渲染一次,不会随着数据的改变而改变
- v-html:希望得到的包含链接的参数链接依然可用
- v-text:类似于mustache,但是是写在标签内部的解析
- v-pre:直接原封不动地输出内容,不做解析
- v-cloak:在vue语法加载成功之前不显示未成功解析的{{}}
2.2 v-bind 动态地决定属性值
-
示例
<div id="app"> <img v-bind:src="imgUri" alt=""> <!-- 语法糖写法--> <a :href="aHref">百度一下</a> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { imgUri: 'https://m.360buyimg.com/babel/jfs/t1/153690/12/20982/113203/603f4e6aE0e5a373b/7704dbfc9071090b.jpg', aHref: 'http://www.baidu.com' } }) </script>
-
动态绑定class(对象语法)
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.active{
color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- <h2 :class="active">{{message}}</h2>-->
<!-- 一个标签可以同时拥有多个类,如果有一个类必有的可以再写一个,会合并-->
<h2 :class="{active: isActive, line: isLine}">{{message}}</h2>
<button @click="btnClick">更换颜色</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
//类可以替换,写出来多个类的需求与否也可以替换
// active: 'active'
isActive: true,
isLine: false
},
methods: {
btnClick: function (){
this.isActive = !this.isActive
}
}
})
</script>
</body>
上图的class就只有active,没有line了,如果嫌太长可以直接写成一个方法的返回值
- 数组语法(用得少)
:class="[active,line]"
如果想表示成字符串就加单引号,如果想表示成变量就不用加
题目不会做。。。
-
动态绑定style(对象语法)
<h2 :style="{fontSize: '50px'}">{{message}} <h2 :style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}
-
数组语法(用得少)
数组里就放一个个单个的对象,对象具体定义在data里面
2.3 计算属性
-
特征
计算属性是computed函数,而且里面的命名一般不加get等动词,因为它本质就是个属性,使用不需要加小括号
它是有缓存的,不会重复调用,比methods性能更高一点
-
使用
<div id="app"> <h2>总价格为:{{totalPrice}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { books: [ {id: 100, name: 'java', price: 100}, {id: 101, name: 'js', price: 101}, {id: 102, name: 'python', price: 102}, {id: 103, name: 'c++', price: 103}, ] }, computed: { totalPrice: function (){ let result = 0 for(let i=0;i<this.books.length;i++){ result += this.books[i].price } return result } } }) </script>
-
set和get方法
计算属性原本是有get和set方法的,一般set方法不用,赋值的时候才会调用这个方法,所以不写就默认是写在get方法里面
2.4 ES6语法补充
-
用let代替var
var太猖狂了,可以随意跨作用域,闭包才可以解决这个问题,因为函数是一个作用域
ES5中的var是没有块级作用域的(if/for)
ES6中的let是有块级作用域的(if/for)
理解下面这段代码
<script> const btns = document.getElementsByTagName('button'); for (let i=0;i<btns.length; i++){ //如果写成var就是错的 btns[i].addEventListener("click", function (){ console.log('第' + i + '个按钮被点击'); }) } </script>
在点击按钮之前,for循环就会先执行一遍,直接生成按钮数量个监听按钮器
-
const的使用
- 在开发中优先使用const,只有需要改变某一个标识符的时候才使用let
- const定义常量,不可改变
- 在使用const定义标识符,必须进行赋值
- 常量的含义是指向的对象不能修改,但是可以改变对象内部的属性(保存的是对象的内存地址)
-
对象字面量增强写法
- 属性的简写:如果之前定义的key=value,传到对象的时候只要写一个key就行了
- 方法的简写:省略了:function
2.5 v-on
- 参数传递问题
- 如果没有参数,调用方法的时候可以不加小括号
- 有参数如果省略参数,只写小括号,就会传入undefined进入
- 如果有参数的情况下连小括号都不写,就会传入点击事件event进入
- 如果本来就要传入event参数,调用的实参写成$event
- 修饰符的使用
- @click.stop:调用 event.stopPropagation() 停止冒泡
- .prevent:调用event.preventDefault() 阻止默认行为
- .{keyCode | keyAlias}:只当事件是从特定键触发时才触发回调
- .once:只触发一次回调
2.6 v-for
-
遍历数组
<li v-for="(item,index) in items">{{index}}-{{item}}</li>
-
遍历对象
<div id="app"> <ul> <!-- 如果不加key和index,默认只有value--> <li v-for="(value,key,index) in books">{{value}}-{{key}}-{{index}}</li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { books: { aaa: '111', bbb: '222' } } }) </script>
-
提升算法效率
为了在数组中间插入元素的效率更高,可以通过绑定key的方式,与原理有关
<li v-for="item in letters" :key="item">{{item}}</li>
虚拟dom的原理,原来在中间插入元素的时候,会直接占用那个元素的位置,后面的元素再依次覆盖,现在这样写就不会了
2.7 响应式方法有哪些
push,pop,shift,unshift,splice,sort,reverse
通过索引值修改数组中的元素不是响应式的,如果想要改变,可以通过splice方法和set方法改变,推荐splice
2.8 综合案例:书籍购物车案例
主要问题是不知道变量的范围,不知道什么时候加this
2.9 JavaScript高价函数的使用
-
for循环里面的in和of(对于数组里面是一个个对象)
-
in就是简写版,返回的是下标
-
of是增强版,返回的是数组遍历过程中一个个含下标的对象
totalPrice() { let result=0 for (let item of this.books){ result += item.price + item.count } }
-
-
数组.filter/.map/.reduce
const nums = [10,20,111,222,444,40,50] //filter获取数组里面小于100的元素存入新数组 let total = nums.filter(function (n){ return n < 100 //返回值为true就可以存入数组 //map把所有的数组元素*2再存入 }).map(function (n){ return n * 2 //返回数组中所有元素的和 }).reduce(function (prevValue,n){ return prevValue + n },0) console.log(total);
2.10 v-model
-
双向绑定的使用和原理
<div id="app"> <input type="text" v-model="message"> {{message}} </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script>
输入任何数据,页面和实际内容都会一起改变
<input type="text" v-model="message"> 等同于 <input type="text" v-bind:value="message" v-on:input="message=">
-
在选择框的使用
- radio
- checkbox
- select
- 值绑定:让选择框中的数据不是写死的,从数组中动态读取
-
修饰符
- .lazy:失去焦点和回车的时候才会进行更新
- .number:不让输入的数字自动转变为string类型,转为数字类型
- .trim:忽略输入的空格
3. 组件化
3.1 组件化的基本过程
div id="app">
//3.使用组件
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1.创建组件构造器对象
const cpnC = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容111</p>
<p>我是内容222</p>
</div>`
})
//2.注册组件(这样注册的是全局组件,意味着可以在多个Vue的示例下面使用)
Vue.component('my-cpn',cpnC)
//可以认为这是一个顶级组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
3.2 局部组件
以上直接注册的组件时全局组件,在Vue实例里面注册的才是局部组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: cpnC
}
})
3.3 父组件和子组件
const cpnC2 = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容111</p>
<p>我是内容222</p>
<cpn1></cpn1>
</div>
`,
//在组件二里面注册组件一并使用,代表组件二是组件一的父组件,之后直接使用组件二就行,不格外注册组件一就不能直接用组件一
components: {
cpn1: cpnC1
}
})
3.4 语法糖
-
全局组件
Vue.component('cpn',{ template:` <div> <h2>我是标题</h2> <p>我是内容</p> </div>` })
-
局部组件
//root const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { 'cpn2': { template:` <div> <h2>我是标题</h2> <p>我是内容</p> </div>` } } })
3.5 可以把template里面的代码抽取出来
在外面格外定义一个templated标签,属性加上id,通过 template:’#id’ 引入就行
3.6 组件data
组件不能直接使用Vue中的data,只能够使用组件中定义的data,其必须是一个函数
为什么呢?
因为只有函数return返回的每个地址都不相同,这样对于共用同一个变量进行计数的情况,就不会产生连锁反应
3.7 父子组件的通信
-
父传子用props
<div id="app"> <cpn :cmovies="movies" :cmessage="message"></cpn> </div> <template id="cpn"> <div> <!-- <p>{{cmovies}}</p>--> <ul> <li v-for="item in cmovies">{{item}}</li> </ul> <h2>{{cmessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> const cpn = { template : '#cpn', //使用数组方式 // props: ['cmovies','cmessage'], //对象方式 props: { // 1.类型限制 // cmovies: Array, // cmessage: String, // 2.提供一些默认值,以及必传值 cmessage: { type: String, default: 'aaaaaa', request: true }, cmovies: { type: Array, default: [] } }, data() { return {} }, methods: { } } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王','海贼王','海尔兄弟'] }, components: { cpn //增强写法 } }) </script>
html里面不能用驼峰标识,只能用 - 加小写代替
template标签里的东西最好用div标签包裹起来
-
子传父用自定义事件
<!--父组件模块--> <div id="app"> <cpn @item-click="cpnClick"></cpn> </div> <!--子组件模块--> <template id="cpn"> <div> <button v-for="item in categories" @click="btnClick(item)"> {{item.name}} </button> </div> </template> <script src="../js/vue.js"></script> <script> <!-- 子组件--> const cpn = { template : '#cpn', data() { return { categories: [ {id:'aaa',name:'热门推荐'}, {id:'bbb',name:'手机数码'}, {id:'ccc',name:'家电办公'}, {id:'ddd',name:'电脑办公'}, ] } }, methods: { btnClick(item){ // 发射事件:自定义事件 this.$emit("item-click",item) } } } // 父组件 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn }, methods: { cpnClick(item){ console.log('cpnClick',item) } } }) </script>
- 在子组件中,通过$emit()来触发事件
- 在父组件中,通过v-on来监听子组件事件
-
结合双向绑定
跳过,后看
3.8 父子组件的访问方式
-
父访问子
-
$children
比如button在父组件,在定义的点击事件函数,可以通过this.$children.*** 直接使用子组件的data、methods等
-
$refs
主要是用这个,因为这个可以通过里的ref直接指定是哪个组件
console.log(this.$refs.***.***)
-
-
子访问父(用得很少)
-
$parent
button在子组件,用法与上面相反
-
$root 访问祖宗属性
-
4. 插槽
4.1 为什么要用插槽
很多时候都要求复用组件,但是服用的组件需求可能会不会,比如不同页面的导航栏
4.2 如何使用
抽取共性,保留不同。相同的写好,不同的预留成插槽,在使用的时候可以一起替换
如果插槽里面有的就是默认值,不写替换元素的话直接显示默认值
4.3 具名插槽
就是给插槽取名字,方便具体替换哪个插槽
4.4 作用域插槽
-
编译作用域的概念
当前模块的代码只能够访问对应的组件,不能跨作用域
-
使用
父组件对子组件不满意,想以另外一种方式展示
父组件替换插槽的标签,但是内容由子组件来提供
代码用的时候再查
5. 模块化
5.1 为什么要有模块化
有很多js文件的时候,命名会冲突,所以需要封闭编写的函数,留出一个出口方便后面的函数调用
5.2 ES6模块化的导出导入
-
导出
- 先定义变量,函数,类,再导出
- 直接导出
-
导入
- 单个导入
- 全部取别名导入
-
default导出
不希望给这个功能取名,让导入者可以自己来命名,一个文件只能定义一次
-
引入js代码时加上 type=“module”
5.3 Webpack
-
概念:模块+打包
-
webpack和node和npm关系
-
基本使用
编写js文件,写好之间的依赖。运行打包的命令生成bundle.js在dist文件夹就可以了
-
ES6转ES5需要使用babel
-
创建Vue时template和el的关系
vue内部如果同时有el和template:
***
(这个是主属性,不是子模块里面的这个),template里的代码会覆盖index.html页面的<div id="app"></div>
在模块化开发之后,这样可以不用手动频繁修改
这里的template使用template:’<App’/>
-
Vue的终极使用方案
前面这样做,代码可能会很冗余,又是一步步抽取出来
原来除了index页面就这行,很简洁,但是负责控制它的main.js页面就很复杂了,需要拆分抽取
- 把root组件的template,data,methods等全部抽取到const App子组件中,再引入
- 可以把const App格外创建一个.js文件,作为默认值引入
- 2方法还是不够简洁,因为js文件里面这个多东西还是不太好看,把里面的各个部分都移入到新创建的.vue文件对应的部分中
- 可以在这个.vue文件中再在script部分中引入其它.vue子组件
npm stall命令是根据package.json里面的版本安装各种工具
npm run build 和 npm run dev 的区别 npm run serve
这里我还没有下载webpack
5.4 VueCLI3(我用的是4)
-
作用
可以快速搭建Vue开发环境以及对应的webpack配置
-
使用方式
-
先下载webpack和vue-cli
-
运行命令创建项目
vue create testvuecli3
-
-
创建项目分析
node_modeles:npm下载的东西放这里
public:代替以前的static,放资源和index
src:主代码
.browserslistrc:浏览器相关的配置
.babel.config.js:对babel的配置
package.json:配置文件
-
运行项目
- cd进入当年目录
- npm run server
-
main.js代码分析
new Vue({ render: h => h(App), }).$mount('#app') 相当于 new Vue({ el:'#app', render: funtion(h){ return h(App) } }) 相当于vue 1.0写法 new Vue({ el: '#app', template:'</App>' componets: {App} }) render就是把template模板里面的页面渲染出来 这是vue3的写法
-
配置去哪里了
- 启动配置服务器:vue ui
- 可以在node_modules里面找到
- 自定义配置文件vue.config.js
5.5 箭头函数
-
普通函数写法
const sum1 = function (x,y){ return x+y } //这种语法时对象的写法,定义的无名函数就是increment方法键的值,可以当作两步 increment: function (){ } 也等于 increment(){ }
-
常数作为函数名的写法
const sum2 = function (x,y){ return x+y } 若不写参数,默认返回整个函数体 function (x,y){ return x+y }
-
箭头函数写法
//经典格式: 函数名 = ( 参数 ) => { 方法体 } const sum3 = (x,y)=>{ return x+y; } //当方法体只有一行时,花括号可以省略: const sum4 = (x,y) => x+y; //当只有一个参数时,括号可以省略: const sum5 = x => { 方法体 }
-
箭头函数中的this引用的就是最近作用域中的this
const obj = { aaa(){ setTimeout(function (){ console.log(this); //window }) setTimeout(() => { console.log(this); //obj对象 }) } } obj.aaa()
6. 路由
6.1 发展的几个阶段
-
后端渲染和路由
早期的网站开发整个html页面是由服务器来渲染的,然后返回给客户端进行展示
-
前后端分离阶段
随着Ajax的出现,有了前后端分离的开发模式,后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面上,而且当移动端(ios/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可
-
前端路由阶段(SPA页面)
6.2 url的hash和history
还有history.replaceState({},’’,’***’) 这个是直接替换而不保存栈
history.back()等价于history.go(-1)
history.forward()等价于history.go(1)
这三个接口等用于浏览器界面的/前进后退
6.3 路由的安装
-
npm安装路由
-
编写路由代码index.js
- 导入路由对象,并且调用Vue.use(VueRouter)
- 创建路由实例,并且传入路由映射配置
- 在Vue实例中挂载创建的路由实例
// 配置路由相关的信息 import VueRouter from 'vue-router' import Vue from 'vue' //导入后面创建的.vue组件 //1.通过Vue.use(插件),安装插件 Vue.use(VueRouter) // 2.创建VueRouter对象,先抽取出来写 const routes = [ { path:'/', component: }, { path: '/', component: } ] const router = new VueRouter({ //配置路由和组件之间的映射关系 routes }) //3.将router对象传入到Vue实例,先导出 export default router
6.4 使用vue-router的步骤
-
创建路由组件 各个component页面 .vue
-
配置路由映射:组件和路径映射关系
import Home from "../components/Home" import About from "../components/About" *** const routes = [ { path:'/home', component: Home }, { path: '/about', component: About } ] const router = new VueRouter({ //配置路由和组件之间的映射关系 routes }) ***
-
使用路由:通过和
App.vue <template> <div id="app"> //默认被渲染成一个a标签 <router-link to="/home">首页</router-link> <router-link to="/about">关于</router-link> //占位的一个东西 <router-view></router-view> </div> </template>
-
main.js代码
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ //那两个.vue就是App.vue的子模块,因为下面router的传递 router, render: h => h(App) }).$mount('#app')
6.5 路由的一些修改
-
让进入项目时默认显示首页
路由文件加一个 { path:'/', redirect:'/home' },
-
改为history模式,浏览器地址也不会再多显示一个#
-
router-link的其它属性补充
-
通过代码跳转路由
6.6 动态路由的使用
根据不同的userId为例,期待可以根据拿到的数据动态路由到不同的ip地址,并且显示userId
-
更改主页面router-link标签地址
-
添加路由映射配置
-
修改显示页面
<template> <div> <h2>我是用户</h2> <p>嘿嘿嘿嘿嘿</p> {{userId}} //{{$route.params.userId}}也行,注意在这里使用不用加this </div> </template> <script> export default { name: "User", computed: { userId() { return this.$route.params.userId } } } </script> <style scoped> </style>
-
页面显示
- r o u t e r 和 router和 router和route的区别: r o u t e r 就 是 n e w 的 这 个 V u e R o u t e r , router就是new的这个VueRouter, router就是new的这个VueRouter,route是拿到的处于活跃状态的对象
6.7 懒加载
-
为什么要用懒加载
当打包构建应用时,Javascript包会变得非常大,影响页面加载,如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
-
路由懒加载的效果
-
懒加载代码
import Home form '../components/Home' 改为 const Home = () => import('../components/Home')
6.8 vue-router路由嵌套
在home主页又嵌入两个路由
-
分别创建两个小组件
-
在Home.vue写路由跳转信息
<template> <div> <h2>我是首页</h2> <p>哈哈哈哈哈</p> <router-link to="/home/news">新闻</router-link> <router-link to="/home/message">消息</router-link> <router-view></router-view> </div> </template>
-
在路由js文件中在home映射下面再嵌套子映射
{ path:'/home', component: Home, children: [ { //默认打开第一个 path:'', redirect:'news' }, { path:'news', component: HomeNews }, { path: 'message', component: HomeMessage } ] },
6.9 vue-router参数传递
把查询参数通过浏览器的?和&传过去
-
新建一个组件Profile.vue
-
再Vue.vue主页面写匹配的访问地址
<router-link :to="{path: '/profile', query: {name: 'why',age: 18, height: 1.88}}">档案</router-link>
-
路由.js文件
{ path: '/profile', component: Profile }
-
在跳转页面接收query的参数
<template> <div> <h2>我是Profile组件</h2> {{$route.query.name}} {{$route.query.age}} {{$route.query.height}} </div> </template>
URL:协议://主机:端口/路径?查询
scheme://host:port/path?query#fragmet
-
改为按钮
<!-- <router-link :to="'/user/'+userId">用户</router-link>--> <!-- <router-link :to="{path: '/profile', query: {name: 'why',age: 18, height: 1.88}}">档案</router-link>--> <button @click="userClick">用户</button> <button @click="profileClick">档案</button>
methods: { userClick(){ this.$router.push('/user/'+this.userId) }, profileClick(){ this.$router.push({ path: '/profile', query: { name: 'why', age: 18, height: 1.88 } }) } }
-
router和route
在User.vue中通过methods点击事件拿到router和route后分别打印
第一个是new的VueRouter
第二个是最近活跃的
所有的组件都继承自vue的原型,如果给vue的原型加个方法或者变量,每个组件都会收影响
Vue.prototype. --vue原型
6.10 导航守卫
-
更改路由访问不同页面的标题
-
在组件中一个个改
<script> export default { name: "Home", created() { document.title = '首页' } } </script>
太麻烦了,由于跳转都是经过路由,可以使用方法2
-
使用导航守卫
先在路由index.js中给每个跳转映射加一个title名字
{ path: '/about', component: About, meta:{ title: '关于' }, },
前置守卫
//前置守卫(hook) //从from跳转到to router.beforeEach((to,from,next) => { document.title = to.matched[0].meta.title, //next函数必须要有 next() })
-
-
导航守卫的补充
- 全局守卫
- 前置守卫即上面这个,路由跳转之前进行的回调
- router.afterEach,后置钩子,不需要主动调用next()函数,路由跳转之后
- 局部守卫
- 路由独享的守卫
- 组件内的守卫
- 全局守卫
6.11 keep-alive
离开某个组件的时候不让组件频繁被创建和销毁,可以保持原状
<keep-alive exclude="Profile,User"> //忽略多个用逗号隔开,不能加空格
<router-view></router-view>
</keep-alive>
只有写了keep-alive才能使用activated和deactivated这两个函数,是否保持活跃
7. Promise
7.1 出现原因
当网络请求非常复杂的时候,会出现回调地狱
7.2 使用Promise
<script>
new Promise((resolve, reject) => {
//第一次网络请求的代码
setTimeout(() => {
resolve()
},1000)
}).then(() => {
//第一次拿到结果的处理代码
console.log('Hello World');
console.log('Hello World');
console.log('Hello World');
console.log('Hello World');
return new Promise((resolve, reject) => {
//第二次网络请求的代码
setTimeout(() => {
resolve()
},1000)
})
}).then(() => {
//第二次处理的代码
console.log('Hello Vuejs');
console.log('Hello Vuejs');
console.log('Hello Vuejs');
console.log('Hello Vuejs');
return new Promise((resolve, reject) => {
//第三次网络请求的代码
setTimeout(() => {
resolve()
},1000)
})
}).then(() => {
// 第三次处理的代码
console.log('Hello Python');
console.log('Hello Python');
console.log('Hello Python');
console.log('Hello Python');
})
</script>
7.3 成功和失败
new Promise((resolve, reject) => {
//第一次网络请求的代码
setTimeout(() => {
//成功的时候调用resolve
// resolve('Hello world')
//失败的时候调用reject
reject('error message')
},1000)
}).then((data) => {
//第一次拿到结果的处理代码
console.log('data');
console.log('data');
console.log('data');
console.log('data');
}).catch((err) => {
console.log(err);
})
7.4 三种状态
- pending:等待状态
- fulfill:满足状态,回调resolve时就处于该状态,并且会回调.then()
- reject:拒绝状态,回调reject时就处于该状态,并且会回调.catch()
另一种写法
new Promise((resolve, reject) => {
//第一次网络请求的代码
setTimeout(() => {
//成功的时候调用resolve
// resolve('Hello world')
//失败的时候调用reject
reject('error message')
},1000)
}).then(成功的方法1,失败的方法2)
7.5 链式调用及简写
-
原代码
return new Promise(resolve => { //给res字符串拼接上222 resolve(res + '222') })
-
第一次简写:new Promise(resolve => resolve(结果))
return Promise.resolve(res + '222') or return Promise.reject('error message') | throw 'error message'
-
第二次简写:省略掉Promise.resolve
return res+'222'
7.6 all方法的使用
如果要两个请求都请求到了再开始执行
<script>
//模拟真实的请求
Promise.all([
new Promise((resolve, reject) => {
//逻辑代码1
}),
new Promise((resolve, reject) => {
//逻辑代码2
})
//results里面放上面这两个对象
]).then(results => {
//对results的处理
})
</script>
8. Vuex
8.1 Vuex概念
把公共的变量放在一个单例对象进行集中管理,而且可以做到响应式。父子组件传信息不用放在Vuex中
8.2 单界面到多界面状态管理切换
-
单页面的状态管理
-
如果组件之前关系复杂
-
安装vuex命令
npm install vuex --save
-
在store文件夹创建index.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ //共享的状态,对应官方给出图片的对应部分 state: { counter: 1000 }, mutations: { }, actions: { }, modules: { } })
-
在main.js里导入vuex
-
现在可以共享了,在components文件夹创建子组件HelloVue.vue
<template> <div> <h2>{{$store.state.counter}}</h2> //在这里直接通过vue components直接控制counter不好,因为没有经过mutations记录不到 </div> </template> <script> export default { name: "HelloVuex" } </script> <style scoped> </style>
-
根组件
<template> <div id="app"> <h2>{{$store.state.counter}}</h2> <!-- 在components里面注册过了这个子组件,所以有这个标签--> <hello-vuex/> </div> </template> <script> import HelloVuex from './components/HelloVuex' export default { name: 'App', components: { HelloVuex }, data() { return { message: '我是App组件' } } } </script>
-
8.3 Vuex的状态管理
以通过vuex对counter加减为例
-
官方图片
-
谷歌商店下载devtools插件
-
mutations里面定义函数
mutations: { increment(state){ state.counter++ }, decrement(state){ state.counter-- } },
-
通过按钮使用这两个函数
methods: { addition(){ this.$store.commit('increment') }, subtraction(){ this.$store.commit('decrement') } }
-
可以通过devtools监听到每个事件的状态
8.4 Vuex核心概念
-
state单一状态树
只要创建一个state就行了
-
getters的使用详解
-
作用
可以代替计算属性,而且所有人都能使用,不要重复造轮子
-
定义一个变量和一个对象数组
state: { counter: 1000, students: [ {id: 110, name: 'why', age: 18}, {id: 111, name: 'kobe', age: 24}, {id: 112, name: 'james', age: 30}, {id: 113, name: 'curry', age: 10}, ] },
-
定义处理数组的getters函数
-
组件中的使用
-
展示结果
-
-
Mutation
-
状态更新
只要改store,就要提交Mutation
-
传递参数
-
两种提交风格
-
上面的通过commit提交时一种普通的风格
-
另一种风格,它是一个包含type属性的对象
-
-
一种约定俗成的规矩
为了两边名字能够写对,定义方法名称的时候写成常量形式,格外定义一个js模块保存方法名称为常量,再在使用的时候import
-
-
数据的响应式原理
-
为什么能够做到响应式
vuex给state里的每个对象里的每个属性都加入了一个监听,发生变化后在网页上会实时响应
-
什么情况下没有响应式
直接使用普通方法添加和删除属性的时候不能实现响应式,要换一个函数
-
代码实例
给对象添加删除属性
-
-
Action(多参照官方提供的图)
-
作用
在Mutation中不能够出现异步的情况,因为这样的话devtools时监听不了的,监听显示的是错误的数据,Action类似于Mutation,但是是用来代替Mutation进行异步操作的
-
异步无参数情况下的使用
-
异步有一个参数的情况下使用
定义:aUpdateInfo(context,形参)
使用:this.$store.dispatch(‘aUpdateInfo’,实参)
-
异步传函数的情况下
用Promise更优雅
-
-
modules(套娃)
-
作用
-
与state结合
用modeleA里面的state
{{$store.state.a.name}}
-
与mutations结合
与之前一样,不用多加
-
与getters结合
与之前差不多,注意使用本getters的其它函数和根state的用法,使用对象的解构把context解构为state,getters,rootState
(原来之前只写state是因为只要用到state,实际上是写context的),对象的解构按照名字对应
-
与actions结合
这里commit是调用本部分mutations的函数
-
-
store文件夹的目录结构
建议把Vuex的核心属性除state以外全部抽取出来,成为一个个js文件
9. axios
9.1 asiox框架的基本使用
-
npm install axios --save
-
import axios
-
访问老师的接口地址
axios({ url: 'http://123.207.32.32:8000/home/multidata', /methods: 默认是get请求 //params:{地址后面?带的请求参数也可以写在这里 //} }).then(res => { //axios内部实现了Promise console.log(res); })
-
结果
9.2 发送并发请求
-
使用axios,可以放入多个请求的数据
axios.all([ axios({ url: 'http://123.207.32.32:8000/home/multidata', }),axios({ url: 'http://123.207.32.32:8000/home/data', params: { type: 'sell', page: 5 } }) ]).then(results => { console.log(results); })
-
axios.all([])返回的结果是一个数组,使用axios.spread可将数组[res1,res2]展开为res1,res2
]).then(axios.spread((res1,res2) => { console.log(res1); console.log(res2); }))
9.3 配置相关的信息
-
全局配置
axios.defaults可以设置公共的默认配置,如:
axios.default.baseURL = ‘123.207.32.32:8000’
-
常见的配置选项
9.4 axios的实例和模块封装
-
问题
以上是直接使用全局的axios和对应的配置进行网络请求,但是如果后端有多个ip地址的时候就不适用了
-
创建对应的axios的实例
const instance1 = axios.create({ baseURL: 'http://222.111.32.32:8000', timeout: 5000 }) instance1({ url: 'home/multitdata' }).then(res => { console.log(res); }) instance1({ url: '/home/data', params: { type: 'pop', page: 1 } }).then(res => { console.log(res); })
如果不止一个请求
const instance2 = axios.create({ baseURL: 'http://222.111.32.32:8000', timeout: 10000 })
-
为什么要进行模块封装
-
封装一个axios实例,使用的时候直接调用它就行了
request.js import axios from "axios"; //写function不写default是因为如果ip地址不同的话可以添加更多的封装函数 export function request(config) { //创建axios的实例 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) //发送真正的网络请求 return instance(config) }
调用的函数
//因为导入时没加default import {request} from "./network/request" request({ url: '/home/multidata' }).then(res => { console.log(res); }).catch(err => { console.log(err); })
9.5 拦截器
-
用于我们在发送每次请求或者得到响应后,进行对应的处理,写在发送真正的网路请求return instance(config)之前
-
请求拦截
instance.interceptors.request.use(config => { console.log(config); //不返回放行就拦截住了 return config //很少用到error }, err => { console.log(err); })
-
响应拦截
instance.interceptors.response.use(res => { console.log(res); return res.data //可以只返回data }, err => { console.log(err); })