function () {
console.log(this)
}
```
* 箭头函数 - 箭头函数中是没有this的,this指向父级作用域中的 this
* 严格模式下 this 是 undefined 的 (一般用不到,但是要知道)
* call 和 apply 和 bind
```js
var obj1 = {
name: '旺财',
eat: function () {
console.log(this)
}
}
var obj2 = {
name: '小黑',
}
obj1.eat(); // this指向obj1
obj1.eat.call(obj2); // this指向obj2,call改变了this指向
```
* 在 vue2 中 this 指向
vue 当中 this 永远指向实例对象
```html
按钮
{{ this.msg }}
{{ msg }}
```
## Object.defineProperty()
Object.defineProperty()
功能:给对象设置属性用的,使用该方法设置的属性,在设置值和获取值的时候,可以感知到设置值和获取值的过程
参数:
* 参数一: 要添加属性的目标对象
* 参数二: 要添加的属性名
* 参数三: 是一个配置对象,配置当前添加的这个属性的
返回值:
返回参数一这个对象
```js
var obj = {
name: '张三'
}
// 给对象添加属性
obj.age = 18;
obj['sex'] = '男';
var str = '我爱你'
Object.defineProperty(obj, 'height', {
// value: 176, // 设置的当前这个属性的属性值
// writable: true, // 配置是否可修改当前这个属性
enumerable: true, // 配置当前属性是否可枚举
configurable: false, // 配置当前属性是否可以重新定义(被删除)
// 获取值的函数,在我们获取当前属性的时候会走get方法
get() {
return str
},
// 设置值的函数,给当前属性设置值的时候,会走set方法
set(val) {
str = val;
}
})
console.log(obj.height);
obj.height = '身高和爱你没关系';
```
注意:
Object.defineProperty 中 get set 方法和 value writable 冲突
---
拓展:Object 还有哪些常用方法
```js
Object.defineProperty
Object.defineProperties
Object.create()
Object.assign()
Object.keys()
```
## vue2响应式原理
问题一:我们在创建 vm 实例的时候,传入的配置项目data中的数据是如何到vm实例上的?
问题二:当我们修改数据的时候,页面怎么就更新了?
### 数据代理
vue2中响应式原理,主要是通过 数据代理 和 数据劫持 实现的,数据代理 和 数据劫持 底层用的都是 Object.defineProperty() 方法
什么叫数据代理?
数据代理的意义在于,将配置项中的 data 数据代理到 vm 实例上
让我们访问数据的时候,使用 this.xxx 可以直接访问到数据
```js
function VM(options) {
let data = options.data; // 拿到配置的对象
// 数据代理的实现
// Object.keys(data) // 拿到data所有属性组成的数组
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(val) {
console.log('数据代理set')
data[key] = val
}
})
})
}
const vm = new VM({
data: {
name: '张三',
age: 20
}
})
console.log(vm.name)
```
### 数据劫持
什么叫数据劫持?
数据劫持的意义在于,我们修改数据的时候,需要更新DOM显示
更新DOM这个过程需要我们可以劫持到数据的设置/获取
当修改数据的时候,通过 Object.defineProperty() 劫持到数据的修改,在set和get方法中,更新DOM
```js
function VM(options) {
let data = options.data; // 拿到配置的对象
// 数据代理的实现
// Object.keys(data) // 拿到data所有属性组成的数组
Object.keys(data).forEach(key => {
...
})
// 数据劫持
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(data, key, value) { // 注意:这里data拿到的是地址 注意:形参相当于是变量
// data作为一个普通的对象是拿不到设置值和获取值得过程
// 需要使用 Object.defineProperty() 重新给data定义一下属性,让属性具有set和get方法,可以拦截到设置值和获取值的过程
Object.defineProperty(data, key, {
get() {
return value;
},
set(val) {
console.log('数据劫持set')
value = val;
// 当截获到给data设置值的时候,此时应该通知页面更新
// updateDOM() --- 调用更新DOM方法
}
})
}
const vm = new VM({
data: {
name: '张三',
age: 20
}
})
console.log(vm.name)
vm.name = '李四'
// 当给vm.name设置值的时候,走数据代理的set方法,因为当前是给vm下的某一个属性设置值
// 在数据代理的set中,我们写了
// set(val) {
// console.log('数据代理set')
// data[key] = val
// }
// 此时走了 data[key] = val,这行代码,那么此时是给 data的属性设置值
// 而刚刚在 defineReactive 这个函数中把data所有的属性都重写了,变成了可以拦截到设置值和获取值的效果
console.log(vm.name)
// 疑问: 数据劫持函数的参数三 value 不能直接使用 obj.name 来代替 get 返回的值吗?
// 报错 - 有了递归了
// let obj = {
// name: 'xxx'
// }
// Object.defineProperty(obj, 'name', {
// get() {
// return obj.name
// }
// })
// console.log('obj.name', obj.name)
```
---
回顾 - 闭包形成的条件:
1. 函数的嵌套
2. 内部函数引用外部函数变量
3. 外部函数被调用
## computed 和 watch
需求:给姓和名的数据,最终得到姓名,有几种方式?
```html
第一种: 模板中插值语法字符串拼接
{{ firstName + lastName }}
第二种: 指令中字符串拼接
第三种: 通过methods中方法调用返回值
{{ getFullName() }}
{{ getFullName() }}
{{ getFullName() }}
第四种: 通过计算属性 computed 得到
{{ fullName }}
{{ fullName }}
{{ fullName }}
第五种: 通过 watch 来监视数据的变化
{{ fullName2 }}
```
# day02
### computed - 计算属性
计算属性 是一个配置项,在这个配置项当中可以配置属性
这个属性是给vm实例配置的,当vm实例使用这个属性的时候,会自动去走这个函数
计算属性是一个属性,当模板中没有使用到这个属性的时候,它不会去计算;当模板中使用了这个属性,会走这个方法计算出来,并且把结果缓存起来
只要计算属性所依赖的数据不发生变化,就不会重新计算
只要依赖的数据发生变化就会重新计算
**两种写法**
```js
computed: {
// 第一种写法: 函数写法
fullName() {
return this.firstName + ' - ' + this.lastName
},
// 第二种写法: 对象写法
fullName: {
get() {
return this.firstName + ' - ' + this.lastName
},
set(val) {
console.log('走了set', val)
}
}
}
```
### watch - 监听
watch 也是一个配置项,用来监听数据的变化
配置当前实例上已存在的数据,当监听的数据发生变化的时候会执行回调,在回调中可以进行一些 js 的逻辑(同步/异步)操作
**两种写法**
```js
watch: {
// 函数写法
fullName(nval, oval) {
console.log(nval)
},
// 对象写法
fullName: {
handler(nval, oval) {
console.log(nval)
},
immediate: true,
deep: true
},
// 监听某个对象下的具体某个属性
"obj.name": {
handler(nval, oval) {
console.log(nval)
},
immediate: true,
deep: true
},
}
```
immediate - 是否初始化时执行一次监听回调,默认 false
deep - 是否开启深度监听,默认 false
---
重点:
methods 和 computed 区别
1. methods 需要调用 computed不需要
2. 计算属性会把结果缓存在内存中,只要依赖的数据不发生变化,就不会重新计算
computed 和 watch 的区别
1. computed 是计算属性,计算出来是一个属性,这个属性是自身不存在,然后计算得出得
watch 是监视,对已存在数据的监视
2. computed 中不能存在异步,因为计算属性的值依赖return
watch 中是可以存在异步的,监视到行为之后,可以延时出处理,执行操作
## 条件渲染
### v-if
v-if 条件渲染指令有三个 `v-if`、`v-else`、`v-else-if`
v-if 用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
```html
测试文本1
```
v-else、v-else-if 指令必须和 v-if 指令一起使用,先写 v-if 再写 v-else、v-else-if , 和 js 的逻辑 if 语句一样
```html
测试文本1
测试文本2
```
### v-show
v-show 只有这一个指令,同样用于条件性渲染一块内容
```html
测试文本1
测试文本2
```
以上代码用来模拟 v-if 和 v-else 一样的显示效果
---
v-if 和 v-show 的区别?
v-if 条件指令中隐藏的元素,在真实的DOM中是不存在的
v-show 条件指令中隐藏的元素,在真实的DOM中是存在的,只是当前的元素被隐藏掉了(使用css `display: none;` 隐藏的元素)
## 列表渲染
v-for 指令用于列表的渲染,写法有点类似于 js 中的 for...in 语句写法
```html
序号: {{ index }} - id: {{ item.id }} - 内容: {{ item.content }}
```
> 注意:
> key是一个唯一标识,key的作用主要是为了高效的更新虛拟DOM,其原理是vue在diff对比虚拟DOM的过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM操作量,提高性能。
## 列表过滤
需求:列表的过滤
详情:在页面中有 input 框,input中输入内容,列表的展示需要 和 输入的内容相同
```html
id: {{ item.id }} - 内容: {{ item.content }}
```
---
拓展:你用过哪些数组方法,列举出来,越多越好(面试题)
```js
push、pop、shift、unshift
slice
concat
join
reverse
splice
toString
valueOf // 返回当前调用数据的值
sort
forEach
filter
map
find
findIndex
some
every
reduce
```
>什么场景下会用到 valueOf ?
>
>```
>var a = [3, 5]
>var b = { name: '张三' }
>console.log( a + b )
>
>引用数据类型的运算:
>1. 会先调用 valueOf 这个方法,得到数据的值,看是不是一个基本值(基本数据类型)
> a.valueOf() -> [3, 5]
> b.valueOf() -> { name: '张三' }
>2.如果是基本值直接进行运算,如果不是基本值,那么此时调用 toString() 转成字符串
> a.toString() -> "3,5"
> b.toString() -> '[object Object]'
>3. 转成字符串之后,就可以进行运算了
>```
## 列表排序
需求:列表的排序
在页面中有 input 框,input中输入内容,列表的展示需要 和 输入的内容相同
数据中每个对象都有年龄,设置三个按钮,"按升序排列"、"按降序排列"、"按原序排列",点击之后列表展示的结果将有顺序
注意:这里点击排序,也是不能改原数据,并且不能把过滤给丢失掉
```html
按升序排列
按降序排列
按原序排列
id: {{ item.id }} - 内容: {{ item.content }} - 年龄: {{ item.age }}
```
以上列表的过滤和排序都是使用 computed 实现的,computed能实现的东西,watch一定能够实现,接下来使用watch 实现一下,列表的排序和过滤
## 列表的排序和过滤 - watch 实现
```html
按升序排列
按降序排列
按原序排列
id: {{ item.id }} - 内容: {{ item.content }} - 年龄: {{ item.age }}
```
## vue中数组数据的响应式变化
需求:数组数据,修改数据数据有几种方式?
修改下标为0位置的content
```html
第一种修改
第二种修改
第三种修改
{{ item.content }}
```
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
- `push()`
- `pop()`
- `shift()`
- `unshift()`
- `splice()`
- `sort()`
- `reverse()`
## 绑定 class 和 style
```html
内容1
绑定一个class
单独绑定一个class,可以直接绑定字符串,或者数据
内容1
内容1
绑定多个class
数组绑定,数组中直接放类名,或者放数据数据中写类名
内容1
绑定多个类型,但是不确定哪个类名好使
对象绑定的时候,属性名是类名,控制这个类名存在与否,是一个布尔值
哈哈哈
哈哈哈
```
* 绑定class三种写法
* 直接绑定 - 绑定一个class
`
内容1
`
* 数组绑定 - 绑定多个class
`
内容1
`
* 对象绑定 - 绑定多个class
```html
哈哈哈
new Vue({
...,
data: {
isA: true
}
})
```
* 绑定 style
```html
哈哈哈
```
# day03
## 事件拓展 - $event来源
结论:在模板中尽量不要写this
疑问:在模板中写回调的时候,携带上 this 有的情况下会报错,什么情况下会报错?为什么会报错?
注意:在推导过程看不懂的情况下记住结论即可
```html
按钮1
按钮2
按钮3
按钮4
按钮5
按钮6
```

发现:
当@click绑定事件的时候,
回调函数不书写小括号的时候,直接就是把这个函数传到的底层
回调函数书写小括号的时候,底层给你传的内容包裹了一层,包裹的这层函数的return的值才是真正传到底层上的值
结论:只要加小阔好,底层上就包裹一层
当传入的内容加小括号,且加了this
`@click="this.testHandler($event, 9527)"`
此时这里的this指向window,非严格模式下指向window,严格模式下指向undefined
## 事件深入
### 事件传参
* 默认不传参是回调用接收到的是事件对象
`
按钮1 `
* 回调中参数一接的是 9527
`
按钮2 `
* 既有事件对象和参数
`
按钮2 `
### 事件修饰符
* .prevent 修饰符
需求:点击a标签的时候会默认的进行跳转,能不能不让 a 标签点击的时候跳转?
可以,阻止事件的默认行为即可
```html
百度一下
methods: {
baiduClick(e) {
console.log('跳转百度')
// e.preventDefault(); // 阻止默认行为
},
}
```
* .stop
需求:两个div嵌套的时候,点击内部的div可以触发到外部div绑定的事件,可以不可以阻止这种行为?
可以,阻止事件冒泡即可
```html
methods: {
box1Click() {
console.log('box1触发')
},
box2Click(e) {
console.log('box2触发')
// e.stopPropagation(); // 阻止冒泡
},
}
```
* 不常用的事件修饰符
* .capture 默认事件是冒泡的,加.capture修饰符,事件变为捕获触发
* .self 修饰符,只有自己点击事件被触发时候才会执行,冒泡过来的不会执行回调
* .once 事件只触发一次 (和.stop的作用一样,都是阻止冒泡,只不过针对的绑定元素不一样)
* .passive 用在滚轮事件上
当滚轮滚动的时候,会触发一个事件,如果在这个回调中有非常耗时的操作
例如说: 循环打印10万次'i love you',此时等待这个耗时的操作执行完毕之后,才能页面滚动(这是正常的)
当加了.passive之后,先去滚了页面,在执行这个回调操作
* 键盘事件 - 一般在input框中会用到
.enter 键盘事件修饰符
```html
methods: {
keydownHandler() {
console.log('keydown')
},
keyupHandler(e) {
// if (e.keyCode == 13 || e.code == 'Enter') {
// console.log('敲击了回车', e.keyCode, e.code)
// }
console.log('执行了回车事件回调')
},
}
```
---
不常用键盘修饰符
```html
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
```
## 收集表单数据
需求: 注册账号

```html
用户名:
密码:
邮箱:
性别:
男
女
爱好:
抽烟
喝酒
烫头
城市:
{{ c.name }}
是否同意该协议
提交
```
## vue内置指令
* v-text 渲染文本
* v-html 渲染html
* v-show 通过css控制显示和隐藏
* v-if DOM中直接不渲染 条件控制(true、false)
v-else
v-else-if
* v-for 列表渲染,像forin 注意: 绑定key
* v-on 绑定事件,简写 v-on:click --> @click
* v-bind 绑定属性,v-bind:aa="xxx" --> :aa="xxx"
* v-model 双向数据绑定,用在表单元素上,用来收集用户输入的内容
* v-slot -----> 单独说,后面讲
* v-pre 加了该指令,标签中的内容直接渲染,不会解析
* v-once 一次,加了该指令的元素,元素的内容只会被解析一次
* v-cloak
当网络不好的时候,vue.js这个文件还没请求回来的时候,此时页面请求回来了
页面会展示出插值语法来,对用户显示很不友好
使用 v-cloak 来解决闪动插值语法这个现象
1. 给有插值语法的标签添加这个指令
2. 通过css控制当前元素不显示
```html
```
未使用v-cloak效果

使用v-cloak效果

* vue获取真实DOM
步骤:
1. 给标签设置 ref 属性,属性值自己取
2. 在vue中,使用 $refs 来获取
$refs是一个对象,这个对象会把页面中所有有ref属性的元素拿到
$refs对象中的属性名是标签上的ref属性的属性值
```html
获取元素
哈哈
呵呵
methods: {
getEl() {
console.log(this.$refs)
console.log(this.$refs.boxRef)
console.log(this.$refs.qwerRef)
}
}
```
## 过渡和动画
需求: 点击按钮让div盒子显示和隐藏, 需要过渡的效果是宽高和透明度
步骤:
1. 将要过渡的元素放到 transition 标签中 (这个标签是vue提供的)
2. 书写类名
v-enter 进入前
v-enter-to 进入后
v-enter-active 过程中
3. 如果页面中过多个过渡的元素,给 transition 标签添加 name 属性
类名 v- 开头变成 transition 的 name 属性值开头
```html
```
animation 复合属性:
| 属性名 | 解释 |
| --- | --- |
| animation-name | 指定要绑定到选择器的关键帧的名称 |
| animation-duration | 动画指定需要多少秒或毫秒完成 |
| animation-timing-function | 设置动画将如何完成一个周期,完成动画的速率曲线 - ease 先快后慢 linear 匀速直线 |
| animation-delay | 设置动画在启动前的延迟间隔 |
| animation-iteration-count | 定义动画的播放次数 - infinite 无限次 |
| animation-direction | 指定是否应该轮流反向播放动画 - reverse 反向 |
| animation-fill-mode | 规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式。 |
| animation-play-state | 指定动画是否正在运行或已暂停 running - 运行中 paused - 暂停 |
> 拓展问题:
> css中哪些属性可以设置过渡?
> 所有可以连续的值都可以过渡,例如:宽高、颜色、透明度都可以过渡,display不能过渡
## 生命周期钩子函数
生命周期钩子函数分4大部分 创建、挂载、更新、销毁
细分为8个创建前、创建后、挂载前、挂载后、更新前、更新后、销毁前、销毁后
* beforeCreate - 创建前,获取不到数据
* created - 创建后,可以获取到数据,创建指的不是实例的创建,创建指的是初始化数据和事件
* beforeMount - 挂载前,获取不到DOM
* **mounted** - 挂载后,可以获取到DOM元素
这个钩子最常用,我们一般会把网络请求 和 一些异步操作 放在这个钩子中
* beforeUpdate - 更新前,可以获取到数据,获取不到DOM
* updated - 更新后,可以获取到数据,可以获取到DOM。
更新前后指的是DOM的更新前后,不是数据的更新前后,只有当数据发生变化之后才会触发更新前和更新后钩子
* beforeDestroy - 销毁前
* destroyed - 销毁后
通过 v-if 隐藏的时候可以触发销毁(写在组件上,后续说)
手动触发销毁 `vm.$destroy()`
```html
```

## 自定义过滤器
* 自定义过滤器
是用来格式化文本使用的
* 在哪用?
在 插值语法 和 v-bind 指令中使用
* 怎么玩(步骤):
1. 定义过滤器
2. 使用 | 来使用过滤器
* 需求:
对所有要显示的数值加上10(局部注册)
对所有要显示的数值减上10(全局注册)
> 全局注册 和 局部注册
> 全局注册相当于在任何地方都可以用
> 局部注册只在当前实例中(模板中)使用
```html
{{ n }}
{{ n + 10 }}
{{ n | addTen }}
{{ n | subTen }}
```
### 自定义过滤器练习
需求:展示时间
格式化时间戳 -> 2022-09-04 15:17:45
日期 -> 2022-09-04
时间 -> 15:17:45
```html
{{ newDate }}
{{ newDate | dateTimeFormat }}
{{ newDate | dateFormat }}
{{ newDate | timeFormat }}
```
## 自定义指令
* 自定义指令
我们自己去定义指令,去书写指令的逻辑
* 需求:
自定义指令 - 将所有字符转成大写(局部注册)
自定义指令 - 将所有字符转成小写(全局注册)
```html
```
### 自定义指令练习
需求: 点击按钮之后2秒内不能再次点击
```html
按钮
```
# day04
## 自定义插件
1. 定义插件: 写一个对象,给这个对象必须暴露一个 install 方法
```js
(function( window ) {
// 使用立即执行函数为了防止插件中的一些变量受到污染,
// 例如说定义的abc
const abc = ''; // 有可能外部也用到abc这个变量会污染这个变量,模块化一下
// 声明一个对象,这个对象就是个插件
const MyPlugin = {}
window.MyPlugin = MyPlugin; // 全局可以访问到
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
console.log('myGlobalMethod')
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
// 逻辑...
console.log('插件自定义的指令');
}
})
// 3. 注入组件选项
// Vue.mixin({
// created: function () {
// // 逻辑...
// }
// })
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
console.log('实例方法 $myMethod 调用')
}
}
})( window );
```
2. 使用插件: Vue.use(插件名) --> 使用插件本质上就是调用了插件的 install 方法
```html
```
## 组件
什么是组件?
由html、css、js组成的代码块,组件的目的为了复用
在vue中组件分为 单文件组件 和 非单文件组件
* 非单文件组件 一个文件不是一个组件
* 单文件组件 一个文件就是一个组件
组件的步骤:
1. 定义组件
2. 注册组件
3. 使用组件
### 自定义组件-非单文件组件
1. 定义组件
Vue.extend() 定义组件,里面需要传入一个配置项,这个配置项和 new Vue()中传入的配置一摸一样,唯独没有el这个配置项
Vue.extend()这个函数返回一个函数,为什么 ? 这个函数是在页面中使用组件的时候 new 的
```js
const vueComponent = Vue.extend({
// 注意: 组件中data配置项,必须使用函数形式
// data: {
// n: 8
// },
data() {
return {
n: 7
}
},
// template 配置像来配置组件的html
// 注意: 模板中必须有一个根节点(根标签),这是规定
template: `
`
})
```
template 配置像来配置组件的html
注意: 模板中必须有一个根节点(根标签),这是规定
> 注意: 组件中data配置项,必须使用函数形式,为什么?
> 如果使用对象形式的话,页面中有多个组件实例的时候,会公用一套数据
2. 注册组件
Vue.component() 用来创建组件,两个参数
参数一: 组件名
参数二: Vue.extend()返回的函数
```js
Vue.component('mybutton', vueComponent);
```
3. 使用组件
页面上在使用这个组件,本质上实在 new 一个组件实例,需要的是一个构造函数
```html
```
---
以上写法是分开步骤写的,我们可以把步骤1和步骤2合并到一起,Vue.component()的参数二直接放配置项
```js
// 这个是简写,合并了之前的步骤1和步骤2,定义并注册了
Vue.component('mybutton', {
data() {
return {
n: 7
}
},
// template 配置像来配置组件的html
// 注意: 模板中必须有一个根节点(根标签),这是规定
template: `
`
})
```
---
以上写法是全局注册,组件除了全局注册以外还可以进行局部注册
```html
```
### 自定义组件-单文件组件
单文件组件顾名思义,一个文件就是一个组件,在这个我们可以创建一个 `App.vue` 文件作为单独的一个组件,这个组件作为整个页面的根组件来使用
```vue
```
同时在 `App.vue` 组件中引入了 `mybutton.vue` 组件,除了根组件以外,一般情况下我们把组件放在 `components` 这个文件夹下
`components/mybutton.vue`
```vue
```
然后将 `App.vue` 组件引入到 `index.html` 文件中进行渲染即可(两种写法)
* 第一种写法 - 注册 App 组件在模板中直接写App组件
```html
```
* 第二种写法 - 写 template 配置项,在template配置项中写 `
` 在模板中就不用再写 App 组件了
```html
```
注意: 此时是不能运行的,为什么?
1. script标签不认识ES6语法 - 给script标签加 type="module" 可以让script标签认识ES6语法
2. 不认识.vue文件 - .vue文件无法被解析,所以需要上脚手架工具,把.vue文件转成浏览器可以识别的
> 关于浏览器解析ES6语法拓展
>
> `index.html`
>
> ```html
>
> ```
>
> `99.js`
>
> ```js
> export default {
> a: 100,
> fn() {
> console.log('i love you')
> }
> }
> ```
## 脚手架的安装 和 目录结构(参考全家桶)
一、使用脚手架工具 `vue-cli` 构建项目,`vue-cli` 本质上还是使用的 `webpack` 在构建项目(脚手架工具目前最新版本是5,之前的一些老项目还有2/3/4版本)
创建项目:
1. 创建脚手架5/4/3的vue项目, 并运行
```shell
npm install -g @vue/cli 安装脚手架5/4/3的版本(目前是5版本)
vue create vue-demo 使用安装的脚手架创建一个新的vue项目
npm run serve 运行创建的项目命令
```
2. 创建脚手架2的vue项目
```shell
npm install -g @vue/cli-init
vue init webpack vue-demo
npm run dev
```
两种创建方式的差异(了解):
1. webpack配置
(1) 2脚手架: 配置是暴露的, 我们可以直接在里面修改配置
(2) 4/3脚手架: 配置是包装隐藏了, 需要通过脚手架扩展的vue.config.js来配置
(3) 5脚手架: vue.config.js直接生成了
2. 运行启动命令
(1) 2: npm run dev
(2) 3/4/5: npm run serve
> 目录介绍
>
> vue.config.js 是vue留给我们配置webpack的一个通道,我们自己配置webpack的内容写在vue.config.js文件中
>
> README.md 当前项目的说明文档(程序员自己写,这个文件一般会在上传到 git 远端后,在远端直接渲染出来)
>
> package.json 包的信息,包括依赖,启动项目的指令都在这个文件中
>
> package-lock.json 依赖的依赖信息
>
> jsconfig.json 配置js的
>
> babel.config.js 配置 babel 内容
>
> .gitignore 上传git的忽略文件
>
> src 文件夹是我们要写代码的地方
>
> src/assets 是静态资源
>
> src/components 放组件
>
> src/App.vue 根组件
>
> src/main.js 入口文件
>
> public 静态资源
>
> 注意:src/assets 和 public 都是静态资源,但是有区别,assets中的内容会被webpack处理, public 中的内容不会被webpack处理
二、eslint的禁用
1. 局部禁用某个错误提示 - 单个文件禁用ESlint的
```js
/* eslint-disable no-unused-vars */
```
2. package.json 当中找到 eslintConfig 项,全局配置禁用某些错误提示
```json
"rules": {
"no-unused-vars":"off", // off关闭当前规则
"no-unused-vars":0, // 和off一样
"no-unused-vars":1, // 规则警告
"no-unused-vars":2, // 规则报错
}
```
3. 开发阶段直接关闭eslint的提示功能
`vue.config.js`
```js
// 这个文件是脚手架给的一个通道,这个文件当中配置的webpack配置项最终都会合并到真正的webpack.config.js当中
module.exports = {
// 写自己想要配置的东西去覆盖系统自带的
// 关闭ESLint的规则
lintOnSave: false
}
```
> ESLint规则:
> http://eslint.cn/docs/rules/
>
> https://eslint.nodejs.cn/docs/latest/rules/
>
> vscode中vue文件高亮插件: vetur
## 组件模板解析 - Vue渲染两种方式
1. render:h => h(App)
2. components注册组件,template解析,但是vue导入需要导入带解析器的版本
> 为什么? 不带模板编译器的体积小


## git的基本使用6大步
先有本地再有远程
1. 创建本地
2. 创建远程
3. 关联本地和远程
4. 本地修改推向远程
5. 远程修改拉向本地
先有远程再有本地
6. 如果来的一个新员工,那么此时项目在远程已经存在,这个人只需要clone
## 验证上午写的单文件组件
## 组件间传参 - props
### props传参
父子组件之间的传参使用props
1. 在子组件标签上,使用 v-bind 绑定属性进行传参
2. 在子组件中,配置props来接收父组件传过来的参数
在接收数据的时候总共有三种形式
1. 数组形式
```js
props: ['qwer'],
```
将绑定在组件标签上的属性名,直接在数组接收一下即可
2. 对象形式
```js
props: {
qwer: String,
},
```
接收的qwer是绑定的属性名, String规定了接收的数据类型
3. 配置对象的形式
```js
props: {
qwer: {
required: true, // 必须传,不传报错
type: String, // 限制类型
default: '我爱你,杨幂' // 不传该属性的时候默认值,与required互斥
}
},
```
### 单项数据流向
不能直接修改props传过来的数据,使用props父组件给子组件传参的时候,遵循单向数据流规则
单向数据流规则: 数据是单行向下传递的,不能够子组件修改父组件传过来的数据。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
当使用props传递对象的时候,子组件可以去修改对象中的属性,注意:这是不被允许的
虽然没有问题,但是违反了 单向数据流的规则,我们不要这么做,这样会把数据的流向改的混乱,不利于理解
举个例子:
就像过马路,遵循红绿灯,传对象能修改对象属性,相当于半夜路上没车,你闯红灯
总结:
props传参分为两大类
1. 非函数数据 - 让子组件展示使用
非函数数据又分为:
* 基本数据类型 - 子组件不能改
* 引用数据类型 - 子组件不能改地址,可以改属性(不建议,违反单向数据流)
2. 函数数据 - 让子组件调用,修改父组件的数据
# day05
## todoList案例
1. 静态页面 - 静态组件拆分
拿静态页面过来拆分组件即可,需要拆html、css、js
2. 数据初始化展示
数据在哪? - 放在所有组件的公用的父组件当中
数据长什么样子?
```js
todos: [
{ id: 1001, content: '抽烟', isSel: false },
{ id: 1002, content: '喝酒', isSel: true },
{ id: 1003, content: '烫头', isSel: false },
]
```
1. main组件中每个item的展示
需要去遍历todos,循环展示每个Item组件
2. footer组件中,总个数 和 选中个数的展示
需要todos数据的,长度是总个数,选中个数需要计算
```js
// 选中个数
computed: {
selCount() {
return this.todos.reduce((prev, item) => prev + item.isSel, 0)
}
}
```
3. 交互
1. Header组件中输入内容,创建一条数据,添加到todos当中
> 注意:
>
> 1. 输入重复的内容,拦截住,给提示,不能让用户输入重复内容
>
> 需要拿到todos这个数据去判断已有数据的content和输入内容 keyword 不能重复
>
> ```js
> // 1. 使用some - 判断数组中有一个和我们输入的内容相容,返回true
> const isRepate = this.todos.some((item, index, currentArr) => {
> return item.content === this.keyword.trim()
> })
> if (isRepate) {
> alert('输入的内容有重复,请重新输入')
> this.keyword = ''
> return
> }
> // 2. 使用map + includes
> // this.todos.map(item => item.content) --> ['抽烟', '喝酒', '烫头', '打豆豆']
> if (this.todos.map(item => item.content).includes(this.keyword.trim())) {
> alert('输入的内容有重复,请重新输入')
> this.keyword = ''
> return
> }
> ```
>
> 2. 当输入添加完数据的时候,需要清空keyword
> 3. 添加数据的时候需要调用父组件传过来的函数,让父组件App修改todos数据
2. 每个Item的选中
点击每个Item的checkbox,拿到点击的哪一个,拿到索引(索引从main组件循环的地方传进来),调用App传过来的函数,把索引传 过去,让App组件去修改数据的选中状态
> 注意:函数是一层一层传下来的,从App传到Main不动,直接继续往Item传
3. 删除按钮
鼠标移入、移出显示隐藏
先把button按钮的隐藏样式去除掉,使用一个变量来控制button的显示和隐藏,我们使用的是v-show
这个变量在移入Item的时候变为true,移出变为false
点击删除按钮删除Item
获取到索引值(索引从main组件循环的地方传进来),调用App组件传过来的函数(一层一层往下传),告诉App组件要删除数据的索引
4. Footer组件中的全选
之前在数据展示的时候只是展示,之前使用的是计算属性的函数形式,现在这个属性有交互了,可以去设置这个值,计算属性就需要改成对象的形式,有set方法,当值发生改变的时候,可以获取到,获取到之后调用父组件传过来的方法,让父组件修改所有item的选中状态
5. 删除所有已选中的Item
调用父组件传过来的函数,让父组件删除已选中的数据
4. 数据持久化
localStorage 和 sessionStorage 区别?
localStorage 只要不删除,一直存在于浏览器当中
sessionStorage 只要关闭浏览器重新打开,里面的内容就会清空
可以存储的数据大小大约为5M
使用方式:
```js
// 设置值
localStorage.setItem(key, value)
// 获取值
localStorage.getItem(key)
// 删除值
localStorage.removeItem(key)
// 注意 - value只能存的是字符串,我们存数据的时候需要转成json字符串
```
todolist案例中如何使用的:
1. 设置值
只要页面中的数据发生变化的时候,就需要存储一下这个数据(使用的watc监听)
2. 获取值
当页面刷新的时候,直接去 localStorage 当中获取之前存的值,获取到就用之前的,获取不到设置默认的
遗留的BUG:
1. 每个Item的选中状态发生改变的时候,刷新页面不好使
使用watch监视数据的时候,需要使用 `deep: true` 深度监听
2. 当删除掉所有的item的时候,全选按钮变成了true
问题是由于数组的 every 方法,当调用这个方法的时候,数组是空数组也会返回true,我们只需要把空数组的情况单独讨论即可
# day06
## 自定义事件
* 系统事件
* 事件类型 - 有限个数 click mouseenter ...
* 触发机制 - 浏览器触发的,触发之后会给一个事件书香
* 自定义事件
* 事件类型 - 无限个数
* 触发机制 - 自己使用 `$emit` 触发,参数自己给
## todolist - 改成自定义事件
所有的交互都改成了自定义事件,只要是原来调用函数修改父组件App数据的地方都改了
1. Header添加
2. Main - Item 选中状态
3. Main - Item 删除按钮
4. Footer组件 - 全选
5. Footer组件 - 删除已选中
## 自定义事件 - 关于参数 - $event
$event
* 在系统事件当中,模板中写 $event 是事件对象
* 在自定义事件中,$event 是触发事件时候传的第一个参数
## .native 修饰符
当给组件绑定自定义事件的时候,此时加上 .native ,事件变为系统事件,绑定到组件的跟标签上
## $on、$off、$once
* $on - 获取到组件实例,绑定事件,留下回调
注意: $on 可以对相同的事件类型进行多次绑定
* $off - 解绑事件
* $off() - 当前组件实例上的事件全部解绑
* $off('事件类型') - 解绑某一个事件类型
* $off('事件类型', 回调) - 解绑某一个事件的某一个回调
* $once
绑定的事件只能触发一次,触发一次之后自动解绑
## $on 、 $emit 在哪
$on 和 $emit 在Vue的原型对象上,组件实例 vc 和 vue 实例 vm 之间的关系?
```js
VueComponet.prototype = Object.create(Vue.prototype)
```

> 终极原型链:
>
> 
>
>
> 
## 事件总线
全局事件总线本质是一个对象,必须符合两个条件
1. 所有组件都能访问
2. 必须可以使用 $on 、 $emit
步骤:
1. 安装总线
```js
new Vue({
beforeCreate() {
Vue.prototype.$bus = this; // 安装总线
}
})
```
2. 接收数据
在接收数据的组件中,找到总线,绑定事件,留下回调,接收数据
```js
this.$bus.$on('事件类型', 回调)
```
3. 发送数据
在发送数据的组件中,找到总线,触发事件,发送数据
```js
this.$bus.$emit('事件类型', 参数)
```

## PubSub
pubsub 是一个第三方的插件,用于跨组件间的通信
步骤:
1. 安装
`npm i pubsub-js -S`
2. 接收数据
```js
import PubSub from 'pubsub-js'
PubSub.subscribe('消息类型', 回调)
```
3. 发送数据
```js
import PubSub from 'pubsub-js'
PubSub.publish('消息类型', 参数)
```
> 注意: pubsub 在回调当中第一个参数永远是消息类型,从第二个参数开始才是真实传递的参数
# day07
## userAjax - 案例
github的两个测试接口:获取比较火的仓库及获取用户
接口1: https://api.github.com/search/repositories?q=v&sort=stars
接口2: https://api.github.com/search/users?q=aa
---
1. 静态组件拆分
2. 数据初始化展示 - 没有初始化需要展示的内容
3. 交互
输入内容,点击按钮,携带输入的内容发送请求获取数据,拿到数据之后展示列表
1. 获取到输入的内容
2. 点击按钮,携带上收集到的输入内容,发送请求
发送请求? 请求放在哪个组件好?
请求放到Main组件当中,header组件在点击按钮的时候只提供一个搜索的关键词
* 把输入的内容点击按钮给了Main组件(总线传参)
* 拿到keyword数据发请求
发请求,拿数据,展示列表
3. 需要完善交互 - 状态切换
第一进入页面 - 展示欢迎页
在发送请求的过程中 - 展示 '正在加载'
发送完请求的时候,获取到数据展示
获取不多或者报错给提示
### vue-resource 使用(了解)
使用vue-resource发送请求,但是现在用的不多,了解就好
插件使用步骤:
1. 安装
`npm install vue-resource`
2. 注册使用插件
```js
import Vue from 'vue'
import VueResource from 'vue-resource' // 插件,提供了intasll方法
Vue.use(VueResource) // 本质上是在调用插件的install方法
```
3. 代码中书写
`this.$http.get()`
> 使用方式类似axios,失败的提示信息略有不同
## 跨域
* 什么是跨域?
违反了同源策略的叫跨域
* 什么是同源策略
协议、域名、端口号相同,就是同源
* 注意: 跨域会发生在哪里?
跨域只会发生在浏览器端,服务端是不会跨域
浏览器发请求的时候跨域,浏览器会发什么请求
* 普通请求 - a标签、form表单
* ajax请求 - 为了局部刷新出现的技术,当前页面不改变,发送网络请求,拿到数据,修改当前页面部分内容
只有ajax请求会发生跨域
为什么请求 github 的api发生了跨域,还能使用?
因为github的服务器处理过来,允许这个接口发生跨域
自己造一个跨域,看看跨域如何诞生
```js
const express = require('express');
const app = express();
app.get('/userinfo', (request, response) => {
// 拓展
// 后端解决跨域,设置CORS头
// response.setHeader('Access-Control-Allow-Origin', '*');
const userinfo = {
username: '尼古拉斯·赵四',
age: 44,
intro: '大米饭前吃还是饭后吃'
}
response.send(userinfo)
})
app.listen(3000, () => {
console.log('server runing... port 3000');
});
```
```js
async getUserInfo() {
// 跨域
// let res = await axios.get(`http://127.0.0.1:3000/userinfo`)
// webpack-dev-serve 代理解决跨域
let res = await axios.get(`/api/userinfo`)
}
```
解决跨域
```js
devServer: {
// 看门狗
proxy: {
// 标识
'/api': {
// 带有 /api 这个标识的就放行,进行转发
target: 'http://127.0.0.1:3000',
// 路径重写 - 因为接口的路径中没有/api,而/api是标识,在路径中需要去掉,所以要重写
pathRewrite: { '^/api': '' }
}
}
}
```

## Vuex
1. 状态管理是什么:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,是一个官方插件。
它采用集中式存储管理应用的所有组件的状态(数据),并以相应的规则保证状态以一种可预测的方式发生变化。
我们也可以认为它也是一种组件间通信的方式,并且适用于任意组件
2. 理解:对vue应用中多个组件的共享状态(数据)进行集中式的管理(读/写)
3. 为什么要有这个(问题):
* **多个视图依赖于同一状态(数据)**
* 来自不同视图的行为需要变更同一状态
* 以前的解决办法
a. 将数据以及操作数据的行为都定义在父组件
b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
* vuex就是用来解决这个问题的
4. 什么时候用:
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
也就是说应用简单(组件比较少)就不需要使用(但是可以),如果应用复杂,使用就会带来很大的便捷
### 使用步骤:
1. 安装
`npm i vuex@3 -S`
2. 使用
```js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
```
3. 创建并暴露一个 store
```js
export default new Vuex.Store()
```
4. 创建vm实例的时候,关联store(目的是为了可以使用 this.$store)
### 核心概念
* state -> 放数据
* mutations -> 修改 state 中的数据,不能有if、for、异步
* actions -> 调用mutations,可以有if、for、异步,并且是vue和vuex之间桥梁
* getters -> 像vue中计算属性的get方法,计算state中没有的数据
### 调用方式
调用actions
`this.$store.dispatch('actions')`
调用mutations
```js
actions: {
getData({ commit }) {
commit('mutaions')
}
}
```
mutations修改数据
```js
mutations: {
add(state, res) { // res 是传过来的参数
state.data = res;
}
}
```
> 注意: actions 和 mutaions 调用的传参只能传一个
### userAjax - Vuex版本
Main组件中所有的数据放到store中,通过修改store中的数据进行页面显示
`src/store/index.js`
```js
import axios from 'axios'
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
isFirst: true, // 第一次进入页面
isLoading: false, // 是否正在加载
errMessage: "", // 保存错误信息
list: []
}
// 思路二: 通过请求的状态来设置我们的数据
const mutations = {
// 发请求之前的状态
BEFORE_REQUEST(state) {
state.isFirst = false;
state.isLoading = true;
state.errMessage = ""
},
// 发请求成功
SUCCESS(state, list) {
state.list = list
state.isLoading = false
},
// 请求失败
FAILED(state, errMsg) {
state.errMessage = errMsg
state.isLoading = false
}
}
const actions = {
async getData({ commit }, keyword) {
// 发请求之前
commit('BEFORE_REQUEST')
try {
let result = await axios.get(`https://api.github.com/search/users?q=${ keyword }`)
let res = result.data.items.map(item => ({
id: item.id,
username: item.login,
avatar_url: item.avatar_url,
html_url: item.html_url
}))
// 请求成功
commit('SUCCESS', res)
} catch (error) {
// 请求失败
commit('FAILED', error.message)
}
}
}
const getters = {}
export default new Vuex.Store({
state,
mutations,
actions,
getters,
})
```
> json-server 使用步骤
>
> 1. 安装
>
> `npm i json-server`
>
> 2. 终端使用
>
> `json-server --watch --port 8000 db.json`
>
> 此时就在8000端口启动了一个服务,需要当前路径下由 db.json 文件
# day08
### vuex辅助函数
辅助开发者的函数,因为直接使用this来写太长了,费劲,所以出了辅助函数
mapState 和 mapGetters 映射的内容映射在 computed 当中
mutaions 和 actions 映射的内容映射在 methods 当中
只是我们一般不会映射 mutaions, mutaions 是为了让 actions 去调用的,所以一般不会映射过来
#### 映射state
数组写法
`...mapState(['count'])`
对象写法
```js
...mapState({
count: 'count' // 属性名是页面中使用,属性值是state中的属性
}),
```
唯独state有这种特殊的写法,属性值是函数的形式
```js
...mapState({
count: state => state.count
}),
```
#### 映射getters
数组写法
`...mapGetters(['newCount'])`
对象写法
```js
...mapGetters({
// 属性名是组件中要使用的,属性值直接拿getters中的属性
newCount123: 'newCount'
})
```
#### 映射actions
数组写法
`...mapActions(['increment', 'decrement', 'evenIncreament', 'asyncIncreament']),`
对象写法
```js
...mapActions({
// 属性名是组件中要使用的,属性值是actions中的属性
incrementAdd: 'increment',
decrement: 'decrement'
})
```
使用映射actions传参
```js
this.incrementAdd(参数); // 这里的参数会在actoions函数的第二个参数接收到
actions: {
incrementAdd({ commit }, 参数) { // 这里的参数就是调用actions映射函数的参数传递过来的
}
}
```
### 模块化
我们目前把所有的数据直接放在 state 配置项目中,当页面中用的数据过多的时候,把所有数据都放在一个 state 中进行管理会很混乱
在store当中配置 modules ,配置每一个模块,
模块:
```js
export default {
state,
mutations,
actions,
getters
}
```
store配置
```js
import home from './modules/home'
import user from './modules/user'
export default new Vuex.Store({
modules:{
home,
user
}
})
```
只有state加了一层,其他的mutations和actions和getters使用方式和之前一样
state使用方式
```js
// 直接调用
$store.state.home.data1
// 辅助函数
...mapState({
data1: state => state.home.data1
})
```
### 命名空间
在开启模块化的时候,只是将每个模块的 state 模块了,mutations、actions、getters还是和之前一样,放在大的store中,容易产生命名冲突的问题,如何解决 mutations、actions、getters 中命名冲突问题?
开启命名空间,在模块化的基础上配置 `namespaced: true` 选项即可开启命名空间,开启命名空间之后,每个模块中的 state 和开启模块化的时候一模一样,只是 mutations、actions、getters 相当于变成了一个独立的区域,使用方式和写法也发生了改变
#### state 写法 - 和模块化下的写法一样
> 注意: 使用辅助函数映射的时候只能写对象写法,例如:
>
> ```js
> computed: {
> ...mapState({
> count: state => state.home.count,
> count: state => state.模块名.数据名
> })
> }
> ```
>
> 也支持数组写法
>
> ```js
> ...mapState("user", ["count"]),
> ...mapState("模块名", ["数据名"]),
> ```
#### actions 写法
```js
// 普通写法
$store.dispatch('模块名/actions方法名')
$store.dispacth('home/increment')
// 映射写法
methods: {
...mapActions(模块名, ['actions方法名'])
...mapActions('home', ['increment'])
}
```
> mutions写法和actions写法保持一直
#### getters 写法
```js
// 普通写法
$store.getters['模块名/getters属性名']
$store.getters['home/tenCount']
// 映射写法
computed: {
...mapGetters('模块名', ['getters属性名'])
...mapGetters('home', ['tenCount'])
}
```
图片说明:蓝色内容是命名空间,红色内容是模块化


## 插槽
什么是插槽?
父子组件之间的一种通信方式,父组件给子组件传html和css
插槽共分为 普通插槽、具名插槽、作用域插槽
### 普通插槽
#### 子组件 - Child
`
默认内容,在父祖家没有使用默认插槽的时候渲染 `
#### 父组件 - App
```html
// 简写
组件标签之间的内容会被渲染到子组件的slot标签位置
// 全写
组件标签之间的内容会被渲染到子组件的slot标签位置
```
### 具名插槽
#### 子组件 - Child
`
`
#### 父组件 - App
```html
具名插槽
```
### 作用域插槽
#### 子组件 - Child
`
`
> :userinfo="userinfo" slot标签的属性(除了name属性)会收集起来变成一个对象,供父组件写插槽内容的时候使用
#### 父组件 - App
```html
{{ scope.userinfo }}
{{ scope.intro }}
{{ scope.abc }}
{{ userinfo }}
{{ intro }}
{{ abc }}
```
> 注意:
>
> 1. v-slot指令有简写,简写成#,v-slot:qwer="scope" ----> #qwer="scope"
> 2. 当插槽单独使用的时候,template标签是可以省略的
## 动态组件渲染 - component
* 什么是动态组件
多个组件挂载到同一个组件上,通过参数动态的切换不同组件就是动态组件。
* 书写形式:
` `
这里的 componentName 可以是:
* **已注册组件的名字**
* **一个组件的配置对象**
* 内置组件:
component:是vue里面的一个内置组件。
> vue内置的组件还包括:
> transition、keep-alive ...
案例:
* 已注册的组件名:
```html
App组件_标签名
展示MyButton组件
展示MyComp组件
```
* 一个组件的配置对象
```html
App组件_组件配置项
展示MyButton组件
展示MyComp组件
```
### 动态加载某一目录下的所有组件 - 拓展
在 webpack 的依赖管理中,提供了一个方法 `require.context()` 来创建一个上下文环境(可以理解为某一目录下的所有文件组成的一个容器),使用方式如下:
```js
const context = require.context('./components', false, /\.vue$/)
// require.context()
// 功能: 创建一个上下文环境
// 参数:
// 参数一: 要检索的目标路径
// 参数二: 是否对子目录进行检索
// 参数三: 正则,用来匹配目标文件
// 返回值: 函数
// 上下文环境函数提供了一个keys方法,通过调用该方法,可以拿到当前上下文环境中所有的文件路径组成的数组
// context.keys()
context.keys().forEach(path => {
// 上下文环境函数调用,将检索出来的文件路径传入,可以得到上下文环境中的一个文件详细内容
// 这个详细内容会作为一个js模块来解析展示
const module = context(path)
})
```
案例:
```html
App组件_组件配置项
{{
compName }} 组件
```
## 路由
* vue-router 是什么?
是vue官方的一个路由插件,专门用来实现一个SPA应用
基于vue的项目基本都会用到此库(vuex、vue-router 这两个插件应用比较广泛)
* 单页Web应用(single page web application,SPA),例如:掘金
* 整个应用只有一个完整的页面(这个完整的页面,由多个组件组成)
* 点击页面中的链接不会刷新页面, 本身也不会向服务器发普通请求(a、form表单)
* 当点击路由链接时, 只会做页面的局部更新(组件切换)
* 切换过来之后,数据都需要通过ajax请求获取, 并在前端异步展现
* 什么是路由?
一种映射关系,一个key-value的映射关系
路由 分为前端路由 和 后端路由
### 前端路由:
```js
{
path: '/home',
component: Home
}
```
### 后端路由
```js
app.get('/userinfo', function (req, res) {})
```
## 路由的使用步骤:
1. 安装
`npm i vue-router@3 -S`
2. 引入使用
```js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
```
3. 创建路由器并暴露
`export default new VueRouter({ ... })`
4. 在创建vm实例的实例的时候关联路由器
```js
import router from '@/router'
new Vue({
...,
router
})
```
### 一级路由拆分
1. 定义 - 创建一个.vue文件
2. 注册 - 在路由器中注册路由
```js
export default new VueRouter({
routes: [
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/',
redirect: '/home'
}
]
})
```
3. 使用
* 点击的位置 - 需要使用 router-link 标签
* 展示的位置 - router-view
### 二级路由拆分
* Message
* News
```js
{
path: '/home',
component: Home,
children: [
{
// path: '/home/message',
path: 'message', // 简写
component: Message
},
{
path: 'news', // 简写
component: News
},
{
path: '', // 不写的话匹配到的就是 /home
redirect: 'message'
}
]
},
```
### 三级路由拆分
* Message -> MsgDetail
传参:传参通过url去传的,两中传参形式,param 和 query
目标url: /home/message/msgdetail/1001?content=高圆圆
其中 1001 是消息id params传参,content=高圆圆 是消息内容 query传参
1. 首先跳转的时候要拼接出这个url
` `
2. 路由中解析参数
```js
{
path: 'msgdetail/:msgId', // params 占位
component: MsgDetail
}
```
3. 组件接参数:
```html
{{ $route.params.msgId }}
{{ $route.query.content }}
```
### router-link 的三种写法
```html
```
### 路由的props参数映射
当路由配置了 props 之后,参数会映射到组件当中,在组件中使用props接收参数即可使用
```js
{
name: 'msgdetail',
path: 'msgdetail/:msgId',
component: MsgDetail,
// 布尔值: 只能传params参数
props: true,
// 对象形式 - 可以传递额外的参数
props: {
text: '哈哈哈'
},
// 函数形式 - 都可以传
props: (route) => { // route 是当前的路由对象 $route
return {
msgId: route.params.msgId,
content: route.query.content,
text: '额外参数'
}
}
}
```
* News -> NewsDetail
声明式导航 - route-link标签
编程式导航 - $router.push()
编程式导航也是三种写法,和 route-link 中的 to 一样
* $router.back() -- 后退
* $router.push() -- 有历史记录
* $router.replace() -- 没有历史记录
匹配数据 - $route 是可以被监听到的
```js
watch: {
$route: {},
// 监听某一个对象中的某一个集体属性值,可以使用下面这种监听
"$route.params.newId": {}
}
```
### keep-alive
缓存组件不被销毁
* include - 配置的组件不会被销毁
* exclude - 配置的会被销毁
* max - 最大缓存数
写法:
```html
字符串写法
数组写法
同时支持正则,不讨论
```
#### 组件配置项 name 作用
1. 在 keep-alive 中的 include 和 exclude 中配置使用
2. 浏览器的 dev-tools 中可以搜索到组件
3. 组件注册
Vue.componet(Home.name, Home)
### 重复点击 编程式导航 报错
```js
const originPush = VueRouter.prototype.push // 存一下之前VueRouter的push方法
VueRouter.prototype.push = function (localtion) {
return originPush.call(this, localtion).catch(() => {})
}
const originReplace = VueRouter.prototype.replace // 存一下之前VueRouter的replace方法
VueRouter.prototype.replace = function (localtion) {
return originReplace.call(this, localtion).catch(() => {})
}
```
### hash 和 history 路由差别
路由有两种模式,hash模式 和 history模式(router 默认不配置该选项的时候就是 hash)
* hash 模式就是路径中带有 # 这种形式
http://localhost:8080/#/home/news
hash模式一般在开发环境使用,生产环境很少使用,几乎不用
```js
export default new VueRouter({
mode: 'hash',
...
})
```
* history 一般在上线的时候会使用
http://localhost:8080/home/news
开发的时候使用刷新样式丢掉了,样式丢掉是路径找不对的问题,如何解决
解决:
添加配置
devServer添加: historyApiFallback: true, // 任意的 404 响应都被替代为 index.html - 现在默认配置上,脚手架3版本之前是没有的
output添加: publicPath: '/', // 引入打包的文件时路径以/开头 - 现在默认配置上,脚手架3版本之前是没有的
修改
index.html中引入的css由 ./ 相对路径变成 / 绝对路径
```js
export default new VueRouter({
mode: 'history',
...
})
```
> 注意:history 模式在上线的时候需要特殊处理,到时候细说
### $set
当初始化的data数据中没有某一条数据的时候,而在页面交互的时候,需要添加一个新的数据,让新的数据具有响应式,用 `$set` (本质上 $set 就是给数据走了一遍数据劫持,让数据具有了响应式)
```js
this.$set(this.userinfo, 'intro', '吃饺子能不能蘸醋')
// 功能: 添加响应式数据
// 参数:
// 参数一: 想要添加数据的对象
// 参数二: 想要添加的属性
// 参数三: 想要添加属性的值
// 返回值: 想要添加属性的值
```
### scoped
作用:将样式限制在当前组件和子组件的根标签上
做了什么事?
1. 把当前组件的所有的css样式都加了 data-v-xxx 这个属性
xxx是hash值,是唯一的,整个项目都是唯一的,作为了标签的属性
例如:
```css
h2[data-v-xxx] {
color: red
}
```
2. 将当前组件的html标签和子组件的根标签加了 data-v-xxx 这个属性
```css
h2[data-v-xxx] {
color: red
}
// 只有满足h2标签和data-v-xxx的才会采用 color: red 这个样式
// 而拥有data-v-xxx这个属性的只在当前组件和子组件的根标签
```


---
拓展内容:
## Render函数
介绍
```js
new Vue({
render: h => h(App)
}).$mount('#app')
```
在main.js中我们见到的render配置项,配置了一个函数,这个函数叫做render函数
render函数中有一个参数 h, 这个h函数还有一个名字: createElement, 调用这个h函数生成了虚拟DOM,并且这个函数的返回值作为了render函数的返回值
注意: 在.vue文件中的所有内容,都会最终转化成render函数,而render函数比模板写法更接近编译器
也就是说:
我们写的组件编译过程如下
`.vue文件的内容 -> 编译成render函数 -> 编译出VNode(虚拟DOM) -> 编译出真实DOM`
而我们如果使用 render 函数渲染的话,整个编译过程将变成
`render函数 -> 编译出VNode(虚拟DOM) -> 编译出真实DOM`
这样将省略一个步骤,效率更高,这也解释了我们为什么要慢慢学习render函数
为什么说事慢慢学习render函数(同问题: 为什么尤雨溪设计vue的时候,不摒弃.vue文件,直接使用render函数呢)?
因为render函数对于初学者而言并不友好,在没有深入理解过vue的设计思路和模式的时候,直接让开发者上手render函数,对于vue框架的推广会造成很大阻力
> 其实对于绝大多数场景来说,书写 template 方式都可以得到满足,且性能不错,但是对于某一些特殊场景而言render函数更合适(后续再说)
### render 用法
render函数中可以写逻辑,render的返回值是 `createElement` 函数的返回值,所以我们重点研究 `createElement` 函数
```
createElement:
第一个参数是标签名类型必须是 String 或 组件配置项
第二个是属性值 我们后面来讲,类型是Object
第三个是子级虚拟节点 (VNodes) 可以是 String|Array
```
#### createElement 参数二具体说明
```js
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM property
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
```
### Render Demo
1. render基本使用
```js
export default {
render(createElement) {
// 我爱你
return createElement('div', {}, '我爱你')
//
// 我爱你
// 高圆圆
//
return createElement('div', {}, [
createElement('span', {}, '我爱你'),
createElement('span', {}, '高圆圆'),
])
}
}
```
2. v-html 和 v-text
直接渲染DOM相关内容
```js
export default {
data() {
return {
msg: '我爱你',
message: '我爱你 ',
}
},
render(createElement) {
return createElement('div', {
domProps: {
innerText: this.msg,
innerHTML: this.message
}
})
}
}
```
3. v-bind:class
```js
export default {
render(createElement) {
return createElement('div', {
// class: 'bgRed',
// class: ['bgRed', 'f30'],
class: {
bgRed: true, // bgRed bgGreen bgBlue
f30: this.isF30 // 布尔值
}
}, '哈哈')
}
}
```
4. 事件
```js
export default {
render(createElement) {
return createElement('button', {
on: {
click: (e) => {
console.log(e, this)
}
}
}, '点击我试试')
}
}
```
5. 需求: 点击按钮,切换div显示(事件 和 v-if)
```js
export default {
data() {
return {
isShow: false
}
},
render(createElement) {
if (this.isShow) {
return createElement('div', {}, [
createElement('button', {
on: {
click: (e) => {
this.isShow = !this.isShow
}
}
}, '切换'),
createElement('div', {
class: {
bgRed: true,
f30: this.isF30
}
}, '哈哈')
])
} else {
return createElement('div', {}, [
createElement('button', {
on: {
click: (e) => {
this.isShow = !this.isShow
}
}
}, '切换')
])
}
}
}
```
6. v-for展示li列表
```js
export default {
data() {
return {
list: [
{ id: 10100, content: '抽烟' },
{ id: 10200, content: '喝酒' },
{ id: 10300, content: '烫头' },
],
}
},
render(createElement) {
return createElement('ul', {}, [
(this.list.map(item => createElement('li', {}, item.content)))
])
}
}
```
7. v-model
```js
export default {
data() {
return {
keyword: ''
}
},
render(createElement) {
return createElement('input', {
attrs: {
value: this.keyword
},
on: {
input: e => {
this.keyword = e.target.value
}
}
})
}
}
```
8. v-show
```js
export default {
render(createElement) {
data() {
return {
isShow: false
}
},
return createElement('div', {}, [
createElement('button', {
on: {
click: (e) => {
this.isShow = !this.isShow
}
}
}, '切换'),
createElement('div', {
directives: [
{
name: 'show',
value: this.isShow
}
]
}, '哈哈')
])
}
}
```
9. 自定义指令
```js
export default {
data() {
return {
text: 'I Love You',
}
},
directives: {
upper(el, binding) {
el.innerHTML = binding.value.toUpperCase()
}
},
render(createElement) {
return createElement('div', {
directives: [
{
name: 'upper',
value: this.text
}
]
})
}
}
```
9. 自定义过滤器
> this.$options 可以拿到当前的配置项
```js
export default {
filters: {
addTen(arg) {
return arg + 10
}
},
render(createElement) {
return createElement('div', {}, this.$options.filters.addTen(this.num))
}
}
```
10. ref
```js
export default {
mounted() {
console.log(this.$refs.myRef)
},
render(createElement) {
return createElement('div', {
ref: 'myRef'
})
}
}
```
11. 组件 props 传参
```js
// 父组件
import MyComp from '@/components/MyComp'
export default {
render(createElement) {
return createElement(MyComp, {
props: {
title: this.msg,
content: this.text
}
})
}
}
```
```js
// MyComp子组件
export default {
props: {
title: String,
content: String
},
render(createElement) {
return createElement("div", {}, [
createElement('h5', {}, this.title),
createElement('div', {}, this.content),
])
}
}
```
12. 组件自定义事件(点击按钮修改父组件传递的数据)
```js
// 父组件
import MyComp from '@/components/MyComp'
export default {
render(createElement) {
return createElement(MyComp, {
props: {
title: this.msg,
content: this.text
},
on: {
changeMsg: (text) => {
this.msg += text
}
},
})
}
}
```
```js
// MyComp子组件
export default {
props: {
title: String,
content: String
},
render(createElement) {
return createElement("div", {}, [
createElement('button', {
on: {
click: (e) => {
this.$emit('changeMsg', '燕子')
}
}
}, '修改父组件传过来的title'),
createElement('h5', {}, this.title),
createElement('div', {}, this.content),
])
}
}
```
13. 插槽
##### 默认插槽
```js
// 父组件
import MyComp from '@/components/MyComp'
export default {
render(createElement) {
return createElement(MyComp, {}, [
createElement('div', {}, '哈哈')
])
}
}
```
```js
// MyComp子组件
export default {
render(createElement) {
return createElement("div", {}, [
createElement('h5', {}, '测试标题'),
this.$slot.default
])
}
}
```
##### 具名插槽
```js
// 父组件
import MyComp from '@/components/MyComp'
export default {
render(createElement) {
return createElement(MyComp, {}, [
createElement('div', {}, '哈哈'),
createElement('div', {
slot: 'qwer',
}, '呵呵'),
])
}
}
```
```js
// MyComp子组件
export default {
render(createElement) {
return createElement("div", {}, [
createElement('button', {}, '按钮'),
createElement('h5', {}, '测试标题'),
this.$scopedSlots.default(),
createElement('div', {}, '测试文本'),
this.$scopedSlots.qwer()
])
}
}
```
##### 作用域插槽
```js
// 父组件
import MyComp from '@/components/MyComp'
export default {
data() {
return {
msg: '我爱你',
message: '我爱你 ',
}
},
render(createElement) {
return createElement(MyComp, {
props: {
title: this.msg,
content: this.text
},
scopedSlots: {
qwer: props => createElement('em', {}, props.intro)
}
}, [
createElement('strong', {}, 'so easy!!')
])
}
}
```
```js
// MyComp子组件
export default {
props: {
title: String,
content: String
},
data() {
return {
userinfo: {
username: '贾玲',
age: 21
},
intro: '喜剧演员'
}
},
render(createElement) {
return createElement("div", {}, [
createElement('button', {}, '按钮'),
createElement('h5', {}, this.title),
this.$scopedSlots.default(),
createElement('div', {}, this.content),
this.$scopedSlots.qwer({
intro: this.intro
})
])
}
}
```
### render案例
使用render函数写slot_page案例(自己下去写)
### Render函数 - JSX
地址: https://v2.cn.vuejs.org/v2/guide/render-function.html#JSX
1. 安装bable的包
```
npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
```
2. 修改 babel.config.js 文件
```js
module.exports = {
presets: [
// '@vue/cli-plugin-babel/preset' // cli默认的插件
'@vue/babel-preset-jsx' // 手动安装支持jsx插件
]
}
```
3. render函数中使用 jsx 即可
```jsx
export default {
data() {
return {
count: 2
}
},
methods: {
clickHandler(e, num) {
console.log(e)
console.log(num)
this.count++
}
},
render() {
return
我爱你,高圆圆
爱了 { this.count } 次
直接修改count
this.clickHandler(e, 99) }>传参
}
}
```
----------------
##### 关于快捷键
```js
选中多行单词
alt + 双击鼠标左键
选中多行
alt + 单击鼠标左键
按下滚轮键上下拖拽
选中代码上下移动
alt + up
alt + down
代码的缩进
往后退 ---- tab键
往前推 ---- shift + tab
Live server 快速启动
alt + l alt + o (alt不松手)
vscode 启动终端快捷键
ctrl + `
vscode 侧边栏快捷键
ctrl + b
```