内容概述
认识Vuejs
为什么学习Vuejs
简单认识一下Vuejs
Vuejs安装方式
CDN引入
下载和引入
NPM安装管理
Vuejs初体验
Hello Vuejs
Vue列表展示
案例:计数器
Vuejs的MVVM
Vue中的MVVM
我相信每个人学习Vue的目的是各部相同的。
Vue (读音 /vjuː/,类似于 view),不要读错。
Vue是一个渐进式的框架,什么是渐进式的呢?
Vue有很多特点和Web开发中常见的高级功能
这些特点,你不需要一个个去记住,我们在后面的学习和开发中都会慢慢体会到的,一些技术点我也会在后面进行讲解。
学习Vuejs的前提?
使用一个框架,我们第一步要做什么呢?安装下载它
安装Vue的方式有很多:
你可以选择引入开发环境版本还是生产环境版本
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<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
后续通过webpack和CLI的使用,我们使用该方式。
我们来做我们的第一个Vue程序,体验一下Vue的响应式
代码做了什么事情?
我们来阅读JavaScript代码,会发现创建了一个Vue对象。
创建Vue对象的时候,传入了一些options:{}
浏览器执行代码的流程:
并且,目前我们的代码是可以做到响应式的。
代码见,01-HelloVuejs.html
<body>
<div id="app">
<h2>{{message}}h2>
<h1>{{name}}h1>
div>
<div>{{message}}div>
<script src="../js/vue.js">script>
<script>
// 源码里面有类似于这样的东西 有一个Vue的类 可以往里面传一些参数 Vue的参数是对象类型
function Vue() {}
script>
<script>
// let(变量)/const(常量)
// 编程范式: 声明式编程 实例管理div时,只需声明显示什么东西
const app = new Vue({
el: "#app", // 用于挂载要管理的元素
data: {
// 定义数据
message: "你好啊,李银河!",
name: "coderwhy",
},
});
// 原始js的做法(编程范式: 命令式编程) 明确告诉一步步该如何做
// 1.创建div元素,设置id属性
// 2.定义一个变量叫message
// 3.将message变量放在前面的div元素中显示
// 4.修改message的数据: 今天天气不错!
// 5.将修改后的数据再次替换到div元素
// Vue的响应式 --> 可以在打印台修改 app.name='yyy'
script>
body>
现在,我们来展示一个更加复杂的数据:数据列表。
比如我们现在从服务器请求过来一个列表
希望展示到HTML中。
HTML代码中,使用v-for指令
是不是变得So Easy,我们再也不需要在JavaScript代码中完成DOM的拼接相关操作了
而且,更重要的是,它还是响应式的。
- {{item}}
代码见,03-vue案例-计数器.html
什么是MVVM呢?
我们直接来看Vue的MVVM
看至这
视图层
在我们前端开发中,通常就是DOM层。
主要的作用是给用户展示各种信息。
数据层
数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据。
在我们计数器的案例中,就是后面抽取出来的obj,当然,里面的数据可能没有这么简单。
视图模型层
视图模型层是View和Model沟通的桥梁。
一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中
另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。
计数器的MVVM
我们的计数器中就有严格的MVVM思想
View依然是我们的DOM
Model就是我们我们抽离出来的obj
ViewModel就是我们创建的Vue对象实例
它们之间如何工作呢?
首先ViewModel通过Data Binding让obj中的数据实时的在DOM中显示。
其次ViewModel通过DOM Listener来监听DOM事件,并且通过methods中的操作,来改变obj中的数据。
有了Vue帮助我们完成VueModel层的任务,在后续的开发,我们就可以专注于数据的处理,以及DOM的编写工作了。
你会发现,我们在创建Vue实例的时候,传入了一个对象options。
这个options中可以包含哪些选项呢?
目前掌握这些选项:
el:
类型:string | HTMLElement
作用:决定之后Vue实例会管理哪一个DOM。
data:
类型:Object | Function (组件当中data必须是一个函数)
作用:Vue实例对应的数据对象。
methods:
生命周期coderwhy理解:事物从诞生到消亡的整个过程。
Vue生命周期:
主要内容:
插值操作
绑定属性
计算属性
事件监听
条件判断
循环遍历
阶段案例
v-model
如何将data中的文本数据,插入到HTML中呢?
Mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式。
<div id="app">
<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: {
firstName: 'kobe',
lastName: 'bryant',
counter: 100
}
})
script>
我们可以像下面这样来使用,并且数据是响应式的
但是,在某些情况下,我们可能不希望界面随意的跟随改变。这个时候,我们就可以使用一个Vue的指令
v-once:
该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的)
该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
代码如下:
某些情况下,我们从服务器请求到的数据本身就是一个HTML代码
如果我们希望解析出HTML展示
v-text作用和Mustache比较相似:都是用于将数据显示在界面中
v-text通常情况下,接受一个string类型
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
比如下面的代码:
当加上该指令后,
代码见,06-v-cloak指令的使用.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<style>
[v-cloak] {
display: none;
}
style>
head>
<body>
<div id="app" v-cloak>
<h2>{{message}}h2>
div>
<script src="../js/vue.js">script>
<script>
// 在vue解析之前, div中有一个属性v-cloak
// 在vue解析之后, div中没有一个属性v-cloak
setTimeout(function () {
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
}, 1000)
script>
body>
html>
前面我们学习的指令主要作用是将值插入到我们模板的内容当中。
但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
这个时候,我们可以使用v-bind指令:
下面,我们就具体来学习v-bind的使用
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍)
在开发中,有哪些属性需要动态进行绑定呢?
比如通过Vue实例中的data绑定元素的src和href,代码如下:
v-bind有一个对应的语法糖,也就是简写方式
简写方式如下:
很多时候,我们希望动态的来切换class,比如:
绑定class有两种方式:
绑定方式:对象语法
对象语法有下面这些用法:
用法一:直接通过{}绑定一个类
Hello World
用法二:也可以通过判断,传入多个值
Hello World
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
Hello World
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
绑定方式:数组语法
数组语法有下面这些用法:
用法一:直接通过{}绑定一个类
Hello World
用法二:也可以传入多个值
Hello World
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
Hello World
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
Hello World
我们可以利用v-bind:style来绑定一些CSS内联样式。
在写CSS属性名的时候,比如font-size
绑定style有两种方式:
:style="{color: currentColor, fontSize: fontSize + 'px'}"
代码见,
{{message}}
{{message}}
{{message}}
{{message}}
我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
我们可以将上面的代码换成计算属性:
代码见,
{{firstName + ' ' + lastName}}
{{firstName}} {{lastName}}
{{getFullName()}}
{{fullName}}
附:高阶函数reduce的补充
代码见,
总价格: {{totalPrice}}
每个计算属性都包含一个getter和一个setter
代码见,03-计算属性的setter和getter
在浏览器控制台查看缓存:
(fullName没有变化,就调用了一次)
(fullName有变化,重新调用)
代码见,
{{firstName}} {{lastName}}
{{getFullName()}}
{{getFullName()}}
{{getFullName()}}
{{getFullName()}}
{{fullName}}
{{fullName}}
{{fullName}}
{{fullName}}
事实上var的设计可以看成JavaScript语言设计上的错误,但是这种错误多半不能修复和移除,以为需要向后兼容
块级作用域
// 下面是一个代码块。{} 就是一个代码块,有没有{}对于定义变量来说没任何意义 内外都可以使用name
{
var name = 'why';
console.log(name);
}
console.log(name); // 可以使用name
补充:JS:代码块{},及代码块的作用是什么?
2.没有块级作用域引起的问题: if的块级
if (true) {
var name = 'why';
}
console.log(name);// 可以打印
if (true) {
var name = 'why';
}
console.log(name);// 可以打印
var func;
if (true) {
var name = 'why';
func = function () { // 这个变量是为了打印这里的name
console.log(name);
}
// func();// 可以打印
}
name = 'kobe'; // 这里把变量改掉了,不应该能改
func(); // 依然可以打印
// console.log(name);
3.没有块级作用域引起的问题: for的块级
综上,*if和for里面没有块级*
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
(function (num) { // 0
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
})(i)
}
补充:立即执行函数
// 立即执行函数
(function () {
}())
(function () {
})()
var name = "yyyy";
function abc(name) {
console.log(name);
}
name="why";
abc('aaaaaa');// 无论外部怎么改变name,打印的还是aaaa
var name = 'why'
function abc(bbb) { // bbb = 'why'
console.log(bbb);
}
abc(name);// 也不会有任何冲突 name还是why
name = 'kobe'
补充:关于一些闭包的文章
闭包,看这一篇就够了——带你看透闭包的本质,百发百中
【JS】函数闭包(重点)
前端面试题:闭包
// es6写法
const btns = document.getElementsByTagName('button')
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 0
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 1
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 2
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 3
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 4
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
// ES5
var i = 5
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
const关键字
什么时候使用const呢?
建议:在ES6开发中优先使用const只有需要改变某一个标识符的时候才使用let
const的注意
const注意一
const a=20;
a=30;// 错误:不可以修改
const注意二
const name; // 错误:const修饰的标识符必须赋值
// 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);
const obj = new Object()
const obj = { // 这个{} 就是对象的字面量
name: 'why',
age: 18,
run: function () {
console.log('在奔跑');
},
eat: function () {
console.log('在次东西');
}
}
1.属性的增强写法
const name = 'why';
const age = 18;
const height = 1.88
ES5的写法:
// ES5的写法
const obj = {
name: name,
age: age,
height: height
}
console.log(obj);
ES6的写法:
// ES6的写法
const obj = {
name,
age,
height,
}
console.log(obj);
2.函数的增强写法
ES5的写法
// ES5的写法
const obj = {
run: function () {
},
eat: function () {
}
}
ES6的写法
// ES6
const obj = {
run() {
},
eat() {
}
}
在前端开发中,我们需要经常和用于交互。
v-on介绍
下面,我们就具体来学习v-on的使用
这里,我们用一个监听按钮的点击事件,来简单看看v-on的使用
注:v-on也有对应的语法糖:
代码见,
{{counter}}
当通过methods中定义方法,以供@click调用时,需要注意参数问题:
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
代码见,
v-if、v-else-if、v-else
简单的案例演示:
v-if的原理:
代码见,
abc
abc
abc
abc
abc
{{message}}
代码见
abc
abc
abc
abc
abc
{{message}}
isShow为false时, 显示我
代码见,
优秀
良好
及格
不及格
{{result}}
05-用户登录切换的案例(小问题).html
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
v-if和v-show对比
v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
开发中如何选择呢?
代码见,
{{message}}
{{message}}
当我们有一组数据需要进行渲染时,我们就可以使用v-for来完成。
我们来看一个简单的案例:
如果在遍历的过程中不需要使用索引值
如果在遍历的过程中,我们需要拿到元素在数组中的索引值呢?
代码见,
- {{item}}
-
{{index+1}}.{{item}}
- {{item}}
- {{value}}-{{key}}
- {{value}}-{{key}}-{{index}}
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。
为什么需要这个key属性呢(了解)?
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
所以我们需要使用key来给每个节点做一个唯一标识
所以一句话,key的作用主要是为了高效的更新虚拟DOM。
代码见,
- {{item}}
补充:
vue 过滤器filter(全面)
HTML代码:
书籍名称
出版日期
价格
购买数量
操作
{{item.id}}
{{item.name}}
{{item.date}}
{{item.price | showPrice}}
{{item.count}}
总价格: {{totalPrice | showPrice}}
购物车为空
CSS代码:
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
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
// 其他计算总价格方法
// for (let i in/of this.books)
// reduce
}
},
filters: { // 过滤器
showPrice(price) { // 参数是你要过滤的东西
// toFixed 保留两位小数
return '¥' + price.toFixed(2)
}
}
})
计算总价格其他方法 :
computed: {
totalPrice() {
// 1.普通的for循环
// let totalPrice = 0
// for (let i = 0; i < this.books.length; i++) {
// totalPrice += this.books[i].price * this.books[i].count
// }
// return totalPrice
// 2.for (let i in this.books)
// let totalPrice = 0
// for (let i in this.books) {
// const book = this.books[i]
// totalPrice += book.price * book.count
// }
//
// return totalPrice
// 3.for (let i of this.books)
// let totalPrice = 0
// for (let item of this.books) {
// totalPrice += item.price * item.count
// }
// return totalPrice
return this.books.reduce(function (preValue, book) {
return preValue + book.price * book.count
}, 0)
}
},
也可以使用高阶函数reduce,下面介绍高阶函数
// 编程范式: 命令式编程/声明式编程
// 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
// filter/map/reduce
// filter中的回调函数有一个要求: 必须返回一个boolean值
// true: 当返回true时, 函数内部会自动将这次回调的n加入到新的数组中
// false: 当返回false时, 函数内部会过滤掉这次的n
const nums = [10, 20, 111, 222, 444, 40, 50]
// 高阶函数 本身参数也是一个函数
// let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n);
// console.log(total);
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);
// 1.filter函数的使用
// 10, 20, 40, 50
let newNums = nums.filter(function (n) {
return n < 100
})
// console.log(newNums);
// 2.map函数的使用
// 20, 40, 80, 100
let new2Nums = newNums.map(function (n) { // 20
return n * 2
})
console.log(new2Nums);
// 3.reduce函数的使用
// reduce 作用对数组中所有的内容进行汇总
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
// 普通写法
// 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)
}
console.log(new2Nums);
// 3.需求:将所有new2Nums数字相加,得到最终的记过
let total = 0
for (let n of new2Nums) {
total += n
}
console.log(total);
表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。
Vue中使用v-model指令来实现表单元素和数据的双向绑定。
案例的解析:
{{message}}
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
也就是说下面的代码:
等同于下面的代码:
代码,
{{message}}
<div id="app">
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">男
label>
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
label>
<label for="other">
<input type="radio" id="other" value="其他" v-model="sex">其他
label>
<h2>您选择的性别是: {{sex}}h2>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
sex: '女' // 可以给radio默认值
}
})
script>
复选框分为两种情况:单个勾选框和多个勾选框
单个勾选框:
多个复选框:
代码:
您选择的是: {{isAgree}}
篮球
足球
乒乓球
羽毛球
您的爱好是: {{hobbies}}
和checkbox一样,select也分单选和多选两种情况。
单选:只能选中一个值。
多选:可以选中多个值。
代码,
您选择的水果是: {{fruit}}
您选择的水果是: {{fruits}}
初看Vue官方值绑定的时候,我很疑惑:what the hell is that?
但是仔细阅读之后,发现很简单,就是动态的给value赋值而已:
这不就是v-bind在input中的应用吗?搞的我看了很久,搞不清他想将什么。
这里不再给出对应的代码,因为会用v-bind,就会值绑定的应用了。
代码见,06-v-model修饰符的使用.html
{{message}}
{{age}}-{{typeof age}}
您输入的名字:{{name}}
内容概述
认识组件化
注册组件
组件其他补充
组件数据存放
父子组件通信
父级向子级传递
子级向父级传递
插槽slot
这里的步骤都代表什么含义呢?
1.Vue.extend():
调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。
该模板就是在使用到组件的地方,要显示的HTML代码。
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
2.Vue.component():
3.组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)
1.全部小写,使用短横线-连接
2.或驼峰命名
父子组件错误用法:以子标签的形式在Vue实例中使用
父子组件正确用法:在 父组件的components注册,在template 中使用子组件标签
代码见,
在上面注册组件的方式,可能会有些繁琐。
Vue为了简化这个过程,提供了注册的语法糖。
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
语法糖注册全局组件和局部组件:
代码见,
刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
Vue提供了两种方案来定义HTML模块内容:
使用
我是内容,呵呵呵我是标题
补充
我们发现不能访问,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。
结论:Vue组件应该有自己保存数据的地方。
组件自己的数据存放在哪里呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHc7QGmo-1631696029338)(C:\Users\atiff\AppData\Roaming\Typora\typora-user-images\image-20210730154101436.png)]
代码见,
{{title}}
我是内容,呵呵呵
为什么data在组件中必须是一个函数呢?
代码见,
当前计数: {{counter}}
在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。
但是,在开发中,往往一些数据确实需要从上层传递到下层:
如何进行父子组件间的通信呢?Vue官方提到
在下面的代码中,我直接将Vue实例当做父组件,并且其中包含子组件来简化代码。
真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的
代码,
Title
- {{item}}
{{cmessage}}
在前面,我们的props选项是使用一个数组。
我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。
验证都支持哪些数据类型呢?
当我们有自定义构造函数时,验证也支持自定义的类型
代码见,
// ***方式2:对象,对象可以设置传递时的类型,也可以设置默认值等->当需要对props进行类型等验证时
props: {
// 1.类型限制
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
// cmovies: Array,
// cmessage: String,
// 多个可能的类型
// propB: [String, Number],
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String,
default: 'aaaaaaaa',
required: true // 必填的字符串
},
// 类型是对象或者数组时, 默认值必须是一个工厂函数
cmovies: {
type: Array,
default () {
return {
message: 'hello'
}
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
},
代码见,
{{cInfo}}
{{childMyMessage}}
props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
什么时候需要自定义事件呢?
自定义事件的流程:
我们来看一个简单的例子:
代码见,
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<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>
// 1.子组件
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)
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
script>
body>
html>
需求
分析
代码,
父组件
-----num1----
{{num1}}
-----num2----
{{num2}}
子组件
-----number1----
props:{{number1}}
data:{{dnumber1}}
-----number2----
props:{{number2}}
data:{{dnumber2}}
代码见,
props:{{number1}}
data:{{dnumber1}}
props:{{number2}}
data:{{dnumber2}}
参考文章:Vue——watch选项详解_
1.什么是watch选项?(官方解释)
类型:{ [key: string]: string | Function | Object | Array }
详细:一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例i化时调用 $watch(),遍历 watch 对象的每一个属性。
2.通俗解释
watch选项能够监听值的变化。
3.基本使用
代码见,
this.$children
是一个数组类型,它包含所有子组件对象。
这里通过一个遍历,取出所有子组件的message
状态。
$children的缺陷:
$children
访问子组件时,是一个数组类型
,访问其中的子组件
必须通过索引值
。$refs
$refs的使用:
$refs
和ref
指令通常是一起使用
的。ref
给某一个子组件绑定一个特定的ID
。this.$refs.ID
就可以访问到该组件了。代码见,
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<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();
// }
// console.log(this.$children[3].name);
// 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb'
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
},
}
})
script>
body>
html>
如果我们想在子组件中直接访问父组件,可以通过$parent
注意事项:
Vue
开发中,我们允许通过$parent
来访问父组件,但是在真实开发中尽量不要
这样做。$parent
直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。代码见,
我是cpn组件
我是子组件
刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢?
在Vue1.x的时候,可以通过 d i s p a t c h 和 dispatch和 dispatch和broadcast完成
在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
slot翻译为插槽:
组件的插槽:
栗子:移动网站中的导航栏。
但是,每个页面的导航是一样的吗?No,我以京东M站为例
如何去封装这类的组件呢?
如何封装合适呢?抽取共性,保留不同。
这就是为什么我们要学习组件中的插槽slot的原因
了解了为什么用slot,我们再来谈谈如何使用slot?
我们通过一个简单的例子,来给子组件定义一个插槽:
代码见,
哈哈哈
呵呵呵
呵呵呵
我是div元素
我是p元素
略略略
我是组件
我是组件, 哈哈哈
我是组件222
我是组件22222, 哈哈哈
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC。
推荐文章:vue_插槽的理解和使用 - 圆圆方方 - 博客园 (cnblogs.com)
当子组件的功能复杂时,子组件的插槽可能并非是一个。
如何使用具名插槽呢?
我们来给出一个案例:
代码(vue2.5版本):
没有名字的替换
标题
没有名字的插槽默认内容
左边
中间
右边
返回
标题
替换没有名字的插槽
替换后的右边
左边
中间
右边
默认插槽内容
在真正学习插槽之前,我们需要先理解一个概念:编译作用域。
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
我们来考虑下面的代码是否最终是可以渲染出来的:
答案:最终可以渲染出来,也就是使用的是Vue实例的属性。
为什么呢?
代码见,
我是子组件
我是内容, 哈哈哈
作用域插槽是slot一个比较难理解的点,而且官方文档说的又有点不清晰。
这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会:
我们先提一个需求:
我们来看看子组件的定义:
代码(vue2.6之前):
{{slot}}
{{slot.data.join(' - ')}}
{{slot.data.join(' * ')}}
- {{item}}
代码:
默认
替换样式
{{props1.data1.join('-')}}
{{props1.msg}}
{{props2.data2}}
{{props3.data3}}
-
{{item}}
{{name}}
默认插槽
PPT无。ximnd中有
PPT无。ximnd中有
PPT无。ximnd中有
常见的模块化规范:
CommonJS、AMD、CMD,也有ES6的Modules
我们可以使用匿名函数来解决方面的重名问题
但是如果我们希望在main.js文件中,用到flag,应该如何处理呢?
内容概述
认识webpack
webpack的安装
webpack的起步
webpack的配置
loader的使用
webpack中配置Vue
plugin的使用
搭建本地服务器
什么是webpack?
我们先看看官方的解释:
但是它是什么呢?用概念解释概念,还是不清晰。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X2DEHZ20-1631696029359)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\1.png)]
grunt/gulp的核心是Task
我们来看一个gulp的task
下面的task就是将src下面的所有js文件转成ES5的语法。
并且最终输出到dist文件夹中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pZaEReI-1631696029360)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\2.png)]
什么时候用grunt/gulp呢?
所以,grunt/gulp和webpack有什么不同呢?
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
查看自己的node版本:
node -v
全局安装webpack(这里我先指定版本号3.6.0,因为vue cli2依赖该版本)
npm install [email protected] -g
局部安装webpack(后续才需要)
cd 对应目录
npm install [email protected] --save-dev
为什么全局安装后,还需要局部安装呢?
我们创建如下文件和文件夹:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVaCA13O-1631696029361)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\3.png)]
文件和文件夹解析:
mathUtils.js文件中的代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSZ38iru-1631696029363)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\4.png)]
main.js文件中的代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dvifJngz-1631696029363)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\5.png)]
现在的js文件中使用了模块化的方式进行开发,他们可以直接使用吗?不可以。
我们应该怎么做呢?使用webpack工具,对多个js文件进行打包。
OK,如何打包呢?使用webpack的指令即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQVYb2sQ-1631696029364)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\6.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qY192Fxn-1631696029365)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\7.png)]
打包后会在dist文件下,生成一个bundle.js文件
文件内容有些复杂,这里暂时先不看,后续再进行分析。
bundle.js文件,是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html中引入即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wtDbYBzj-1631696029366)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\8.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6ivdLg3-1631696029367)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\9.png)]
我们考虑一下,如果每次使用webpack的命令都需要写上入口和出口作为参数,就非常麻烦,有没有一种方法可以将这两个参数写到配置中,在运行时,直接读取呢?
当然可以,就是创建一个webpack.config.js文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zxRywby-1631696029368)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\10.png)]
因为output的路径得是绝对路径,需要安装path包
依赖到node的包,建议先npm init初始化一下,会出现下面信息,填package name后一路回车就行,然后会生成package.json文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hpfw2rR8-1631696029369)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\11.png)]
在终端输入 webpack 就能直接打包了 ,但是在开发中一般不会用webpack,一般映射为npm run build
目前,我们使用的webpack是全局的webpack,如果我们想使用局部来打包呢?
第一步,项目中需要安装自己局部的webpack
npm install [email protected] --save-dev
第二步,通过node_modules/.bin/webpack
node_modules/.bin/webpack
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQgbaWAc-1631696029370)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\12.png)]
但是,每次执行都敲这么一长串有没有觉得不方便呢?
OK,我们可以在package.json的scripts中定义自己的执行脚本。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ffYFeU2-1631696029371)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\13.png)]
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
首先,会寻找本地的node_modules/.bin路径中对应的命令。
如果没有找到,会去全局的环境变量中寻找。
如何执行我们的build指令呢?
npm run build
项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独的文件中。
在src目录中,创建一个css文件,其中创建一个normal.css文件。
我们也可以重新组织文件的目录结构,将零散的js文件放在一个js文件夹中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tNxJ283U-1631696029372)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\14.png)]
normal.css中的代码非常简单,就是将body设置为red
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rrsFCMS1-1631696029373)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\15.png)]
但是,这个时候normal.css中的样式会生效吗?
在入口文件中引用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gd0swOaR-1631696029374)(D:\ProjectsQin\otherQin\cloudimages\vue\coderwhy\5\16.png)]
什么是NPM呢?
cnpm安装
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install [name]
npm install webpack -g
vue init webpack my-project
vue create my-project
main.js代码:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false // 消息提示的环境配置,设置为开发环境或者生产环境
/* eslint-disable no-new */
/*
// runtime-compiler
new Vue({
el: '#app',
components: { App },
template: ' ',
})
*/
// const cpn = { // 组件
// template: '{{message}}',
// data() {
// return {
// message: '我是组件message'
// }
// }
// }
// 也可以用下面这个方案 runtime-only
new Vue({
el: '#app',
render: function (createElement) { // createElement是一个函数
// 1.使用方式一: createElement('标签', {标签的属性}, ['内容'])
// 1.1 基本使用
// return createElement('h2', {
// class: 'box'
// },
// ['Hello World'])
// 1.2 嵌套render函数
// return createElement('h2', {
// class: 'box'
// },
// ['Hello World', createElement('button', ['按钮'])])
// 2.传入组件对象:
// return createElement(cpn)
return createElement(App)
}
})
// runtime-compiler(v1)
// template -> ast -> render -> vdom -> UI
// runtime-only(v2)(1.性能更高 2.下面的代码量更少)
// render -> vdom -> UI
function
const aaa = function () {
}
// 2.对象字面量中定义函数
const obj = {
bbb() {
}
}
// 3.ES6中的箭头函数
// const ccc = (参数列表) => {
//
// }
const ccc = () => {
}
// 1.参数问题:
// 1.1.放入两个参数
const sum = (num1, num2) => {
return num1 + num2
}
// 1.2.放入一个参数
const power = num => {
return num * num
}
// 2.函数中
// 2.1.函数代码块中有多行代码时
const test = () => {
// 1.打印Hello World
console.log('Hello World');
// 2.打印Hello Vuejs
console.log('Hello Vuejs');
}
// 2.2.函数代码块中只有一行代码
// const mul = (num1, num2) => {
// return num1 + num2
// }
const mul = (num1, num2) => num1 * num2
console.log(mul(20, 30));
// const demo = () => {
// console.log('Hello Demo');
// }
const demo = () => console.log('Hello Demo')
console.log(demo());
setTimeout(function () {
console.log(this);
}, 1000)
setTimeout(() => {
console.log(this);
}, 1000)
const obj = {
aaa() {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // obj对象
})
}
}
obj.aaa()
const obj = {
aaa() {
setTimeout(function () {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // window
})
})
setTimeout(() => {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // obj
})
})
}
}
obj.aaa()
内容概述
- 认识路由
- vue-router基本使用
- vue-router嵌套路由
- vue-router参数传递
- vue-router导航守卫
- keep-alive
说起路由你想起了什么?
额, 啥玩意? 没听懂
早期的网站开发整个HTML页面是由服务器来渲染的.
但是, 一个网站, 这么多页面服务器如何处理呢?
上面的这种操作, 就是后端路由.
后端路由的缺点:
前后端分离阶段:
单页面富应用阶段:
前端路由的核心是什么呢?
目前前端流行的三大框架, 都有自己的路由实现:
当然, 我们的重点是vue-router
vue-router是基于路由和组件的
因为我们已经学习了webpack, 后续开发中我们主要是通过工程化的方式进行开发的.
npm install vue-router --save
使用vue-router的步骤:
第一步: 创建路由组件
: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个标签.
: 该标签会根据当前的路径, 动态渲染出不同的组件.
最终效果如下
我们这里还有一个不太好的实现:
如何可以让路径默认跳到到首页, 并且渲染首页组件呢?
配置解析:
在前面的中, 我们只是使用了一个属性: to, 用于指定跳转的路径.
还有一些其他属性:
// 配置路由相关的信息
// 导入路由对象
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import About from '../components/About'
// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)
// 2.创建VueRouter对象
const routes = [
{
path: '',
// redirect重定向
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
// 创建路由实例,并且传入路由映射配置
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
// 3.将router对象传入到Vue实例
export default router
App.vue
我是APP组件
首页
关于
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
new Vue({
el: '#app',
// 在Vue实例中挂载创建的路由实例
router,
render: h => h(App)
})
代码:
我是用户界面
我是用户的相关信息, 嘿嘿嘿
计算属性userId:{{userId}}
$route.params.id:{{$route.params.id}}
...
const routes = [
...
{
path:'/user/:id',
component:User
}
]
const router = new VueRouter({
routes,
mode:'history',
linkActiveClass:'active'
})
export default router
我是APP组件
首页
关于
用户
官方给出了解释:
官方在说什么呢?
懒加载打包
const Home = resolve => {
require.ensure(['../components/Home.vue'], () => {
resolve(require('../components/Home.vue'))
})
};
const About = resolve => require(['../components/About.vue'], resolve);
const Home = () => import('../components/Home.vue')
URL:协议://主机:端口/路径?查询
传递参数主要有两种类型:
params和query
params的类型:
query的类型:
如何使用它们呢? 也有两种方式: 的方式和JavaScript代码方式
我是Profile组件
{{$route.query.name}}
{{$route.query.age}}
{{$route.query.height}}
我是APP组件
首页
关于
档案
"导航”表示路由正在发生改变。
正如其名,
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
我们来考虑一个需求: 在一个SPA应用中, 如何改变网页的标题呢?
普通的修改方式:
// Home.vue
mounted() {
console.log('home mounted');
document.title='首页'
},
// About.vue
mounted() {
console.log('home mounted');
document.title='关于'
},
// ...
// 其他页面也是这样写
有没有更好的办法呢? 使用导航守卫即可.
什么是导航守卫?
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
// ...
const routes = [
{
path: '',
// redirect重定向
redirect: '/home'
},
{
path: '/home',
component: Home,
// meta元数据(描述数据的数据)
meta: {
title: '首页'
},
// 嵌套路由
children: [
// {
// path: '',
// redirect: 'news'
// },
{
path: 'news',// 没有斜杠 /
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
{
path: '/about',
component: About,
meta: {
title: '关于'
},
},
{
path: '/user/:id',
component: User,
meta: {
title: '用户'
},
},
{
path: '/profile',
component: Profile,
meta: {
title: '档案'
},
}
]
const router = new VueRouter({
// ...
})
// 1. 全局导航守卫
// 1.1 前置守卫(guard) 路由跳转之前
// beforeEach()注册一个全局前置守卫,本身是一个函数,又传入一个函数guard,有三个参数
router.beforeEach((to, from, next) => {
// 从from跳转到to
// // to 和 from都是route对象
// document.title = to.meta.title
// 上面这样写的话如果有嵌套路由的话是undefined,要使用matched(匹配)获取
document.title = to.matched[0].meta.title
console.log(to);// 到哪个页面去?
console.log(from);// 从哪个页面来?
// 调用该方法后,才能进入下一个钩子
// 如果是后置钩子,也就是afterEach,不需要主动调用next()函数
// 这里其实可以判断用户登陆权限之类的,拦截访问 ,权限不符调用next(false)拦截
next()
})
// 1.2 后置钩子(hook) 不需要主动调用next()函数
router.afterEach((to, from) => {
console.log('----');
})
// 钩子->回调
export default router
const routes = [
// ...
{
path: '/about',
component: About,
meta: {
title: '关于'
},
// 2.路由独享的守卫
beforeEnter: (to, from, next) => {
console.log('about beforeEnter');
next()
}
},
// ...
]
const router = new VueRouter({
//...
})
// Home.vue
我是首页
我是首页内容, 哈哈哈
新闻
消息
{{message}}
App.vue
首页
关于
Home.vue
我是首页
我是首页内容, 哈哈哈
新闻
消息
{{ message }}
Profile.vue
User.vue
分析:
body{
margin: 0;
padding: 0;
}
效果图,基本结构搭建完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HW4bWefG-1631696029425)(C:\Users\atiff\Desktop\59.png)]
复习知识点:(十一)插槽
首页
分类
购物车
我的
首页
分类
购物车
我的