赞
踩
跟着b站 coderwhy 老师所做的笔记,对比了几个感觉很适合Vue初学
链接:https://www.bilibili.com/video/BV15741177Eh?from=search&seid=5110308374587736588&spm_id_from=333.337.0.0
下载本地Markdown文件:https://gitee.com/xiao-li-tongxue/img/tree/master/
ES5之前因为if和for都没有块级作用域的概念, 所以在很多时候, 我们都必须借助于function的作用域来解决应用外面变量的问题.(因为function有作用域)
ES6中,加入了let, let它是有if和for的块级作用域
//1.变量作用域: 变量在什么范围内是可用的
{
var name = 'why';
console.log(name);
}
//在括号外面依然可以用,因为ES5之前var是没有块级作用域的概念的
console.log(name);
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
// 没有块级作用域引起的问题: for的块级
var btns = document.getElementsByTagName('button');
for (var i=0; i<btns.length; i++) {
console.log('执行成功');
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
</script>
上面也就相当于执行五次循环,每次循环都是下面这段代码
var i = 5;
var btns = document.getElementsByTagName('button');
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
...*3
若要解决此问题,ES5之前可以使用闭包
<button>按钮1</button> <button>按钮2</button> <button>按钮3</button> <button>按钮4</button> <button>按钮5</button> <script> // 为什么闭包可以解决问题: 函数是一个作用域. var btns = document.getElementsByTagName('button'); for (var i=0; i<btns.length; i++) { (function (num) { console.log('执行成功'); btns[i].addEventListener('click', function () { console.log('第' + num + '个按钮被点击'); }) })(i) } </script>
上面就相当于执行五次循环,每次循环的num值都不一样。
如果使用ES6语法的话,就简单很多
let关键字是有块级作用域的概念的
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
const btns = document.getElementsByTagName('button')
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
</script>
const关键字,在很多语言中已经存在, 比如C/C++中, 主要的作用是将某个变量修饰为常量.
在JavaScript中也是如此, 使用const修饰的标识符为常量, 不可以再次赋值.
建议: 在ES6开发中,优先使用const, 只有需要改变某一个标识符的时候才使用let.
<script> /* //1.注意一: 一旦给const修饰的标识符被赋值之后, 不能修改 const name = 'why'; //错误,不能修改 name = 'abc'; */ // 2.注意二: 在使用const定义标识符,必须进行赋值 // const name; // 3.注意三: 常量的含义是指向的对象不能修改, 但是可以改变对象内部的属性. const obj = { name: 'why', age: 18, height: 1.88 } //这样是错误的: obj = {} console.log(obj); //但修改内部属性却可以 obj.name = 'kobe'; obj.age = 40; obj.height = 1.87; console.log(obj); </script>
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
//无形参 var f = () => 5; // 等同于 var f = function () { return 5 }; var f = a = > a //一个参数,等同于 var f = function(a){ return a; } //多个形参 var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
1,箭头函数:出现的作用除了让函数的书写变得很简洁,可读性很好外;最大的优点是解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题。
2,我们常见的window属性和方法有alter,document,parseInt,setTimeout,setInterval,localtion等等,这些在默认的情况下是省略了window前缀的。(window.alter = alter)。
箭头函数没有自己的this, 它的this是继承而来;
<script>
function test() {
console.log(this);
}
test();
</script>
结果是: window
原因: test()是一个全局函数,也就是说是挂在window对象下的,所以test()等价于 window.test() ,所以此时的this是window
<script>
var obj = {
say: function () {
setTimeout(function () {
console.log(this)
});
}
}
obj.say();
</script>
结果是: window
匿名函数,定时器中的函数,由于没有默认的宿主对象,所以默认this指向window
<script> const obj = { aaa() { setTimeout(function () { // * setTimeout(function () { console.log(this); // window前缀,this指向window }) setTimeout(() => { console.log(this); // window,上一层是 * 标记处,window前缀方法,this指向window }) }) setTimeout(() => { //*** setTimeout(function () { console.log(this); // window前缀 }) setTimeout(() => { console.log(this); // Object ,找到上一层,即 *** 标记处,也是箭头函数,再找上一层,即 aaa() 方法处,this指向Object }) }) } } obj.aaa() </script>
高阶函数2.js
let total = nums.filter(n => n<100).map(n => n*2).reduce((pre,n) => pre + n)
<div id="app">
</div>
<script src="../js/vue.js"></script>
<script src="高阶函数2.js"></script>
<script>
console.log(total)
</script>
按照我们之前的方式
高阶函数.js:
const nums = [10, 20, 111, 222, 444, 40, 50] // 1.需求: 取出所有小于100的数字 let newNums = [] for (let n of nums) { if (n < 100) { newNums.push(n) } } // 2.需求:将所有小于100的数字进行转化: 全部*2 let new2Nums = [] for (let n of newNums) { new2Nums.push(n * 2) } // 3.需求:将所有new2Nums数字相加,得到最终的结果 let total = 0 for (let n of new2Nums) { total += n } console.log(total);
利用函数:
const nums = [10, 20, 111, 222, 444, 40, 50] //1.filter函数的使用 // 10, 20, 40, 50 let newNums = nums.filter(function (n) { return n < 100 }) //filter函数返回一个数组,将 n<100 的值都存入数组中 // console.log(newNums); // 2.map函数的使用 // 20, 40, 80, 100 let new2Nums = newNums.map(function (n) { // 20 return n * 2 }) // 3.reduce函数的使用 /*reduce作用对数组中所有的内容进行汇总,第一个参数中的第一个值preValue的默认值是第二个参数的值0,第一个参数的第二个值n是 数组中的值*/ let total = new2Nums.reduce(function (preValue, n) { return preValue + n }, 0) console.log(total); //第一次: preValue 0 n 20 //第二次: preValue 20 n 40 //第三次: preValue 60 n 80 //第四次: preValue 140 n 100 //240
转换为高阶函数:
let total = nums.filter(function (n) {
return n < 100
}).map(function (n) {
return n * 2
}).reduce(function (prevValue, n) {
return prevValue + n
}, 0)
console.log(total);
利用箭头函数的高阶函数:
即开头的高阶函数2.js
let total = nums.filter(n => n<100).map(n => n*2).reduce((pre,n) => pre + n)
Vue (读音 /vjuː/,类似于 view),不要读错。
Vue是一个渐进式的框架,什么是渐进式的呢?
Vue有很多特点和Web开发中常见的高级功能
安装Vue的方式有很多:
方式一:直接CDN引入
你可以选择引入开发环境版本还是生产环境版本
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
方式二:下载和引入
开发环境 https://vuejs.org/js/vue.js
生产环境 https://vuejs.org/js/vue.min.js
方式三:NPM安装
后续通过webpack和CLI的使用,我们使用该方式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app">{{message}}</div> <!--显示下方数据--> <div>{{message}}</div> <!--只会显示{{message}}--> <script src="../js/vue.js"></script> <script> const app = new Vue({ el:'#app', //用于挂载要管理的元素 data:{ //定义一些数据 message:'你好啊,李银河' } }) </script> </body> </html>
我们来阅读JavaScript代码,会发现创建了一个Vue对象。
创建Vue对象的时候,传入了一些options:{}
{}中包含了el属性:该属性决定了这个Vue对象挂载到哪一个元素上,很明显,我们这里是挂载到了id为app的元素上
{}中包含了data属性:该属性中通常会存储一些数据
这些数据可以是我们直接定义出来的,比如像上面这样,也可能是来自网络,从服务器加载的。
执行到8行代码显然出对应的HTML
执行第13行代码创建Vue实例,并且对原HTML进行解析和修改。
并且,目前我们的代码是可以做到响应式的。
<body> <div id="app"> {{movies}} </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el:'#app', data:{ movies:['星际穿越','少年派','大话西游'] } }) </script> </body>
这样是显示不出来列表的,网页显示是这样:
可以利用v-for显示(后面会讲)
<body> <div id="app"> <ul> <li v-for="item in movies "> {{item}} </li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el:'#app', data:{ movies:['星际穿越','少年派','大话西游'] } }) </script> </body>
在浏览器控制台添加列表数据
点击 + 计数器+1
点击 - 计数器 -1
这里,我们又要使用新的指令和属性了
新的属性:methods,该属性用于在Vue对象中定义方法。
新的指令:@click, 该指令用于监听某个元素的点击事件,并且需要指定当发生点击时,执行的方法(方法通常是methods中定义的方法)
@click 是v-on:click 的语法糖,即 v-on:click的简写。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h2>当前计数:{{counter}}</h2> <!-- <button v-on:click="add">+</button> <button v-on:click="sub">-</button> --> <button @click="add">+</button> <button @click="sub">-</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el:'#app', data:{ counter:0 }, methods:{ add(){ console.log('add被执行'); this.counter++; }, sub(){ console.log('sub被执行'); this.counter--; } } }) </script>
MVVM与MVC最大的区别就是:它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。
Vue实例中的data相当于Model层,而ViewModel层的核心是Vue中的双向数据绑定,即Model变化时VIew可以实时更新,View变化也能让Model发生变化。
整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性。
View层:
视图层
在我们前端开发中,通常就是DOM层。
主要的作用是给用户展示各种信息。
Model层:
数据层
数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据。
在我们计数器的案例中,就是后面抽取出来的obj,当然,里面的数据可能没有这么简单。
VueModel层:
视图模型层
视图模型层是View和Model沟通的桥梁。
一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中
另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情 况下改变对应的Data。
简而言之:从生到死的过程,从Vue实例创建-运行-销毁的过程
Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、销毁等一系列过程
Vue从生到死的过程中伴随着各种各样的事件,这些事件会自动触发一些方法.这些方法我们统称为生命周期方法
生命周期钩子 = 生命周期函数 = 生命周期事件
beforeCreate:
created:
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue生命周期方法</title> <!--引入vue框架--> <script src="js/vue.js"></script> </head> <body> <div id="app"> <p>{{msg}}</p> </div> <script> let vm = new Vue({ el:'#app', data:{ msg:"IT程序员的日常" }, methods:{ say:function () { console.log("IT程序员的日常"); } }, beforeCreate:function () { /*执行beforeCreate的时候,表示Vue刚刚出生,还没有任何内容,data/methods都没有初始化*/ //console.log(this.msg); //this.say(); //console.log(this.say); }, created:function () { /*执行created的时候,表示Vue实例已经初始化好了部分内容,data/methods 想在Vue实例中最早访问到data/methods,只有在这个方法才能访问 */ //console.log(this.msg); //this.say(); //console.log(this.say); }, beforeMount:function () { /*执行beforeMount,表示已经根据数据编译好了模板,但是还没有渲染到界面上*/ // console.log(document.querySelector("p").innerText); // console.log(document.querySelector("p").innerHTML); }, mounted:function () { /*执行mounted,表示已经根据数据编译好了模板,已经将模板有渲染到界面上,此时可以对界面进行其他操作了*/ console.log(document.querySelector("p").innerText); console.log(document.querySelector("p").innerHTML); }, beforeUpdate:function(){ /*主要data中的数据发生了变化就会执行 执行beforeUpdate时候,data的数据已经是最新的了,但是没有更新界面上的数据*/ // console.log(this.msg); // console.log(document.querySelector("p").innerText); // console.log(document.querySelector("p").innerHTML); }, updated:function () { /*主要data中的数据发生了变化就会执行 执行updated时候,data的数据已经是最新的了,界面上的数据也已经更新*/ /* console.log(this.msg); console.log(document.querySelector("p").innerText); console.log(document.querySelector("p").innerHTML); */ }, beforeDestroy:function(){ /*执行beforeDestroy的时候,表示Vue实例即将销毁,但是还未销毁,实例中的数据等都可以使用 最后能使用Vue实例的地址 */ }, destroyed:function () { /* 执行destroyed的时候,表示vue实例完全销毁,实例中的任何内容都不能被使用了 */ } }) </script> </body> </html>
Mustache语法:也就是双大括号语法。
Mustache:胡子/胡须
<div id="app"> <h2>{{message}}</h2> <h2>{{message}}, 李银河!</h2> <!--mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式--> <h2>{{firstName + lastName}}</h2> <h2>{{firstName + ' ' + lastName}}</h2> <h2>{{firstName}} {{lastName}}</h2> <h2>{{counter * 2}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', firstName: 'kobe', lastName: 'bryant', counter: 100 }, }) </script>
当我们不希望界面某些数据跟随改变的时候,可以使用 v-once 指令
该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的)
该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变
<div id="app">
<h2>{{message}}</h2>
<h2 v-once>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
若利用响应式修改数据:message改变,添加 v-once 的标签所显示的界面没有被重新渲染
某些情况下,我们从服务器请求到的数据本身就是一个HTML代码
如果我们直接通过 {{}} 来输出,会将HTML代码也一起输出。但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容。
如果我们希望解析出HTML展示,可以使用v-html指令,该指令后面往往会跟上一个string类型,会将字符串中的html解析出来并且进行渲染
<div id="app">
<h2>{{url}}</h2>
<h2 v-html="url"></h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
url: '<a href="http://www.baidu.com">百度一下</a>'
}
})
</script>
v-text作用和Mustache比较相似:都是用于将数据显示在界面中
v-text通常情况下,接受一个string类型,且后面跟上其他字符串等,不会追加
<div id="app">
<h2>{{message}}, 李银河!</h2>
<h2 v-text="message">, 李银河!</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法
比如下面的代码:
第一个h2元素中的内容会被编译解析出来对应的内容
第二个h2元素中会直接显示{{message}}
<div id="app">
<h2>{{message}}</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
在某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签
v-cloak指令:它会在vue实例结束编译时从绑定的html元素上移除
cloak: 斗篷
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> [v-cloak] { display: none; } </style> </head> <body> <!--vue解析之前,div中有 v-cloak属性,此标签被隐藏--> <!--vue解析之后,div中没有 v-cloak属性了,此标签显示--> <div id="app" v-cloak> <h2>{{message}}</h2> </div> <script src="../js/vue.js"></script> <script> setTimeout(function () { const app = new Vue({ el: '#app', data: { message: '你好啊' } }) }, 1000) </script> </body> </html>
前面我们学习的指令主要作用是将值插入到我们模板的内容当中。
但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
比如动态绑定a元素的href属性,比如动态绑定img元素的src属性
这个时候,我们可以使用v-bind指令:
作用:动态绑定属性
缩写(语法糖)::
<div id="app"> <!-- 错误的做法: 这里不可以使用mustache语法--> <!--<img src="{{imgURL}}" alt="">--> <!-- 正确的做法: 使用v-bind指令 --> <!--alt属性是在图片不能正常加载时候显示的提示语--> <img v-bind:src="imgURL" alt=""> <a v-bind:href="aHref">百度一下</a> <!--<h2>{{}}</h2>--> <!--语法糖的写法--> <img :src="imgURL" alt=""> <a :href="aHref">百度一下</a> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp', aHref: 'http://www.baidu.com' } }) </script>
很多时候,我们希望动态的来切换class,比如:
当数据为某个状态时,字体显示红色。当数据另一个状态时,字体显示黑色。
绑定class有两种方式:
对象语法的含义是:class后面跟的是一个对象
对象语法有下面这些用法:
用法一:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2>
用法二:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title active line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性【
<h2 class="title" :class="classes">Hello World</h2>
案例:
<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">{{message}}</h2> <!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>--> <!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>--> <h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2> <h2 class="title" v-bind:class="getClasses()">{{message}}</h2> <button v-on:click="btnClick">按钮</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', isActive: true, isLine: true }, methods: { btnClick: function () { this.isActive = !this.isActive }, getClasses: function () { return {active: this.isActive, line: this.isLine} } } }) </script> </body>
点击按钮之后:
数组语法的含义是:class后面跟的是一个数组
数组语法有下面这些用法:
加单引号表示字符串,没有单引号表示变量
用法一:直接通过[]绑定一个类
<h2 :class="['active']">Hello World</h2>
用法二:也可以传入多个值
<h2 :class="['active', 'line']">Hello World</h2>
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
<h2 class="title" :c lass="['active', 'line']">Hello World</h2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
案例:
<div id="app"> <!--加上' ' 表示字符串--> <h2 class="title" :class="['active', 'line']">{{message}}</h2> <!--不加' ' 表示变量--> <h2 class="title" :class="[active, line]">{{message}}</h2> <h2 class="title" :class="getClasses()">{{message}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', active: 'aaaaaa', line: 'bbbbbbb' }, methods: { getClasses: function () { return [this.active, this.line] } } }) </script>
作业需求: 点击列表中的哪一项, 那么该项的文字变成红色
v-for中使用下标属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!--将active类颜色改为red--> <style> .active{ color: red; } </style> </head> <body> <!--作业需求: 点击列表中的哪一项, 那么该项的文字变成红色--> <div id="app"> <ul> <li v-for="(m, index) in movies" :class="{active: isActive == index}" @click="changeColor(index)">{{m}}</li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { movies: ['海王', '海尔兄弟', '火影忍者', '进击的巨人'], isActive : -1 }, methods:{ changeColor(index){ this.isActive = index; } } }) </script> </body> </html>
我们可以利用v-bind:style来绑定一些CSS内联样式
在写CSS属性名的时候,比如font-size,我们可以使用驼峰式 (camelCase) fontSize ,或短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size’
绑定class有两种方式:
:style="{color: currentColor, fontSize: fontSize + 'px'}"
style后面跟的是一个对象类型
对象的key是CSS属性名称
对象的value是具体赋的值,值可以来自于data中的属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .title { font-size: 50px; color: red; } </style> </head> <body> <div id="app"> <!--<h2 :style="{key(属性名): value(属性值)}">{{message}}</h2>--> <!--'50px'必须加上单引号, 否则是当做一个变量去解析--> <!--<h2 :style="{fontSize: '50px'}">{{message}}</h2>--> <!--finalSize当成一个变量使用--> <!--<h2 :style="{fontSize: finalSize}">{{message}}</h2>--> <h2 :style="{fontSize: final Size + 'px', backgroundColor: finalColor}">{{message}}</h2> <h2 :style="getStyles()">{{message}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', finalSize: 100, finalColor: 'red', }, methods: { getStyles: function () { return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor} } } }) </script> </body> </html>
<div v-bind:style="[baseStyles, overridingStyles]"></div>
style后面跟的是一个数组类型
多个值以 , 分割即可
案例:
<div id="app">
<h2 :style="[baseStyle, baseStyle1]">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
baseStyle: {backgroundColor: 'red'},
baseStyle1: {fontSize: '100px'},
}
})
</script>
计算属性是写在实例的computed选项中的
<div id="app"> <h2>{{firstName + ' ' + lastName}}</h2> <h2>{{firstName}} {{lastName}}</h2> <h2>{{getFullName()}}</h2> <!--计算属性不用加(),将其当做一个属性使用--> <h2>{{fullName}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { firstName: 'Lebron', lastName: 'James' }, // computed: 计算属性() computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }, methods: { getFullName() { return this.firstName + ' ' + this.lastName } } }) </script>
<div id="app"> <h2>总价格: {{totalPrice}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { books: [ {id: 110, name: 'Unix编程艺术', price: 119}, {id: 111, name: '代码大全', price: 105}, {id: 112, name: '深入理解计算机原理', price: 98}, {id: 113, name: '现代操作系统', price: 87}, ] }, computed: { totalPrice: function () { let result = 0 for (let i = 0; i < this.books.length; i++) { result += this.books[i].price } return result /*其他循环的写法*/ /*for (let i in this.books) { result += this.books[i].price }*/ /* for (let book of this.books) { result +=book.price }*/ } } }) </script>
上面写的计算属性只是简写,实际上是利用get方法
<div id="app"> <h2>{{fullName}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { firstName: 'Kobe', lastName: 'Bryant' }, computed: { /* fullName: function () { return this.firstName + ' ' + this.lastName }*/ //上面是计算属性的简写,实际上计算属性是下面这样写的 // 计算属性一般是没有set方法, 只读属性. fullName: { //若使用set方法,需要接受参数 set: function(newValue) { // console.log('-----', newValue); //利用空格分隔符,将firstName和lastName分出存入names数组中 //split() 方法用于把一个字符串分割成字符串数组 const names = newValue.split(' '); this.firstName = names[0]; this.lastName = names[1]; }, get: function () { return this.firstName + ' ' + this.lastName } }, } }) </script>
因为有set属性,所以可以修改fullName计算属性中set方法形参
计算属性有缓存
<div id="app"> <!--1.直接拼接: 语法过于繁琐,一般不用--> <!--<h2>{{firstName}} {{lastName}}</h2>--> <!--2.通过定义methods--> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <!--3.通过computed--> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> </div> <script src="../js/vue.js"></script> <script> // angular -> google // TypeScript(microsoft) -> ts(类型检测) // flow(facebook) -> const app = new Vue({ el: '#app', data: { firstName: 'Kobe', lastName: 'Bryant' }, methods: { getFullName: function () { console.log('getFullName'); return this.firstName + ' ' + this.lastName } }, computed: { fullName: function () { console.log('fullName'); return this.firstName + ' ' + this.lastName } } }) </script>
在前端开发中,我们需要经常和用于交互
这个时候,我们就必须监听用户发生的时间,比如点击、拖拽、键盘事件等等
在Vue中如何监听事件呢?使用v-on指令
<div id="app"> <h2>{{counter}}</h2> <!--可以直接写成表达式--> <button v-on:click="counter++">+</button> <button v-on:click="counter--">-</button> <!--写成函数形式--> <!--<button v-on:click="increment">+</button>--> <!--<button v-on:click="decrement">-</button>--> <!--语法糖格式:@ --> <button @click="increment">+</button> <button @click="decrement">-</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { counter: 0 }, methods: { increment() { this.counter++ }, decrement() { this.counter-- } } }) </script>
<div id="app"> <!--1.事件调用的方法没有参数--> <button @click="btn1Click()">按钮1</button> <button @click="btn1Click">按钮1</button> <!--<button @click="btn2Click(123)">按钮2</button>--> <!-- 生成的是 -------- 123--> <!--如果函数需要参数,但是没有传入, 那么函数的形参为undefined--> <!-- <button @click="btn2Click()">按钮2</button>--> <!-- 生成的是 -------- undefined--> <!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event原生事件对象作为参数传入到方法--> <button @click="btn2Click">按钮2</button> <!--3.方法定义时, 我们需要event对象, 同时又需要其他参数--> <!-- 在调用方法时, 如何手动的获取到浏览器参数的event对象: $event--> <button @click="btn3Click(abc, $event)">按钮3</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', abc: 123 }, methods: { btn1Click() { console.log("btn1Click"); }, btn2Click(event) { console.log('--------', event); }, btn3Click(abc, event) { console.log('++++++++', abc, event); } } }) </script>
事件冒泡:点击子级元素接收到事件后,会把他接收到的事件传播给他的父级,也就是父级也会接收到子级的事件
<div id="app"> <!--1. .stop修饰符的使用,阻止Html的事件冒泡--> <!--若不加 .stop修饰符,会发生事件冒泡,即点击div中的按钮,回调bthClick方法,会将接收到的事件传播给他的父级(div),也就是divClick也会被回调--> <div @click="divClick"> aaaaaaa <button @click.stop="btnClick">按钮</button> </div> <!--2. .prevent修饰符的使用--> <!--阻止默认行为,点击提交后,不会直接跳转,而会回调submitClick方法--> <br> <form action="baidu"> <input type="submit" value="提交" @click.prevent="submitClick"> </form> <!--3. @keyup. 监听某个键盘的键帽,这是监听回车,当回车敲下并回弹的时候,回调keyup方法--> <!--若只写 @keyup="keyUp",则点击任意键盘回弹的时候都会回调keyUp方法--> <input type="text" @keyup.enter="keyUp"> <!--4. .once修饰符的使用--> <!-- 点击回调只会调用一次,点击第二次的时候不会被调用--> <button @click.once="btn2Click">按钮2</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, methods: { btnClick() { console.log("btnClick"); }, divClick() { console.log("divClick"); }, submitClick() { console.log('submitClick'); }, keyUp() { console.log('keyUp'); }, btn2Click() { console.log('btn2Click'); } } }) </script>
v-if后面的条件为false时,对应的元素以及其子元素不会渲染,也就是根本没有不会有对应的标签出现在DOM中。
<div id="app"> <h2 v-if="isShow"> <div>abc</div> <div>abc</div> <div>abc</div> <div>abc</div> <div>abc</div> {{message}} </h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', isShow: true } }) </script>
<div id="app"> <h2 v-if="isShow"> <div>abc</div> <div>abc</div> <div>abc</div> <div>abc</div> <div>abc</div> {{message}} </h2> <h1 v-else>isShow为false时, 显示我</h1> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', isShow: true } }) </script>
一般这种多条件判断不用这些语法,可以封装为计算属性或者函数
<div id="app"> <h2 v-if="score>=90">优秀</h2> <h2 v-else-if="score>=80">良好</h2> <h2 v-else-if="score>=60">及格</h2> <h2 v-else>不及格</h2> <!--<h1>{{result}}</h1>--> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { score: 62 }, /* //一般不用 v-if v-else-if...的语法,因为有些麻烦,一般封装为函数或者计算属性,方便调用 computed: { result() { let showMessage = ''; if (this.score >= 90) { showMessage = '优秀' } else if (this.score >= 80) { showMessage = '良好' }else if (this.score>=60){ showMessage = '及格' }else { showMessage = '不及格' } return showMessage } }*/ }) </script>
<div id="app"> <span v-if="isUser"> <label for="username">用户账号</label> <!--placeholder属性提供可描述输入字段预期值的提示信息--> <input type="text" id="username" placeholder="用户账号"> </span> <span v-else> <label for="email">用户邮箱</label> <input type="text" id="email" placeholder="用户邮箱"> </span> <button @click="isUser = !isUser">切换类型</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { isUser: true } }) </script>
但是这样会有一个问题,如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
但是按道理讲,我们应该切换到另外一个input元素中了。在另一个input元素中,我们并没有输入内容。
这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。
解决方案:
如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key
并且我们需要保证key的不同
更改方案:
添加key,保证key的不同
<div id="app"> <span v-if="isUser"> <label for="username">用户账号</label> <input type="text" id="username" placeholder="用户账号" key="username"> </span> <span v-else> <label for="email">用户邮箱</label> <input type="text" id="email" placeholder="用户邮箱" key="email"> </span> <button @click="isUser = !isUser">切换类型</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { isUser: true } }) </script>
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
v-if当条件为false时,压根不会有对应的元素在DOM中。
v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
开发中如何选择呢?
<div id="app"> <!--v-if: 当条件为false时, 包含v-if指令的元素, 根本就不会存在dom中--> <h2 v-if="isShow" id="aaa">{{message}}</h2> <!--v-show: 当条件为false时, v-show只是给我们的元素添加一个行内样式: display: none--> <h2 v-show="isShow" id="bbb">{{message}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', isShow: true } }) </script>
当我们有一组数据需要进行渲染时,我们就可以使用v-for来完成。
v-for的语法类似于JavaScript中的for循环
格式如下:item in items的形式
<div id="app"> <!--1.在遍历的过程中,没有使用索引值(下标值)--> <ul> <li v-for="item in names">{{item}}</li> </ul> <!--2.在遍历的过程中, 获取索引值--> <ul> <li v-for="(item, index) in names"> {{index+1}}.{{item}} </li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { names: ['why', 'kobe', 'james', 'curry'] } }) </script>
<div id="app"> <!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value--> <ul> <li v-for="item in info">{{item}}</li> </ul> <!--2.获取key和value 格式: (value, key) --> <ul> <li v-for="(value, key) in info">{{value}}-{{key}}</li> </ul> <!--3.获取key和value和index 格式: (value, key, index) --> <ul> <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { info: { name: 'why', age: 18, height: 1.88 } } }) </script>
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性
为什么需要这个key属性呢(了解)
这个其实和Vue的虚拟DOM的Diff算法有关系
这里我们借用React’s diff algorithm中的一张图来简单说明一下:
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点,我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率
所以我们需要使用key来给每个节点做一个唯一标识
Diff算法就可以正确的识别此节点
找到正确的位置区插入新的节点。
所以一句话,key的作用主要是为了高效的更新虚拟DOM
div id="app"> <ul> <!--绑定的key必须和后面的item是一一对应的,所以不能用index--> <li v-for="item in letters" :key="item">{{item}}</li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { letters: ['A', 'B', 'C', 'D', 'E'] } }) </script>
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,不用刷新页面,视图会发生对应的更新
<div id="app"> <ul> <li v-for="item in letters">{{item}}</li> </ul> <button @click="btnClick">按钮</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { letters: ['a', 'b', 'c', 'd'] }, methods: { btnClick() { // 1.push方法 添加最后一个元素 // this.letters.push('aaa') // this.letters.push('aaaa', 'bbbb', 'cccc') // 2.pop(): 删除数组中的最后一个元素 // this.letters.pop(); // 3.shift(): 删除数组中的第一个元素 // this.letters.shift(); // 4.unshift(): 在数组最前面添加元素 // this.letters.unshift() // this.letters.unshift('aaa', 'bbb', 'ccc') // 5.splice作用: 删除元素/插入元素/替换元素 /* 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素) 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素 其实就是删除 第一个参数 后面的 第二个参数 个元素,然后加上新元素 */ this.letters.splice(1, 3, 'm', 'n', 'l', 'x') // this.letters.splice(1, 0, 'x', 'y', 'z') // 6.sort() 排序 // this.letters.sort() // 7.reverse() 反转数据 // this.letters.reverse() // 注意: 通过索引值修改数组中的元素,这个不是响应式的 // this.letters[0] = 'bbbbbb'; //若要修改,则可以通过以下代码修改 //方法1 // this.letters.splice(0, 1, 'bbbbbb') //方法2 // set(要修改的对象, 索引值, 修改后的值) // Vue.set(this.letters, 0, 'bbbbbb') } } }) </script>
通过索引值修改数组中的元素,不是响应式的,可以利用splice或者Vue的set方法修改数组中的元素
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!--rel 属性规定当前文档与被链接文档之间的关系。 只有 rel 属性为 "stylesheet" 值得到了所有浏览器的支持。其他值只得到了部分地支持。 --> <link rel="stylesheet" href="style.css"> </head> <body> <div id="app"> <div v-if="books.length"> <table> <thead> <tr> <th></th> <th>书籍名称</th> <th>出版日期</th> <th>价格</th> <th>购买数量</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item, index) in books"> <td>{{item.id}}</td> <td>{{item.name}}</td> <td>{{item.date}}</td> <!--用 | 符号后跟过滤器,前面相当于传入过滤器的参数--> <td>{{item.price | showPrice}}</td> <td> <!--当书籍的数量小于或等于1的时候,无效化 - 按钮--> <button @click="decrement(index)" v-bind:disabled="item.count <= 1">-</button> {{item.count}} <button @click="increment(index)">+</button> </td> <td><button @click="removeHandle(index)">移除</button></td> </tr> </tbody> </table> <h2>总价格: {{totalPrice | showPrice}}</h2> </div> <h2 v-else>购物车为空</h2> </div> <script src="../js/vue.js"></script> <script src="main.js"></script> <script> </script> </body> </html>
main.js
const app = new Vue({ el: '#app', data: { books: [ { id: 1, name: '《算法导论》', date: '2006-9', price: 85.00, count: 1 }, { id: 2, name: '《UNIX编程艺术》', date: '2006-2', price: 59.00, count: 1 }, { id: 3, name: '《编程珠玑》', date: '2008-10', price: 39.00, count: 1 }, { id: 4, name: '《代码大全》', date: '2006-3', price: 128.00, count: 1 }, ] }, methods: { // 可以这样写,也可以加个过滤器 //getFinalPrice(price) { // return '¥' + price.toFixed(2) // } increment(index) { this.books[index].count++ }, decrement(index) { this.books[index].count-- }, removeHandle(index) { this.books.splice(index, 1) } }, computed: { totalPrice() { let totalPrice = 0 for (let i = 0; i < this.books.length; i++) { totalPrice += this.books[i].price * this.books[i].count } return totalPrice } }, filters: { showPrice(price) { return '¥' + price.toFixed(2) } } })
style.css
table { /*border:边框的样式 1px表示线的像素 solid表示实线 #e9e9e9表示白色*/ border: 1px solid #e9e9e9; /*为表格设置合并边框模型 border-collapse 属性设置表格的边框是否被合并为一个单一的边框,还是象在标准的 HTML 中那样分开显示 */ border-collapse: collapse; /*设置表格的边框间距*/ border-spacing: 0; } th, td { /*边距填充*/ padding: 8px 16px; border: 1px solid #e9e9e9; text-align: left; } th { /*背景颜色*/ background-color: #5c6b77; /*字体颜色*/ color: #f7f7f7; /*设置段落字体的粗细*/ font-weight: 600; }
双向绑定数据
<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>
v-model其实是一个语法糖,它的本质上是包含两个操作:
v-bind绑定一个value属性
v-on指令给当前元素绑定input事件
也就是说下面三行代码等同
<input type="text" v-model="message">
<!--等同于-->
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
<!--语法糖-->
<input type="text" :value="message" @input="message = $event.target.value">
单选框
for属性规定label与哪个表单元素绑定
label 和表单控件绑定方式有两种:
方法一:将表单控件作为label的内容,这种就是隐式绑定。
此时不需要for属性,绑定的控件也不需要id属性。
隐式绑定:
<label>Date of Birth: <input type="text" name="DofB" /></label>
方法二:为label标签下的for属性命名一个目标表单的id,这种就是显示绑定,for属性绑定input的id
显式绑定:
<label for="SSN">Social Security Number:</label>
<input type="text" name="SocSecNum" id="SSN" />
为什么要给label上面加上for属性
给 label 加了 for 属性绑定了input控件后,可以提高鼠标用户的用户体检
如果在label 元素内点击单选框的文本,就会触发此控件,也就是说,当用户渲染该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上
如下面点击 单选框后面的文本 男 就会自动选上那个单选框,若不加 lable 元素,则点击 男 没有反应
关于 radio 类型标签:
<div id="app"> <!--隐式--> <label> <!--当 v-model绑定同一个值的时候,name可以省略--> <input type="radio" id="male" value="男" v-model="sex">男 <!--<input type="radio" id="male" name='sex' value="男" v-model="sex">男--> </label> <!--显式--> <input type="radio" id="female" value="女" v-model="sex"> <!--<input type="radio" id="female" name='sex' value="女" v-model="sex">女--> <label for="female"> 女 </label> <h2>您选择的性别是: {{sex}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', sex: '女' } }) </script>
单个勾选框、复选框
单个勾选框:
多个复选框:
<div id="app"> <!-- 1.checkbox单选框,绑定的是一个布尔值 --> <label for="agree"> <input type="checkbox" id="agree" v-model="isAgree">同意协议 </label> <h2>您选择的是: {{isAgree}}</h2> <!--当勾选同意了之后,才能进行下一步--> <button :disabled="!isAgree">下一步</button> <br/> <br/> <br/> <br/> <!--2.checkbox多选框,绑定的是一个数组--> <input type="checkbox" value="篮球" v-model="hobbies">篮球 <input type="checkbox" value="足球" v-model="hobbies">足球 <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球 <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球 <h2>您的爱好是: {{hobbies}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', isAgree: false, // 单选框 hobbies: [], // 多选框, } }) </script>
单选:只能选中一个值
多选:可以选中多个值
<div id="app"> <!--1.仅支持选择一个的选择列表--> <select name="abc" v-model="fruit"> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="榴莲">榴莲</option> <option value="葡萄">葡萄</option> </select> <h2>您选择的水果是: {{fruit}}</h2> <!--2.可选择多个的选择列表--> <!--multiple表示可接受多个值的文件上传字段,按住ctrl键即可选择多个--> <select name="abc" v-model="fruits" multiple> <option value="苹果">苹果</option> <option value="香蕉">香蕉</option> <option value="榴莲">榴莲</option> <option value="葡萄">葡萄</option> </select> <h2>您选择的水果是: {{fruits}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', fruit: '香蕉', //单个选择列表 fruits: [] //多个选择列表 } }) </script>
就是动态的给value赋值而已
<div id="app"> <h2>您的爱好是: {{hobbies}}</h2> <!--此处动态给value赋值,即为值绑定--> <label v-for="item in originHobbies" :for="item"> <input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}} </label> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { hobbies: [], // 多选框, originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球'] } }) </script>
a、lazy修饰符:
b、number修饰符:
c、trim修饰符:
<div id="app"> <!--1.修饰符: lazy,懒加载,按下回车或失去焦点后才改变v-model绑定的变量的值--> <input type="text" v-model.lazy="message"> <h2>{{message}}</h2> <!--2.修饰符: number,默认情况下,v-model会自动将绑定的变量转换为string类型--> <input type="number" v-model.number="age"> <h2>{{age}}-{{"输入的类型是:"}}{{typeof age}}</h2> <!--3.修饰符: trim,去除前后空白行--> <input type="text" v-model.trim="name"> <h2>您输入的名字:{{name}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊', age: 0, name: '' } }) </script>
组件的使用分成三个步骤:
<div id="app"> <!--3.使用组件--> <my-cpn></my-cpn> <my-cpn></my-cpn> <my-cpn></my-cpn> <my-cpn></my-cpn> <div> <div> <my-cpn></my-cpn> </div> </div> </div> <my-cpn></my-cpn> <script src="../js/vue.js"></script> <script> // 1.创建组件构造器对象 const cpnC = Vue.extend({ template: ` <div> <h2>我是标题</h2> <p>我是内容, 哈哈哈哈</p> <p>我是内容, 呵呵呵呵</p> </div>` }) // 2.注册组件 Vue.component('my-cpn', cpnC) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script>
结果是显示五次组件中的内容
Vue.extend():
调用Vue.extend()创建的是一个组件构造器
通常在创建组件构造器时,传入template代表我们自定义组件的模板
该模板就是在使用到组件的地方,要显示的HTML代码
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础
Vue.component():
组件必须挂载在某个Vue实例下,否则它不会生效
我们来看下面使用了三次
而第三次其实并没有生效:
//注册组件(全局组件, 意味着可以在多个Vue的实例下面使用)
Vue.component('cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
//注册局部组件,仅允许当前Vue实例使用
components: {
// cpn:使用组件时的标签名
cpn: cpnC
}
})
<div id="app"> <cpn2></cpn2> <!--此处直接写子组件的标签是错误的,浏览器不会进行编译--> <!--<cpn1></cpn1>--> </div> <script src="../js/vue.js"></script> <script> // 1.创建第一个组件构造器(子组件) const cpnC1 = Vue.extend({ template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` }) // 2.创建第二个组件构造器(父组件) const cpnC2 = Vue.extend({ template: ` <div> <h2>我是标题2</h2> <p>我是内容, 呵呵呵呵</p> <cpn1></cpn1> </div> `, //将 cpnC1组件注册到cpnC2组件中 components: { cpn1: cpnC1 } }) // root组件 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn2: cpnC2 } }) </script>
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替
<script> // 1.全局组件注册的语法糖 Vue.component('cpn1', { template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` }) // 2.注册局部组件的语法糖 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { 'cpn2': { template: ` <div> <h2>我是标题2</h2> <p>我是内容, 呵呵呵</p> </div> ` } } }) </script>
Vue提供了两种方案来定义HTML模块内容:
<div id="app"> <cpn></cpn> <cpn></cpn> <cpn></cpn> </div> <!-- 1.script标签, 注意:类型必须是text/x-template <script type="text/x-template" id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,哈哈哈</p> </div> </script> --> <!--2.template标签--> <template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,呵呵呵</p> </div> </template> <script src="../js/vue.js"></script> <script> // 1.注册一个全局组件 Vue.component('cpn', { template: '#cpn' }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script>
组件不能直接访问Vue实例中的data,Vue组件应该有自己保存数据的地方
存放到组件对象的data属性,data属性需要是一个函数
因为对象是一个引用数据类型,如果data是一个对象的情况下会造成所有组件共用一个data。(址传递)
而当data是一个函数的情况下,每次函数执行完毕后都会返回一个新的对象,这样的话每个组件都会维护一份独立的对象(data)
官网的解释为:
data必须是一个函数
当我们定义这个 <button-counter>
组件时,你可能会发现它的 data
并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
若直接提供对象,则是值传递,不能保证每个组件都维护一份独立的对象
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例:
props的值有两种方式:
下面是数组
<div id="app"> <!--若不使用动态绑定,则只会赋值为字符串--> <!--<cpn cmovies="movies" cmessage="message"></cpn>--> <!--动态绑定--> <cpn :cmessage="message" :cmovies="movies"></cpn> </div> <template id="cpn"> <div> <ul> <li v-for="item in cmovies">{{item}}</li> </ul> <h2>{{cmessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> // 父传子: props const cpn = { template: '#cpn', props: ['cmovies', 'cmessage'], data() { /*此处必须有返回,不然会报错*/ return {} }, } //root组件 const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, //子组件 components: { /*对象的增强语法,key是上面定义的cpn变量名,value是cpn变量的值*/ cpn } }) </script>
对象:
<div id="app"> <!--cmovies可以不赋值,因为有默认值,而cmessage必须赋值,因为设置了required--> <cpn :cmessage="message" :cmovies="movies"></cpn> </div> <template id="cpn"> <div> <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: { // 1.只定义类型限制 // cmovies: Array, // cmessage: String, // 2.提供一些默认值, 以及必传值 cmessage: { //定义类型是String type: String, //默认值为 aaaaaaa default: 'aaaaaaaa', //是否为必须传值 required: true }, // 类型是对象或者数组时, 默认值必须是一个函数 cmovies: { type: Array, default() { return [] } } }, } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } }) </script>
验证也支持自定义的类型
若在组件调用时,用普通的驼峰命名法,vue不能识别
<div id="app"> <!--若用 :CInfo,则不会被渲染出来--> <!--驼峰命名加上 - 即可--> <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn> </div> <template id="cpn"> <div> <!--此处可以用驼峰标识--> <h2>{{cInfo}}</h2> <h2>{{childMyMessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> const cpn = { template: '#cpn', props: { //用全部小写也可以,驼峰命名较为麻烦 cInfo: { type: Object, default() { return {} } }, childMyMessage: { type: String, default: '' } } } const app = new Vue({ el: '#app', data: { info: { name: 'why', age: 18, height: 1.88 }, message: 'aaaaaa' }, components: { cpn } }) </script>
<!--父组件模板--> <div id="app"> <!--这个地方也不能用驼峰标识--> <!--此处若不写参数则默认传入 $event --> <cpn @item-click="cpnClick($event)"></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> // 1.子组件 const cpn = { template: '#cpn', data() { return { categories: [ {id: 'aaa', name: '热门推荐'}, {id: 'bbb', name: '手机数码'}, {id: 'ccc', name: '家用家电'}, {id: 'ddd', name: '电脑办公'}, ] } }, methods: { btnClick(item) { // 发射事件: 自定义事件 //子传父,item就是传递的数据 this.$emit('item-click', item) } } } // 2.父组件 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn }, methods: { cpnClick(item) { console.log('cpnClick', item); } } }) </script>
输入框中输入数据,将此值修改为父组件的num1,2和子组件的 number1,2,并且 num1*100 = num2
<body> <div id="app"> <cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change" /> </div> <template id="cpn"> <div> <h2>props:{{number1}}</h2> <h2>data:{{dnumber1}}</h2> <!-- <input type="text" v-model="dnumber1">--> <input type="text" :value="dnumber1" @input="num1Input"> <h2>props:{{number2}}</h2> <h2>data:{{dnumber2}}</h2> <!-- <input type="text" v-model="dnumber2">--> <input type="text" :value="dnumber2" @input="num2Input"> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { num1: 1, num2: 0 }, methods: { num1change(value){ this.num1 = parseFloat(value); }, num2change(value){ this.num2 = parseFloat(value); } }, components:{ cpn: { template: '#cpn', props: { /*应该用父组件进行修改,不应该直接修改 错误提示:避免直接改变属性,因为每当父组件重新渲染时, 该值都将被覆盖。相反,请使用根据此属性值的数据或计算属性修改 */ number1: Number, number2: Number }, data(){ return { dnumber1: this.number1, dnumber2: this.number2 } }, methods:{ num1Input(event){ this.dnumber1 = event.target.value; // 发射事件,改变父组件中的num1 this.$emit('num1change',this.dnumber1); // 同时修饰dnumber2的值 this.dnumber2 = this.dnumber1 *100; this.$emit('num2change',this.dnumber2); }, num2Input(event){ this.dnumber2 = event.target.value; this.$emit('num2change',this.dnumber2); this.dnumber1 = this.dnumber2 / 100; this.$emit('num1change',this.dnumber1); }, } } } }) </script> </body>
利用watch实现
<div id="app"> <cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"/> </div> <template id="cpn"> <div> <h2>props:{{number1}}</h2> <h2>data:{{dnumber1}}</h2> <input type="text" v-model="dnumber1"> <h2>props:{{number2}}</h2> <h2>data:{{dnumber2}}</h2> <input type="text" v-model="dnumber2"> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { num1: 1, num2: 0 }, methods: { num1change(value) { this.num1 = parseFloat(value) }, num2change(value) { this.num2 = parseFloat(value) } }, components: { cpn: { template: '#cpn', props: { number1: Number, number2: Number, }, data() { return { dnumber1: this.number1, dnumber2: this.number2 } }, //watch:当数据发生改变时启动,参数newValue表示新改变的数据 watch: { dnumber1(newValue) { this.dnumber2 = newValue * 100; this.$emit('num1change', newValue); }, //在上面 this.dnumber2 = newValue * 100; 改变了dnumber2 的值,所以会直接调用此处 dnumber2(newValue) { this.number1 = newValue / 100; this.$emit('num2change', newValue); } } } } }) </script>
使用$children
this.$children是一个数组类型,它包含所有子组件对象。可以通过一个遍历,取出所有子组件的message状态
children的缺陷:
关于$refs的使用:
<div id="app"> <cpn></cpn> <cpn></cpn> <my-cpn></my-cpn> <y-cpn></y-cpn> <cpn ref="aaa"></cpn> <button @click="btnClick">按钮</button> </div> <template id="cpn"> <div>我是子组件</div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, methods: { btnClick() { // 1.$children 用的较少 /* console.log(this.$children); for (let c of this.$children) { console.log(c.name); c.showMessage(); }*/ // 2.$refs => 对象类型, 默认是一个空的对象 通过ref='aaa'给子组件绑定特定的ID:aaa console.log(this.$refs.aaa.name); } }, components: { cpn: { template: '#cpn', data() { return { name: '我是子组件的name' } }, methods: { showMessage() { console.log('showMessage'); } } }, } }) </script>
$parent
利用$root,用法和与上面类似
组件的插槽也是为了让我们封装的组件更加具有扩展性
抽取共性,保留不同:将共性抽取到组件中,将不同暴露为插槽
<!-- 1.插槽的基本使用 <slot></slot> 2.插槽的默认值 <slot>button</slot> 3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素 --> <div id="app"> <!--使用默认值--> <cpn></cpn> <!--替换元素--> <cpn><span>哈哈哈</span></cpn> <!--替换元素--> <cpn><i>呵呵呵</i></cpn> <!--使用默认值--> <cpn></cpn> </div> <template id="cpn"> <div> <h2>我是组件</h2> <!--插槽--> <slot><button>按钮</button></slot> <hr/> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn' } } }) </script>
<div id="app"> <cpn><span slot="center">标题</span></cpn> <cpn><button slot="left">返回</button></cpn> </div> <template id="cpn"> <div> <slot name="left"><span>左边</span></slot> <slot name="center"><span>中间</span></slot> <slot name="right"><span>右边</span></slot> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn' } } }) </script>
学习作用域插槽我们需要先知道什么是编译作用域
根据官方的解释:在该模板的所有东西都会在此模板的作用域内编译
<div id="app"> <cpn></cpn> <cpn> <!--目的是获取子组件中的pLanguages--> <!--从子组件传过来的data值为 pLanguages 的数据将会被slot-scope接收, 需要注意的是,slot-scope接收的是一个对象,这里命名为slot, 也就是说你传过来的data将会作为slot的一个属性,所以需要slot.data才能调用到data--> <template slot-scope="slot"> <!--<span v-for="item in slot.data"> - {{item}}</span>--> <!--用上行代码,前面会多出一个 - ,解决此问题可以利用join函数,将字符串用 - 分隔开--> <span>{{slot.data.join(' - ')}}</span> </template> </cpn> <cpn> <template slot-scope="slot"> <span>{{slot.data.join(' * ')}}</span> </template> </cpn> </div> <template id="cpn"> <div> <!--子组件中定义data数据,并且绑定到slot上--> <slot :data="pLanguages"> <ul> <li v-for="item in pLanguages">{{item}}</li> </ul> </slot> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn', data() { return { pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift'], } } } } }) </script>
在早期的网页开发中,为了应对代码量的剧增,通常会将代码组织到多个js文件中进行维护。
但是有很多问题,比如全局变量同名问题:
另外,这种代码的编写方式对js文件的依赖顺序几乎是强制性的
但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较费事的事情。
而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生
我们可以使用匿名函数来解决此类问题,但很多代码变得不可复用
我们可以使用模块化雏形来解决此问题,只是简单的封装而已
CommonJS的导出和导入,但需要在node环境中才能使用
//aaa.js function sum(num1, num2) { return num1 + num2 } var flag = true if (flag) { console.log(sum(10, 20)); } //将flag和sum函数导出 module.exports = { flag: flag, sum: sum } //在mmm.js中导入 var aaa = require('./aaa.js') var flag = aaa.flag; var sum = aaa.sum; //上面三行等用于 var {flag, sum} = require('./aaa.js')
使用export(导出)和import(导入),引用的时候需要类型为 model
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>
两种导出方式
var age = 18
var flag = true
function sum(num1, num2) {
return num1 + num2
}
// 1.导出方式一:
export {
flag, sum
}
// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88
导入:
// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";
if (flag) {
console.log('小明是天才, 哈哈哈');
console.log(sum(20, 30));
}
// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";
console.log(num1);
console.log(height);
导出类或函数
export function mul(num1, num2) {
return num1 * num2
}
export class Person {
run() {
console.log('在奔跑');
}
}
导入
// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";
console.log(mul(30, 50));
const p = new Person();
p.run()
导出 default 。export default在同一个模块中,不允许同时存在多个
//const address = '北京市'
//export default address
export default function (argument) {
console.log(argument);
}
导入:
// 4.导入 export default中的内容,且导入的时候不用加{}
import addr from "./aaa.js";
addr('你好啊');
如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦,可以通过通配符 * 来导入模块中的所有 export 变量
通常为了方便使用,我们会用 as xxx 给 * 取一个别名xxx
// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.height);
从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具
dist文件夹:用于存放之后打包的文件(输入命令后自动生成 webpack src/main.js dist/bundle.js 需要在当前目录输入此命令,即在01-webpack的起步目录)
src文件夹:用于存放我们写的源文件
ppackage.json:通过 npm init 生成的,npm包管理的文件
info.js
export const name = 'why';
export const age = 18;
export const height = 1.88;
mathUtis.js
function add(num1, num2) {
return num1 + num2
}
function mul(num1, num2) {
return num1 * num2
}
//导出 add mul
module.exports = {
add,
mul
}
main.js
// 1.使用commonjs的模块化规范
const {add, mul} = require('./mathUtils.js')
console.log(add(20, 30));
console.log(mul(20, 30));
// 2.使用ES6的模块化的规范
import {name, age, height} from "./info";
console.log(name);
console.log(age);
console.log(height);
如果直接在index.html引入这两个js文件,浏览器并不识别其中的模块化代码。
另外,在真实项目中当有许多这样的 js 文件时,我们一个个引用非常麻烦,并且后期非常不方便对它们进行管理
使用webpack打包工具生成的 bist目录中的 bundle.js文件,只打包 main.js 即可,webpack会自动将 main.js 中的引用也打包
D:\01_Java\00_Java_project\12_vue\14-webpack使用\01-webpack的起步>webpack src/main.js dist/bundle.js
index.html:在 index.html 中引用这个bundle.js 文件即可
<body>
<script src="./dist/bundle.js"></script>
</body>
首先应该使用 npm init 初始化项目 会生成package.json 文件
然后使用npm install 将 packge.json 所依赖的东西安装
如果每次使用 webpack 的命令都需要写上入口和出口作为参数,就非常麻烦,可以利用 webpack.config.js 文件
//导入node自带的path模块,用以获取文件前缀目录
const path = require('path')
module.exports = {
//入口
entry: './src/main.js',
//出口
output: {
//resolve函数表示拼接,__dirname表示当前文件的所在的路径,即将dist拼接到当前路径中
path: path.resolve(__dirname, 'dist'), //此处要写绝对路径
filename: 'bundle.js' //出口文件名
},
}
因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题
所以一个项目安装一个局部的 webpack 是十分必要的
第一步:局部安装开发版本的webpack
npm install webpack@3.6.0 --save-dev
package.json中的 scripts 的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
和直接终端输入不同,输入npm run xxx 是优先在局部安装的 webpack 中使用
此处添加 build 将 webpack 替换为 npm run build 命令
在我们之前的实例中,我们主要是用webpack来处理我们写的js代码,并且webpack会自动处理js之间相关的依赖。
但是,在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
对于webpack本身的能力来说,对于这些转化是不支持的,需要给webpack扩展对应的loader
nloader使用过程:
大部分loader我们都可以在webpack的官网中找到,并且学习对应的用法
normal.css
body {
background-color: red;
}
我们需要在 main.js 中引用它才可以找到它
// 3.依赖css文件
require('./css/normal.css')
然后我们进行打包发现错误,提示 normal.css 必须有对应的loader
在webpack的官方(https://webpack.docschina.org/loaders/)中,我们可以找到如下关于样式的loader使用方法:
按照官方配置 webpack.config.js 文件,首先安装 css-loader
和 style-loader
:
css-loader 和 style-loader 缺一不可,一个负责将 css 加载,一个负责将样式添加到 DOM 中
//npm install --save-dev css-loader
//需要注意的是,本地安装的是webpack3.6.0,默认的css-loader版本过高,需要使用 3.0版本的,否则会出现无法解析的错误
npm install css-loader@3.0.0 --save-dev
npm install --save-dev style-loader
webpack.config.js
const path = require('path') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: [ { //正则表达式 test: /\.css$/, // css-loader只负责将css文件进行加载,若只有css-loader 则不会加载出样式 // style-loader负责将样式添加到DOM中 // 使用多个loader时, 是从右向左的顺序读取的,如果顺序错误会直接报错 use: [ 'style-loader', 'css-loader' ] } ] } }
package.json
"devDependencies": {
"css-loader": "^3.0.0",
"style-loader": "^2.0.0",
"webpack": "^3.6.0"
}
首先创建less文件
@fontSize: 50px;
@fontColor: orange;
body {
font-size: @fontSize;
color: @fontColor;
}
然后在main中引用
require('./css/special.less')
less会被loader解析为css,则需要 less-loader、css-loader、style-loader,另外还需要安装 less(因为 webpack 会使用 less 对 less 文件编译)
package.json
"devDependencies": {
"css-loader": "^2.0.2",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^3.6.0"
}
webpack.config.js
module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.less$/, use: [{ loader: "style-loader", // 从JS字符串创建样式节点 }, { loader: "css-loader" // 将 CSS 编译为 less }, { loader: "less-loader", // 将 less 编译为 CSS }] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192 } } ] } ] }
normal.css中,将背景换为图片 test.jpg小于13kb,timg.jpg大于13kb
body{
/*background-color: red;*/
background: url("../img/test.jpg");
}
package.json
"devDependencies": {
"css-loader": "^2.0.2",
"file-loader": "^3.0.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^3.6.0"
}
图片处理,url-loader来处理,依然先安装 url-loader
然后修改webpack.config.js 配置文件
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
// 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
limit: 13312 //13kb
}
}
]
}
然后发现可以显示出来:背景图片为:text.jpg
如果换为timg.jpg(大于13kb),发现报错
提示需要 file-loader,下面我们安装
npm install --save-dev file-loader
然后修改webpack.config.js
{
test: /\.(png|jpg|gif|jepg)$/,
use: [
{
loader: 'file-loader',
options: {
//图片文件处理-修改文件名称
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
发现还是报错:
此处file-loader需要降级到3.0.1,其实正常开发 url 和 file loader一般只留一个,否则很容易出现图片无法正常加载的问题。
卸载原本安装的 file-loader
npm uninstall --save-dev file-loader
然后安装 3.0.1 的版本
npm install --save-dev file-loader@3.0.1
关于图片文件名称修改 和 图片路径添加 dist/
这样就可以显示出来了
webpack打包的 js 文件中,ES6的语法并没有转换为ES5的语法,这意味着可能一些对ES6还不支持的浏览器没有办法完全运行代码
要讲ES6的语法转换为ES5的,需要babel
在webpack中,直接使用babel对应的loader即可
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
webpack.config.js
{
test: /\.js$/,
// exclude: 排除
// include: 包含
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
},
然后重新打包,发现其中的内容都变成了ES5的语法
在项目中使用Vuejs,那么必然需要对其有依赖,所以需要先进行安装
因为后续在实际项目中也会使用vue,所以并不是开发时依赖
npm install vue --save
导入vue依赖包,然后打包之后运行,浏览器中报错,错误提示时我们默认使用的是runtime-only版本的Vue
runtime-only 其实只能识别 render 函数,不能识别 template,.vue文件中的也是被 vue-template-compiler 翻译成了render函数,所以只能在.vue里写 template
我们修改webpack.config.js,添加如下内容,在model之外添加
resolve: {
//扩展名
extensions: ['.js', '.css', '.vue'],
//别名,使用vue.esm.js版本的Vue,基于构建工具使用的完整版
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
正常运行之后,又出现了另一个问题
定义template属性:
在前面的Vue实例中,我们定义了 el 属性,用于和 index.html 中的 #app 进行绑定,让Vue实例之后可以管理它其中的内容
这里,我们可以将div元素中的{{message}}内容删掉,只保留一个基本的 id 为div的元素
<div id="app">
</div>
但是如果我依然希望在其中显示{{message}}的内容,应该怎么处理呢?
Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应 el 的模板
就像下面动图中展示的那样:template模板的内容会替换掉挂载的对应el的模板
这样做之后我们就不需要在以后的开发中再次操作index.html,只需要在template中写入对应的标签即可
对于上面的代码,书写 template 模块非常麻烦怎么办?
使用组件化的思想,我们可以将template模板中的内容进行抽离。
main.js
//使用vue进行开发 import Vue from 'vue' // 1.抽离出一个组件 let App = { template: ` <div> <h2>{{message}}</h2> <button @click="btnClick">按钮</button> </div> `, data() { return { message: 'hello world' } }, methods: { btnClick() { console.log('---'); } } } let app = new Vue({ el: '#app', template: '<App></App>', // 3.使用 components:{ // 2.在Vue根实例中进行注册 App } })
这样 main.js 看起来不简洁,我们可以将代码抽取到一个js文件中,并且导出,在main.js中导入
创建一个app.js文件
export default { template: ` <div> <h2>{{ message }}</h2> <button @click="btnClick">按钮</button> </div> `, data() { return { message: 'hello world' } }, methods: { btnClick() { console.log('---'); } } }
然后在 main.js 文件中进行引入
//使用vue进行开发
import Vue from 'vue'
import App from './vue/app'
let app = new Vue({
el: '#app',
template: '<App></App>',
components:{
App
}
})
但是上边的代码中,一个组件以一个 js 对象的形式进行组织和使用的时候是非常不方便的
现在,我们以一种全新的方式来组织一个vue的组件:创建一个 App.vue
文件
安装 vue-loader 和 vue-template-compiler
npm install vue-loader vue-template-compiler --save-dev
webpack.config.js
{
test: /\.vue$/,
use: ['vue-loader']
}
完整webpack.config.js
const path = require('path') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', publicPath: 'dist/' }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.vue$/, use: ['vue-loader'] } ] }, resolve: { //扩展名 extensions: ['.js', '.css', '.vue'], //别名,使用vue.esm.js版本的Vue,基于构建工具使用的完整版 alias: { 'vue$': 'vue/dist/vue.esm.js' } } }
package.json
{ "name": "vuep", "version": "1.0.0", "description": "", "main": "webpack.config.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" }, "author": "", "license": "ISC", "dependencies": { "vue": "^2.6.12" }, "devDependencies": { "css-loader": "^2.0.2", "style-loader": "^2.0.0", "vue-loader": "^13.0.0", //若使用的vue-loader是大于15.0.0的,需要额外安装插件使用 "vue-template-compiler": "^2.6.12", "webpack": "^3.6.0" } }
需要注意的是,若使用的vue-loader是大于15.0.0的,需要额外安装插件使用
plugin是插件的意思,通常是对某个现有的框架进行扩展
webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等
loader和plugin区别
plugin的使用过程:
BannerPlugin是在 dist/bundle.js 文件的头部添加横幅注释的插件,属于webpack自带的插件
webpack.config.js
打包程序后,查看bundle.js
目前,我们的index.html文件是存放在项目的根目录下的。
我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。
所以,我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用 HtmlWebpackPlugin 插件
HtmlWebpackPlugin插件可以为我们做这些事情:
安装HtmlWebpackPlugin插件,默认生成的版本过高,需要加上指定版本
npm install html-webpack-plugin --save-dev@3.2.0
安装后打包,这是 不使用模板 自动生成的 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script type="text/javascript" src="dist/bundle.js"></script>
</body>
</html>
可以看出,自动插入了 script 标签
但是没有使用
这一标签我们可以给他加一个模板属性:template
new htmlWebpackPlugin({
template: 'index.html'
})
这样我们原本在项目中写的index.html就会作为模板生成 dist/index.html
项目中作为模板的 index.html,不用导入script标签了,HtmlWebpackPlugin 插件会自动为 dist/index.html 导入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
重新打包,生成的dist目录下的index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="dist/bundle.js"></script></body>
</html>
观察项目结构,发现 dist 目录下的 index.html 中,script标签中的 src 会出现路径错误,正确路径应该直接为 bundle.js
所以我们需要删除 webpack.config.js 中 output 属性的 publicPath
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
//publicPath: 'dist/'
},
在项目发布之前,我们通常需要对js等文件进行文件压缩
我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
修改webpack.config.js文件,使用插件:
查看打包后的 bundle.js 发现已经打包完成,删除了空行,空格,注释等,变量名也变得更加简洁
首先安装模块
npm install --save-dev webpack-dev-server@2.9.1
配置webpack.config.js
devServer: {
contentBase: './dist',
inline: true
}
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
可以再加上一个 script
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
//"dev": "webpack-dev-server"
//加上 --open 命令行输入npm run dev 后会自动在浏览器打开网址,不需要手动点击
"dev": "webpack-dev-server --open"
},
安装配置文件合并工具
npm install webpack-merge@4.1.5 --save-dev
base.config.js
//公共部分的配置 const path = require('path') //html打包插件 const htmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, '../dist'), filename: 'bundle.js', //publicPath: 'dist/' }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.vue$/, use: ['vue-loader'] } ] }, resolve: { //扩展名 extensions: ['.js', '.css', '.vue'], //别名,使用vue.esm.js版本的Vue,基于构建工具使用的完整版 alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ new htmlWebpackPlugin({ template: 'index.html' }) ], }
dev.config.js
//开发时依赖
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
//合并,将baseConfig与后面代码合并
module.exports = webpackMerge(baseConfig,{
devServer: {
contentBase: './dist',
//自动刷新
inline: true
}
})
因为删除了 webpack.config.js 所以需要手动修改配置文件的位置
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
这次打包到了build文件夹中的dist文件夹,与之前不同,因为配置里面默认在当前文件夹下创建build文件夹
即:
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
//publicPath: 'dist/'
},
修改配置文件
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'bundle.js',
//publicPath: 'dist/'
},
然后就会打包到项目第一级目录下的build文件夹
Vue CLI 使用前提需要安装 8.9以上版本的Node和Webpack
npm install -g @vue/cli
默认安装的是Vue CLI3以上的版本,如果想按照Vue CLI2 的方式初始化项目是不可以的
可以安装Vue CLI3版本后,再拉取2.x的模板,全局安装一个桥接工具
Vue CLI2初始化项目
vue init webpack 项目名称
Vue CLI3初始化项目
vue create 项目名称
不同项目使用两种版本的 main.js
其实 h 就是 createElement 的简写
可以看出runtime-compiler模式里的Vue实例的模板,和注册的组件,都被一个render函数替换掉了
template在vue内部是怎么样渲染成页面的?
由上图可知,template会被解析成ast,再被编译成一个render函数,这个render函数会构造一个virtual dom(虚拟dom),最后virtual dom会转换为真实dom,进行页面展示。
runtime-compiler的步骤:
template -> ast -> render -> virtual dom -> 真实dom
runtime-only的步骤
render -> virtual dom -> 真实dom
使用runtime-only虽然不能够自己编译template模板,但是因为之前安装的插件vue - template - compiler ,系统内部会自动编译template模板
这个路径别名在import导入的时候可以直接用,但是在其他地方前面需要加上~(如写 src 的时候)
vue-cli 3 与 2 版本有很大区别
创建时在命令行输入:
vue create 项目名称
设置保存的地址: C:\Users\季秋.vuerc
在VueCLI3中,配置文件不需要那么麻烦,启动配置服务器 :命令行输入 vue ui
在 15-vuecli3-text\node_modules@vue\cli-service\lib\Service.js 中
如果需要更改配置,手动创建一个 vue.config.js 文件,然后将手动修改的配置导出,Service.js会自动进行合并
路由是一个网络工程里面的术语
路由就是通过互联的网络把信息从源地址传输到目的地址的活动
后端路由的缺点:
HTML
代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情.后端路由:
前后端分离阶段:
前端路由阶段也就是单页面富应用阶段(SPA)
SPA
最主要的特点就是在前后端分离的基础上加了一层前端路由前端路由的核心:改变URL,但是页面不进行整体的刷新
URL的hash也就是锚点, 本质上是改变window.location的href属性
我们可以通过直接赋值location.hash来改变href, 虽然网址发生了变化,但是页面不发生刷新
可以看出,更改了URL,但是并没有刷新页面
pushState
history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面
history.pushState()
这种方法类似于压栈,若用history.back会弹出最晚进栈的那一个URL
replaceState 替换网址,与pushState不同的是,不可返回
history.go()
history.back
history.forward()
vue-router是基于路由和组件的
创建vue-router实例
index.js
import Vue from 'vue' import VueRouter from 'vue-router' //1.通过Vue.use(插件),安装插件 Vue.use(VueRouter) //2.创建VueRouter对象 const routes = [{ path:xxx components:xxx }] const router = new VueRouter({ //配置路由和组件之间的应用关系 routes, }) //3.将router对象导出,传入到Vue实例 export default router
创建路由组件
使用路由
配置多一个映射即可,利用重定向
//2.创建VueRouter对象 const routes = [ { path: '/', //重定向,设置路由的默认路径 redirect: '/home' }, { path: '/home', component: Home }, { path: '/about', component: About } ]
还可以利用此性质,使其点击哪个地方哪个变红
<style>
.Active{
color: #f00;
}
</style>
在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望如下的路径:
/user/aaaa或/user/bbbb,除了有前面的/user之外,后面还跟上了用户的ID
这种path和Component的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)
User.Vue
<template> <div> <p>用户</p> <h2>用户用户用户</h2> <h2>{{userId}}</h2> </div> </template> <script> export default { name: "User", //计算属性 computed: { userId() { //利用当前路由的对象中的params方法接收参数 return this.$route.params.id } } } </script> <style scoped> </style>
index.js中添加
{
//动态路由,后面跟上匹配的ID
path: '/user/:id',
component: User
}
App.vue
<template> <div id="app"> <router-link to="/home">首页</router-link> <router-link to="/about">关于</router-link> <!--此处可以利用v-bind: 简写为: 动态绑定--> <router-link :to="'/user/'+id">用户</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App', data() { return { id: 'lisi' } } } </script>
打包构建应用时,Javascript 包会变得非常大,影响页面加载。
如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块,只有在这个路由被访问到的时候, 才加载对应的组件
打开网页
路由嵌套的默认路径
准备工作
传递参数方式一:
传递参数二:利用JavaScript
参数获取通过$route.query/param
我们在项目中使用了vue-router,这时,当前项目的每一个vue组件,都会自动获取到一个$route数据,这个数据中存放的是匹配当前路由的信息对象,可以通过它,得到当前的动态路由参数 Params 或查询参数 Query
$route对象表示的是当前路由信息
$router对象是全局路由的实例,是router构造方法的实例,可以使用push,go等方法
我们来考虑一个需求: 在一个SPA应用中, 如何改变网页的标题呢?
网页标题是通过
但是我们可以通过JavaScript来修改
那么在Vue项目中, 在哪里修改? 什么时候修改比较合适呢?
普通的修改方式:
我们比较容易想到的修改标题的位置是每一个路由对应的组件.vue文件中.
通过mounted生命周期钩子, 执行对应的代码进行修改即可,例如在创建回调函数时更改标题
但是当页面比较多时, 这种方式不容易维护(因为需要在多个页面执行类似的代码).
有没有更好的办法呢? 使用导航守卫即可.
vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
我们可以使用beforeEach来完成标题的修改,在生命周期执行函数之前执行的函数
首先, 我们可以在钩子当中定义一些标题, 可以利用meta来定义
其次, 利用导航守卫,修改我们的标题
导航钩子的三个参数解析:
to: 即将要进入的目标的路由对象.
from: 当前导航即将要离开的路由对象.
next: 调用该方法后, 才能进入下一个钩子,beforeEach必须调用,afterEach中函数内部自身调用,不用调用,所以后置钩子只有两个参数,to和from
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性:
点击首页的非默认嵌套路由,再点击其他页面,再点击首页的时候,页面不变,即:
// 这两个函数, 只有该组件被保持了状态使用了keep-alive时, 才是有效的,与生命周期函数写到一层 //当前路由活跃,调用此函数 activated() { this.$router.push(this.path); console.log('activated'); }, //当前路由不活跃,调用此函数 /* deactivated() { console.log('deactivated'); },*/ //记录离开前的path beforeRouteLeave (to, from, next) { console.log(this.$route.path); this.path = this.$route.path; next() }
被keep-alive包含的组件会被缓存
// 只缓存组件name为a或者b的组件,这个地方逗号后面不能随便加空格 <keep-alive include="a,b"> <router-view/> </keep-alive> // 组件name为c的组件不缓存(可以保留它的状态或避免重新渲染) <keep-alive exclude="c"> <router-view/> </keep-alive> // 如果同时使用include,exclude,那么exclude优先于include, 下面的例子只缓存a组件 <keep-alive include="a,b" exclude="b"> <router-view/> </keep-alive> // 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件 <keep-alive exclude="c" max="5"> <router-view/> </keep-alive>
1.如果在下方有一个单独的TabBar组件,你如何封装
2.TabBar中显示的内容由外界决定
3.自定义TabBarItem,可以传入 图片和文字
4.传入 高亮图片
5.TabBarItem绑定路由数据
6.点击item跳转到对应路由,并且动态决定isActive
7.动态计算active样式
完成图片效果:
项目基本结构
APP.vue
<template> <div id="app"> <!--插槽调用--> <tab-bar> <tab-bar-item> <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item> <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item> <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item> <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </div> </template> <script> //先导入 import TabBar from './components/tabbar/TabBar' import TabBarItem from './components/tabbar/TabBarItem' export default { name: 'App', //再注册 components: { TabBar, TabBarItem } } </script> <style> /* base.css中只写了bady这一个样式 body { padding: 0; margin: 0; } */ @import "./assets/css/base.css"; </style>
功能截图
实现下一步,利用利用跳转页面,并且点击下方视图导航图片变色
功能结构截图
TabBar.vue不改
<template> <div id="tab-bar"> <slot></slot> </div> </template> <script> export default { name: "TabBar" } </script> <style scoped> #tab-bar { display: flex; background-color: #f6f6f6; position: fixed; left: 0; right: 0; bottom: 0; box-shadow: 0 -1px 1px rgba(100,100,100,.2); } </style>
TabBarItem更改
<template> <!-- 点击之后调用itemClick函数,使链接跳转--> <div class="tab-bar-item" @click="itemClick"> <div v-if="!isActive"><slot name="item-icon"></slot></div> <div v-else><slot name="item-icon-active"></slot></div> <div :style="activeStyle"><slot name="item-text"></slot></div> </div> </template> <script> export default { name: "TabBarItem", props: { path: String, activeColor: { type: String, default: 'red' } }, computed: { isActive() { //当前活跃路由的地址是否是this.path(由MainTabBar中的tab-bar-item中的path传入),若是,则返回true return this.$route.path.indexOf(this.path) !== -1 }, activeStyle() { //当前活跃路由下方字体(首页、分类...)变色 return this.isActive ? {color: this.activeColor} : {} } }, methods: { itemClick() { //跳转/替换 地址 this.$router.replace(this.path) } } } </script> <style scoped> .tab-bar-item { flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 3px; vertical-align: middle; margin-bottom: 2px; } </style>
MainTabBar填写插槽
<template> <tab-bar> <!--传入path和activeColor--> <tab-bar-item path="/home" activeColor="pink"> <img slot="item-icon" src="../../assets/img/tabbar/home.svg" alt=""> <img slot="item-icon-active" src="../../assets/img/tabbar/home_active.svg" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item path="/category" activeColor="pink"> <img slot="item-icon" src="../../assets/img/tabbar/category.svg" alt=""> <img slot="item-icon-active" src="../../assets/img/tabbar/category_active.svg" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item path="/cart" activeColor="pink"> <img slot="item-icon" src="../../assets/img/tabbar/shopcart.svg" alt=""> <img slot="item-icon-active" src="../../assets/img/tabbar/shopcart_active.svg" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item path="/profile" activeColor="deepPink"> <img slot="item-icon" src="../../assets/img/tabbar/profile.svg" alt=""> <img slot="item-icon-active" src="../../assets/img/tabbar/profile_active.svg" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </template> <script> import TabBar from 'components/tabbar/TabBar' import TabBarItem from 'components/tabbar/TabBarItem' export default { name: "MainTabBar", components: { TabBar, TabBarItem } } </script> <style scoped> </style>
<script> // 使用setTimeout // setTimeout(() => { // console.log('Hello World'); // }, 1000) //改为使用Promise // 什么情况下会用到Promise? // 一般情况下是有异步操作时,使用Promise对这个异步操作进行封装 // new -> 构造函数(1.保存了一些状态信息 2.执行传入的函数) // 在执行传入的回调函数时, 会传入两个参数, resolve, reject.本身又是函数 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); console.log(data); }).catch((err) => { console.log(err); }) </script>
另一种写法,可以省略catch,多个异步操作时看起来更加清晰
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('Hello Vuejs')
reject('error message')
}, 1000)
//then(函数1,函数2) 其中函数1表示成功后调用,函数2发生错误时调用
}).then(data => {
console.log(data);
}, err => {
console.log(err);
})
</script>
<script> // 参数 -> 函数(resolve, reject) // resolve, reject本身它们又是函数 // 链式编程 new Promise((resolve, reject) => { // 第一次网络请求的代码 setTimeout(() => { resolve("hello world") }, 1000) }).then((data) => { // 第一次拿到结果的处理代码 console.log('data'); //hello world return new Promise((resolve, reject) => { // 第二次网络请求的代码 setTimeout(() => { resolve(data + '111') }, 1000) }) }).then((data) => { // 第二次处理的代码 console.log('data'); //hello world111 return new Promise((resolve, reject) => { // 第三次网络请求的代码 setTimeout(() => { resolve(data + '222') }) }) }).then((data) => { // 第三处理的代码 console.log(data); //hello world111222 return new Promise((resolve, reject) => { // 第四次网络请求的代码 setTimeout(() => { reject(data + 'error') }) }) }).then((data) => { // 第四处理的代码 console.log(data); //不执行 },(err)=> { console.log(err); //hello world111222error }) </script>
链式调用简写省略
再简写:
<script> Promise.all([ new Promise((resolve, reject) => { setTimeout(() => { resolve({name: 'why', age: 18}) }, 2000) }), //results[0] new Promise((resolve, reject) => { setTimeout(() => { resolve({name: 'kobe', age: 19}) }, 1000) }) //results[1] ]).then(results => { console.log(results[0]); console.log(results); }) </script>
vuex,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面
然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用
Vuex就是为了提供这样一个在多个组件间共享状态的插件,且实现了响应式
Vuex状态管理图例
vuex 中最关键的是store对象,这是vuex的核心。可以说,vuex这个插件其实就是一个store对象,每个vue应用仅且仅有一个store对象。
store是Vuex.Store这个构造函数new出来的实例。在构造函数中可以传一个对象参数。这个参数中可以包含5个对象:
1.state – 存放状态
2.getters – state的计算属性
3.mutations – 更改状态的逻辑,同步操作
4.actions – 提交mutation,异步操作
5.mudules – 将store模块化
关于store,需要先记住两点:
一个完整的store的结构是这样的
const store = new Vuex.Store({ state: { // 存放状态 }, getters: { // state的计算属性 }, mutations: { // 更改state中状态的逻辑,同步操作 }, actions: { // 提交mutation,异步操作 }, // 如果将store分成一个个的模块的话,则需要用到modules。 //然后在每一个module中写state, getters, mutations, actions等。 modules: { a: moduleA, b: moduleB, // ... } });
state上存放的,说的简单一些就是变量,也就是所谓的状态。没有使用 state 的时候,我们都是直接在 data 中进行初始化的,但是有了 state 之后,我们就把 data 上的数据转移到 state 上去了。另外有些状态是组件私有的状态,称为组件的局部状态,我们不需要把这部分状态放在store中去。
const store = new Vuex.Store({
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}
]
}
在组件中获取store中的状态
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数。此时可以用到getters,getters可以看作是store的计算属性,其参数为state。
getters默认是不能传递参数的,如果希望传递参数, 那么只能让getters本身返回另一个函数
获取getters里面的状态
更改Vuex中的state的唯一方法是,提交mutation,即store.commit(‘increment’)
下面的方式可以使state为响应式的
我们来考虑下面的问题:
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
如何避免上述的问题呢?
在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型.
我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
具体怎么做呢?
我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.
首先将插件切换到Vuex
根据规则,我们不会在mutation中进行异步操作,但是某些时候仍然需要在Vuex中进行一些异步操作,这时候可以用Action
根据图中规则,首先 组件 dispatch Actions 然后 Actions Commit Mutations,再返回给状态
我们强调, 不要在Mutation中进行异步操作。
但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的。这个时候怎么处理呢?
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的。
context是什么?
我们定义了actions,,然后又在actions中去进行commit,这样的代码是否多此一举呢?事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了。
Action的基本使用代码如下: index.js
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++ } }, //首先dispatch actions,然后actions commit mutations actions: { increment(context) { context.commit('increment') } } })
App.vue 在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
methods:{
increment: function (){
this.$store.dispatch('increment')
},
}
支持传递payload
methods: {
increment() {
this.$store.dispatch('increment',{cCount: 5})
}
}
mutations: {
increment(state,payload) {
state.count += payload.Count
}
},
actions: {
increment(context,payload) {
setTimeout(() => {
context.commit('increment',payload)
},5000)
}
}
Action
返回的Promise
学习ES6语法的时候,Promiss
经常用于异步操作
在Action
中, 我们可以将异步操作
放在一个Promise
中, 并且在成功或者失败后, 调用对应的resolve
或reject
来看下面的代码:
actions: {
increment(context) {
return new Promise((resolve) => {
setTimeout(() => {
context.commit('increment')
resolve()
},1000)
})
}
}
methods: {
increment() {
this.$store.dispatch('increment').then(res => {
console.log('完成了更新操作')
})
}
}
认识Vuex的Module
我们按照什么样的方式来组织模块呢?
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 的状态 store.state // -> 全局state的状态
Module的局部状态
moduleA
中添加state
、mutations
、getters
mutation
和getters
接收的第一个参数是局部状态
对象const moduleA = { state: { count: 0 }, mutations: { increment(state) { state.count++ } }, getters: { doubleCount(state) { return state.count * 2 } } } const moduleB = { } const store = new Vuex.Store({ state: { gCount: 111 }, modules: { a: moduleA, b: moduleB } }) export default store;
<script> export default { name: 'App', components: { }, computed: { count() { return this.$store.getters.doubleCount } }, methods: { increment() { this.$store.commit('increment') } } } </script>
注意:
doubleCount
和increment
都是定义在对象内部
的this.$store
来直接调用的Module的Actions写法
actions的写法呢? 接收一个context参数对象
局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state,commit,rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
如果getters
中也需要使用全局的状态, 可以接受更多的参数
const moduleA = {
// ...
getters: {
sumWithRootCount (state,getters,rootState) {
return state.count + rootState.count
}
}
}
index.js中的代码,安装插件,创建对象,导出store
import Vue from 'vue' import Vuex from 'vuex' import mutations from './mutations' import actions from './actions' import getters from './getters' import moduleA from './modules/moduleA' // 1.安装插件 Vue.use(Vuex) // 2.创建对象 const 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} ], info: { name: 'kobe', age: 40, height: 1.98 } } const store = new Vuex.Store({ state, mutations, actions, getters, modules: { a: moduleA } }) // 3.导出store独享 export default store // 对象的结构 const obj = { name: 'why', age: 18, height: 1.88, address: '洛杉矶' } const {name, height, age} = obj;
Vue中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?
axios
为什么选择axios
XMLHttpRequests
请求node.js
中发送 http请求Promise API
拦截请求和响应
转换请求和响应数据
axios
请求方式
支持多种请求方式:
get,获取数据的方式
post,提交数据的方式(表单提交以及文件上传)
put,更新数据的方式(提交所有的数据)
patch,提交数据的方式 (提交修改的数据)
delete,删除数据的方式
发送get
请求演示,默认的就是get请求方式
//1.axios的基本使用 //不带参数 axios({ url: 'http://123.207.32.32:8000/home/multidata', }).then(res => { console.log(res); }) //携带参数 axios({ url: 'http://123.207.32.32:8000/home/data', // 专门针对get请求的参数拼接,若用post则用data params: { type: 'pop', page: 1 } }).then(res => { console.log(res); })
发送并发请求
演示
axios.all
, 可以放入多个请求
的数组
.axios.all([])
返回的结果是一个数组,使用 axios.spread
可将数组 [res1,res2]
展开为 res1, res2
//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);
console.log(results[0]);
console.log(results[1]);
})
在上面的示例中, 我们的BaseURL
是固定
的
固定
的.axios
的全局配置
//3.使用全局的axios和对应的配置在进行网络请求 axios.defaults.baseURL = 'http://123.207.32.32:8000' axios.defaults.timeout = 5000 axios.all([axios({ url: '/home/multidata' }), axios({ url: '/home/data', params: { type: 'sell', page: 5 } })]).then(axios.spread((res1, res2) => { console.log(res1); console.log(res2); }))
常见的配置选项:
为什么要创建axios的实例呢?
//4.创建对应的axios的实例 const instance1 = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) instance1({ url: '/home/multidata' }).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.33.33:8000', timeout: 10000, // headers: {} })
还可以直接 return instance,因为instance的返回类型就为 Promise
import axios from 'axios' export function request(config) { // 1.创建axios的实例 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) // 2.axios的拦截器 // 2.1.请求拦截的作用 instance.interceptors.request.use(config => { // console.log(config); // 1.比如config中的一些信息不符合服务器的要求 // 2.比如每次发送网络请求时, 都希望在界面中显示一个请求的图标 // 3.某些网络请求(比如登录(token)), 必须携带一些特殊的信息 //拦截到并修改为想要的数据之后,需要将 config 返回出去 return config }, err => { console.log(err); }) // 2.2.响应拦截 //服务器已经响应过了,传入结果result: res instance.interceptors.response.use(res => { // console.log(res); return res.data }, err => { console.log(err); }) // 3.发送真正的网络请求 return instance(config) }
default {
name: ‘App’,
components: {
},
computed: {
count() {
return this.KaTeX parse error: Expected 'EOF', got '}' at position 30: …doubleCount }̲ }, methods…store.commit(‘increment’)
}
}
}
注意: - 虽然, 我们的`doubleCount`和`increment`都是定义在`对象内部`的 - 但是在调用的时候, 依然是通过`this.$store`来直接调用的 ==Module的Actions写法== actions的写法呢? 接收一个context参数对象 局部状态通过 context.state 暴露出来,根节点状态则为 **context.rootState** ````js const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state,commit,rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
如果getters
中也需要使用全局的状态, 可以接受更多的参数
const moduleA = {
// ...
getters: {
sumWithRootCount (state,getters,rootState) {
return state.count + rootState.count
}
}
}
index.js中的代码,安装插件,创建对象,导出store
import Vue from 'vue' import Vuex from 'vuex' import mutations from './mutations' import actions from './actions' import getters from './getters' import moduleA from './modules/moduleA' // 1.安装插件 Vue.use(Vuex) // 2.创建对象 const 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} ], info: { name: 'kobe', age: 40, height: 1.98 } } const store = new Vuex.Store({ state, mutations, actions, getters, modules: { a: moduleA } }) // 3.导出store独享 export default store // 对象的结构 const obj = { name: 'why', age: 18, height: 1.88, address: '洛杉矶' } const {name, height, age} = obj;
Vue中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?
axios
为什么选择axios
XMLHttpRequests
请求node.js
中发送 http请求Promise API
拦截请求和响应
转换请求和响应数据
axios
请求方式
支持多种请求方式:
get,获取数据的方式
post,提交数据的方式(表单提交以及文件上传)
put,更新数据的方式(提交所有的数据)
patch,提交数据的方式 (提交修改的数据)
delete,删除数据的方式
发送get
请求演示,默认的就是get请求方式
//1.axios的基本使用 //不带参数 axios({ url: 'http://123.207.32.32:8000/home/multidata', }).then(res => { console.log(res); }) //携带参数 axios({ url: 'http://123.207.32.32:8000/home/data', // 专门针对get请求的参数拼接,若用post则用data params: { type: 'pop', page: 1 } }).then(res => { console.log(res); })
发送并发请求
演示
axios.all
, 可以放入多个请求
的数组
.axios.all([])
返回的结果是一个数组,使用 axios.spread
可将数组 [res1,res2]
展开为 res1, res2
//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);
console.log(results[0]);
console.log(results[1]);
})
在上面的示例中, 我们的BaseURL
是固定
的
固定
的.axios
的全局配置
//3.使用全局的axios和对应的配置在进行网络请求 axios.defaults.baseURL = 'http://123.207.32.32:8000' axios.defaults.timeout = 5000 axios.all([axios({ url: '/home/multidata' }), axios({ url: '/home/data', params: { type: 'sell', page: 5 } })]).then(axios.spread((res1, res2) => { console.log(res1); console.log(res2); }))
常见的配置选项:
为什么要创建axios的实例呢?
//4.创建对应的axios的实例 const instance1 = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) instance1({ url: '/home/multidata' }).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.33.33:8000', timeout: 10000, // headers: {} })
还可以直接 return instance,因为instance的返回类型就为 Promise
import axios from 'axios' export function request(config) { // 1.创建axios的实例 const instance = axios.create({ baseURL: 'http://123.207.32.32:8000', timeout: 5000 }) // 2.axios的拦截器 // 2.1.请求拦截的作用 instance.interceptors.request.use(config => { // console.log(config); // 1.比如config中的一些信息不符合服务器的要求 // 2.比如每次发送网络请求时, 都希望在界面中显示一个请求的图标 // 3.某些网络请求(比如登录(token)), 必须携带一些特殊的信息 //拦截到并修改为想要的数据之后,需要将 config 返回出去 return config }, err => { console.log(err); }) // 2.2.响应拦截 //服务器已经响应过了,传入结果result: res instance.interceptors.response.use(res => { // console.log(res); return res.data }, err => { console.log(err); }) // 3.发送真正的网络请求 return instance(config) }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。