# componentvue-0310
**Repository Path**: newsegmentfault/componentvue-0310
## Basic Information
- **Project Name**: componentvue-0310
- **Description**: componentvue - 脚手架工具创建项目基础内容部分
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-07-22
- **Last Updated**: 2022-08-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# componentvue
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
## day05
### 关于项目启动
1. 首先应该down下代码来
2. 安装依赖包,进入到项目路径下执行 `npm install`
3. 跑起来项目`npm run serve`
npm 在运行项目的时候,所有的指令都是 `npm run ` 开头的
而我们之前使用 `npm start` 是一个缩写,全写是 `npm run start`
只有`npm run start`有缩写,可以把 `run`省略
在工作当中,启动项目,打包项目这些指令可能不一样,怎么看呢?
看`package.json`怎么配置的
```json
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
}
```
这里我们应该去执行 `npm run serve` 启动项目,本质上实在命令行执行的`vue-cli-service serve`
### props传参
props传参适用场景:父组件给子组件传参使用props传参
传参的时候可以传任意数据类型
1. 将父组件的数据使用 v-bind 绑定到子组件的标签上
属性名是子组件中要接收的数据,等号后面双引号之间的数据是当前组件的数据
```vue
```
2. 在子组件中去接收一些数据,接收绑定在子组件上的数据
使用props接收父组件传过来的数据
接收数据的形式有三种
3. 接收形式
* 数组形式
```js
export default {
props: ['qwer', 'parentData', 'foo']
}
```
* 对象接收
```js
export default {
props: {
qwer: String, // 接收的数据规定类型
parentData: Array,
foo: Function
},
}
```
* 配置对象接收参数
```js
export default {
props: {
qwer: {
type: String, // 规定接收的数据类型
required: true, // 规定数据必传
// default: '世界和平' // 如果不传的情况下的默认值
// required 和 default 是互斥的
// 必传的情况下不需要默认值
// 只有在不传参的情况下,default才有意义
},
parentData: {
type: Array,
// 设置默认值的时候,遇到Object和Array必须使用工厂函数返回
// 为什么?
// 为了让每个组件实例中,不传参的情况下当前的这个属性是独立
// 地址不会互相影响
default: function () {
return []
},
},
foo: {
type: Function,
required: true
}
},
}
```
总结:
props传参其实是分两种类型在传
1. 非函数数据类型
这种数据就是给子组件使用/渲染用的
2. 函数数据类型
为了让子组件调用函数,来修改父组件数据的
在这里,我们一般情况下不传 [函数数据] ,而传方法(methods中定义的方法), 为什么?
因为数据在代码运行的过程中可能会发生改变,把函数数据改变之后就不能调用了
### todolist 案例
写页面的步骤:
1. 拆分静态组件
2. 初始化数据展示
3. 动态交互
参照案例中注释
## day06
### 拓展内容: forEach 和 map 和 filter 等数组方法的实现
```js
var arr = [3, 7, 5, 9];
arr.forEach((item, index, currentArr) => {})
Array.prototype.customForEach = function (callback) {
// 这里的this是谁?谁调用this就是谁,这里只能数组实例调用,所以指向的就是实例数组
for (var i = 0 ; i < this.length; i++) {
callback(this[i], i, this);
}
}
arr.customForEach((item, index, currentArr) => {})
Array.prototype.customMap = function (callback) {
let arr = [];
for (var i = 0; i < this.length; i++) {
let result = callback(this[i], i, this);
arr.push(result);
}
return arr;
}
```
### 组件的命名规范
组件有两种命名规范
1. 中划线的形式
```js
// 参数一: 组件名
// 参数二:
// 可以是Vue.extend()返回的组件构造函数
// 可以是组件的配置对象(简写)
Vue.component('my-button', mybutton);
```
使用的时候,注册的时候使用中划线的形式,那么在使用的时候只能使用中华线的形式
```html
```
2. 大驼峰
```js
Vue.component('MyButton', mybutton);
```
使用的时候,使用大驼峰注册的组件支持两种形式使用
1.支持中划线的形式使用
2.支持大驼峰形式使用
```html
```
> 注意: 我们在没有关闭ESLint的时候,会提示组件名需要用两个单词生成,这个规则是可以关闭的
### 自定义事件
与系统内置事件对比着去讲的:
##### 系统内置事件 - 系统内置事件是给DOM元素绑定
```html
```
1.click,mouseenter,mouseleave,keydown、keyup等是系统内置事件,个数是有限的
2.DOM元素的事件是由系统触发的事件,调用的回调
##### 自定义事件 - 给组件绑定的
```html
```
1.xxx是事件类型,自己定义的名称,想怎么定义就怎么定义,事件类型是无限个
2.需要自己手动的触发事件调用回调 $emit()触发
> 注意: 自定义事件也是父子组件间通信的一种方式,是子组件给父组件传参使用的
如何做?
```html
// 父组件
// 子组件
export default {
name: "MyButton",
data() {
return {
count: 0
}
},
...
methods: {
tiggerXXX() {
this.$emit('xxx', params); // params泛指,指的参数
}
}
}
```
### $on、$off、$once
#### $on
用来绑定自定义事件的,怎么帮定? 获取组件实例时候,调用$on绑定
```js
this.$refs.footerRef // 这是组件实例
// 参数一: 事件类型
// 参数二: 回调函数
this.$refs.footerRef.$on('xxx', this.xxxHandler);
```
1. 在组件标签中绑定自定义事件,相同的事件只能绑定一次,在使用 `$on()` 绑定事件的时候,可以绑定相同的自定义事件
2. 一般在需要绑定自定义事件的组件里面,再mounted钩子中去绑定
#### $off
$off 是在解绑自定义事件,怎么解绑?获取到组件实例来进行解绑
```js
// 解绑组件实例 footerRef 上绑定的所有自定义事件
this.$ref.footerRef.$off();
// 解绑组件实例上 footerRef 上绑定的 xxx 自定义事件
this.$ref.footerRef.$off('xxx');
// 解绑组件实例上 footerRef 上绑定的 xxx 自定义事件 的 this.xxxHandler 这个回调
this.$ref.footerRef.$off('xxx', this.xxxHandler);
```
#### $once
$once() 绑定自定义事件,在事件触发一次之后,自动解绑
```js
this.$ref.footerRef.$once('xxx', this.xxxHandler);
```
### $on 和 $emit 在哪? -- vm 和 vc 的关系?

主要就是一行代码:
```js
VueComponet.prototype = Object.create(Vue.prototype)
```
> $on 和 $emit 在 Vue 构造函数的原型对象上
### 全局事件总线
全局事件总线本质是一个对象,需要满足以下条件才能作为全局事件总线:
1. 所有的组件都可以访问到
2. 可以调用 $on 和 $emit
怎么做?
1. 安装总线
2. 在需要接收数据的组件中,找到总线这个对象,使用 `$on` 绑定事件,将回调留在这个组件,便于接收参数
3. 在需要传输数据的组件中,找到总线这个对象,使用 `$emit()` 来触发事件,传递参数
```js
new Vue({
beforeCreate() {
Vue.prototype.$bus = this; // 安装总线
},
...
})
// 需要接收数据的组件绑定
this.$bus.$on('xxx', this.xxxHandler);
// 需要传参的组件
this.$bus.$emit('xxx', params); // params 泛指,指参数
```
### pubsub-js
pubsub 是第三放的插件,使用订阅、发布的模式进行跨组件传参
步骤:
1. 下载安装
2. 引入
```js
import PubSub from 'pubsub-js'
```
3. 在接收数据的组件中使用订阅的模式,将回调留在当前组件,接收参数
```js
PubSub.subscribe('changeSelPubsub', this.changeSelPubSub);
```
4. 在发送数据的组件中使用发布的模式,将参数传递出去
```js
PubSub.publish('changeSelPubsub', params); // params 泛指 指参数
```
> 注意:那 全局事件总线 与 pubsub 做对比, 在回调函数中的参数有少许区别
>
> 1. 全局事件总线在触发的回调里,只有参数
> 2. pubsub 的回调里,参数一是消息类型(事件类型),第二个 参数才是有效参数
> pubsub在vue中不常用,因为同样是跨组件间的通信,全局事件总线不需要安装额外包,而pubsub需要
### userajax 案例
写页面步骤: 1. 静态组件拆分 2.初始化数据展示 3.交互
具体内容参见 App.vue 注释
```js
1. 拆分静态组件
Header
Main
2. 初始化数据展示
3. 交互
在Header组件中输入内容,点击搜索按钮,发送网络请求,获取数据,动态展示
3.1. Header
点击搜索按钮,收集到页面input输入的内容
发送请求获取数据,然后展示.
那么发送请求的时候在Header中发请求好呢?还是在Main里面发送请求好?
在main里面发送请求好,因为发送请求之后获取到数据,需要在页面展示
如果在Header中发送请求,拿到的数据需要给Main组件传递过去
如果在Main中发送请求,只需要把header组件输入的keyword传过去即可
把 header 组件中收集到输入的文本,传给Main组件,怎么传?使用什么通信方式?
这里是跨组件之间的通信,使用 全局事件总线 最合适
全局事件总线怎么玩?
1. 安装总线
2. 在接收数据的组件中绑定事件,留下回调接收数据
3. 在发送数据的组件中触发事件,传递参数
3.2. Main
拿到数据后 发送请求
安装axios,来发送请求
npm i axios -s
发送请求的地址:
https://api.github.com/search/users?q=aa
这里q=aa是参数,aa呢是输入的内容
接收到数据之后,发现数据有很多,我们需要的数据有:
{
id: 9527,
avatar_url: '', // 头像
login: '', // 用户名
html_url: '' // 用户的主页
}
```
### 开发依赖 和 生产依赖
```js
安装包的时候可以 -s -D --save --save-dev
-S或-s 是 --save的简写,代表安装到生产依赖
-D 是 --save-dev 的简写,代表安装到开发依赖
-g 全局安装,计算机cmd任意位置都可以使用全局安装的指令
生产依赖 - package.json 中 dependencies 属性
开发依赖 - package.json 中 devDependencies 属性
举个例子:
蛋炒饭,吃过吧?
蛋炒饭可以理解成我们写的一个产品(项目),
蛋炒饭做的时候需要有其他条件才能做,需要有(炒的过程):
鸡蛋、米饭、油、盐、酱、醋、锅、炒饭的铲子、盘子、火等,有这些东西才能做出蛋炒饭
在蛋炒饭上菜的时候,就是成品,就是项目上线
项目上线需要:
鸡蛋、米饭、油、盐、酱、醋、盘子
炒菜的过程中所用到的,在上菜的时候没有用到,这些依赖安装在开发依赖
开发依赖有: 锅、火、炒饭的铲子 --- 这些都是开发依赖
而上线的时候依赖的安装在生产依赖
在项目中,例如说:
babel 就是开发依赖,因为我们的项目最终打包的时候,会打包成 html、css、js静态文件
且js是es5语法,而bable是吧ES6语法转成ES5语法的工具
再例如:
axios
上线之后也需要发送网络请求啊,所以要安装到生产依赖
注意: npm i xxx 这种安装生产依赖 - 默认安装在生产依赖的
```
## day07
### vue-resource - 把userajax案例改成使用vue-resource发请求
`vue-resource`是一个vue发送网络的官方插件,但是在外面用的不多,用的多的还是`axios`
##### 如何使用
1. 下载安装
```js
npm i vue-resource -s
```
2. 引入
```js
import VueResource from 'vue-resource'
import Vue from 'vue'
Vue.use(VueResource); // 本质调用插件的install方法
// 当引入使用之后可以在组件实例上调用到 $http() 方法
```
3. 使用
和 axios 几乎一样
```js
axios({
method: 'get',
url: 'xxx'
})
axios.get()
axios.post()
// 对比axios去使用
this.$http({
method: 'get',
url: 'xxx'
})
this.$http.get()
this.$http.post()
```
> 把userajax案例改成使用 vue-resource 发请求
### 跨域
什么是跨域?
违反同源策略叫跨域
什么是同源策略?
协议、域名(ip)、端口号相同叫同源
跨域会发生在什么情况下?
跨域只会发生在浏览器里面
服务和服务之间是不会跨域的
### 使用express搭建服务 - 制造跨域
1. 安装
```js
npm i express -s
```
2. 引入
创建一个 `server.js` 文件,用来写咱们的服务
启动服务使用的时候node启动的,node实现了CommonJS语法,没有实现ES6语法,所以在写js的时候要遵循CommonJS语法
```js
const express = require('express');
// 3. 创建服务
const app = express();
// 5. 最后写接口
app.get('/userinfo', (request, response) => {
let user = {
username: 'zhangsan',
age: 18
}
// 返回数据
response.send({
code: 10000,
data: user,
message: 'success'
})
})
// 4. 监听端口
app.listen(8888, () => {
console.log('启动了 8888 端口');
})
```
此时创建了一个服务,服务中有一个接口 `http://localhost:8888/userinfo`
现在使用Vue项目请求这个接口就会跨域,vue启动项目的地址`http://localhost:8080`,此时端口不一样,违反同源策略,造成跨域
### 后端解决跨域
```js
app.get('/userinfo', (request, response) => {
// 后端cors头解决跨域(拓展内容)
// 这样做不安全,不光你能请求到这个接口,所有的其他ip也可以跨域请求了
response.setHeader('Access-Control-Allow-Origin', '*');
...
response.send()
})
```
### 前端解决跨域

前端通过webpack配置代理来实现跨域,注意:代理应该配在webpack当中,但是我们项目中没有webpack的配置文件,留一个 `vue.config.js` 文件,这个文件是脚手架留下的一个通道,一个我们配置wenbpack的通道,在这个文件中配置webpack
webpack如何配置代理:
```js
{
devServer: {
// 配置代理
proxy: {
// /api 属性名就是标识,`/api/username`会走代理
'/api': {
// target 代理到目标服务的地址
target: 'http://localhost:8888',
// 路径重写 会把带有`/api/userinfo`中的`/api`去掉,变成`/userinfo`
pathRewrite: { '^/api': '' }
}
}
}
}
```
> 如果忘了代理怎么配置:
>
> 去webpack中文网上,找到`devServer`配置项,里面找 `proxy`,然后复制过来就行
> 拓展:
>
> 上线分为两种:
>
> 1. 打包好项目之后形成的html、css、js的包,给后端同学,这里跨不跨域和咱没关系,但是一般可能会把前端项目直接放 tomcat(java在服务器运行的一个环境),后端会把我们的文件作为静态文件,放到服务器上
>
> 2. 前端自己打包好上线
>
> 也是静态文件上线,然后用不到webpack了,webpack在开发中帮我们做了两件事,第一件事事启动开发服务(npm run serve),第二件事打包,打包好后是静态文件,静态文件是ES5语法,
>
> 和webpack就没关系了
>
> 上线的时候也需要用到代理,使用 nginx 代理 (nginx也是一个服务,在服务器(计算机)上起的一个服务)
>
>
>
> 解释下地址:
>
> 自己本机地址
>
> localhost
>
> 127.0.0.1
>
> 你自己的电脑要在公网有一个ip,只不过这个ip是中国移动给你分配的,并且这个ip隔一段事件就会变,我的公网 ip: `117.152.93.17`
### 插槽
是组件间通信的一种方式,让父组件传模板给子组件
什么情况下要想到用插槽?
当子组件中有不确定内容的时候,此时就要到了插槽,让父组件来决定子组件的html内容
插槽总共分为3种:普通插槽、具名插槽、作用域插槽
1. 普通插槽
子组件 Child:
```html
插槽的默认文本
```
这个 slot 标签就是开了一个槽,这里面的内容有父组件来决定
父组件:
```html
文本
```
父组件中使用子组件标签之间的内容 会 替换 子组件 slot 标签,注意以上父组件是简写,全写需要加template标签
父组件:
```html
文本
```
2. 具名插槽 - 具有名字的插槽
子组件:
```html
```
父组件:
```html
文本
```
使用具名插槽的时候,注意:父组件再写内容的时候需要用到 `v-slot` 指令,这里模板中 `v-slot:myslot` 中的 `myslot` 是子组件中 slot 标签的 name 属性
3. 作用域插槽
子组件:
```html
```
父组件:
```html
文本
```
作用域插槽,不一定是具名插槽,但是一定有数据,数据是子组件中 slot标签上绑定的属性,会组成一个对象,在父组件中
`v-slot="{ userinfo, intro }"`
这里的 userinfo 和 intro 直接解构的
> 总结:
>
> v-slot用法
>
> v-slot:myslot 这是具名插槽,myslot是插槽的名称
>
> v-slot:myslot="data" 这是具名插槽+作用域插槽, myslot是插槽名称、data是绑定slot标签的数据
>
> v-slot="data" 这是作用域插槽 data是数据
### 介绍 element-ui
安装
```js
npm i element-ui@2.15.6 -S
```
> 注意: 2.15.9 最新版datapick组件会有问题
>
> 这里小版本更新的版本差异不大,问题不大
使用
```js
import Vue from 'vue'
// 引入ElementUI
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI); // 本质上在调用插件的install方法
```
代码直接复制黏贴即可:
```html
默认按钮
主要按钮
```
## day08
Vuex
什么是Vuex?
Vuex是用来组组件的状态管理的,状态管理就是数据管理,对组件用到的数据进行读、写
什么情况下用Vuex?
当多个组件(视图)依赖于同一数据的时候,此时可以让vuex来帮我们管理数据
#### 使用步骤?
1. 安装
```js
npm i vuex@3 -S
```
注意: Vue2对应的Vuex是3版本,Vue3对应的是Vuex4版本,这里我们使用vuex3版本
2. 使用
```js
// 在src目录下创建一个 store/index.js 文件
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
```
3. 暴露一个创建好的仓库实例
```js
export default new Vuex.Store()
```
4. 在创建vm实例的时候,关联暴露的store实例
```js
import store from '@/store'
new Vue({
...
store
})
```
#### vuex的4个核心概念
1. state - 本质上是一个对象,里面放数据
2. mutations - 本质上也是一个对象,里面放函数,这些函数用来修改 `state` 中的数据
注意:在mutations的函数中不能有异步、if、for
3. actions - 本质上也是一个对象,里面放函数、用来调用(触发)mutations中的函数
注意:actions中的函数可以有异步、if、for,同时actions的函数作为Vuex和Vue之间沟通的桥梁
4. getters - 也是一个对象,他有点像组件中的"计算属性",根据state中的数据计算state中没有的数据
注意:getters中的函数是计算属性,且只是get方法,没有set方法,为什么?因为不允许直接修改state
```js
const state = {}
const mutations = {}
const actions = {}
const getters = {}
export default new Vuex.store({
state,
mutations,
actions,
getters
})
```
#### 修改state数据的规则(三连环):
1. 在组件中触发actions
```js
$store.dispatch('increment');
```
2. 在 actions 中触发 mutations
```js
const actions = {
increment({ commit }) {
commit('ADD'); // 触发mutations
}
}
```
3. 在mutations中修改state数据
```js
const mutations = {
ADD(state) {
state.count++; // 修改state数据
}
}
```
#### 在页面中使用vuex数据
1. 直接通过store拿state
```js
$store.state.count
// 第一种 在computed中使用
export default {
computed: {
count() {
return this.$store.state.count
}
}
}
// 第二种 直接在模板中使用
{{ $store.state.count }}
```
注意:不要把数据放到组件的data属性中,要么放computed,要么直接在模板中使用
```js
// 错误示例
export default {
data() {
return {
// 这里的数据只会在组件初始化的执行一次,当store中数据发生变化的时候,这里不会改变
// 还有,如果在后续的操作中修改了自己组件的count,store中的count并没有发生改变
count: $store.state.count,
}
}
}
```
2. 从getters中拿数据
```js
const getters = {
newCount(state) {
return state.count; // 可以对count进行处理
}
}
// 在组件中
$store.getters.newCount
```
#### 辅助函数
辅助函数 - 用来辅助我们开发者写代码的函数,让程序员少敲几个字符
1. mapState
映射store中state的数据
```js
import { mapState } from 'vuex'
export default {
computed: {
// 数组形式: 数组中成员'count'一定要和store中state的count属性名相同
...mapState(['count']),
// 对象形式
...mapState({
// 属性名是映射过来的名称,自己起,叫什么随便
// 属性值必须要state中count属性名相同
count: 'count'
// 注意: 唯一一个辅助函数中,对象形式写法属性值能写函数的
// 函数参数 state 就是 store 中 state
count: state => state.count
})
}
}
```
注意: 映射过来的数据,不能放data中
2. mapMutations - 这个不用,应为mutations是为了让actions去调用
注意: 如果强行要映射到组件中让组件调用 mutations,也能走通,把mutations的函数映射到 methods 中
3. mapActions
将actions中的函数映射到组件中,让组件调用,触发数据修改规则(三连环)去修改数据
```js
import { mapActions } from 'vuex'
export default {
methods: {
// 数组形式, 数组成员'increment'必须和actions中函数名一样
...mapActions(['increment'])
// 对象形式
// 对象属性名自己起的名字
// 对象属性值 'increment' 必须和actions中函数名一样
...mapActions({
increment: 'increment'
})
}
}
```
4. mapGetters
将getters中的"计算属性"映射到组件当中,映射到 computed 中
```js
import { mapGetters } from 'vuex'
export default {
computed: {
// 数组形式 - 数组成员'newCount'必须和store中getters下的属性一样
...mapGetters(['newCount']),
// 对象形式
...mapGetters({
// 属性名, 自己起
// 属性值, 'newCount'必须和store中getters下的属性一样
newCount: 'newCount'
}),
}
}
```
### 关于传参
传参主要涉及到两大块内容
1. this.$store.dispath()传参
```js
// 在组件中调用actions只能传一个参数
this.$store.dispath("increment", params)
// 在store中接收参数
const actions = {
// context 默认就有,像一个缩小版的'store'
// 这里的params是传过来的参数
increment(context, params) {
// 调用mutations传参, 也是只能传一个参数
context.commit('ADD', params)
}
}
const mutations = {
// 第一个参数,默认的,state是store中的state
// 第二个参数,是actions传过来的
ADD(state, params) {
state.count = params;
}
}
```
2. mapActions传参
```js
// 在组件中
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions(['increment']),
clickHander() {
// 调用store映射过来的函数,直接传参就行
this.increment(params);
}
}
}
// 在store中接收参数
const actions = {
// context 默认就有,像一个缩小版的'store'
// 这里的params是传过来的参数
increment(context, params) {
// 调用mutations传参, 也是只能传一个参数
context.commit('ADD', params)
}
}
const mutations = {
// 第一个参数,默认的,state是store中的state
// 第二个参数,是actions传过来的
ADD(state, params) {
state.count = params;
}
}
```
#### 案例: count自增案例
#### 案例: userajax
#### Vuex原理图

## day09
#### SPA
单页应用,点击页面中链接的时候不会跳转页面,只会把页面中的内容进行替换
#### 什么是路由?
路由是一种key-value映射关系,路由分为前端路由和后端路由
##### 前端路由
一个路径对应一个组件(视图)
```js
{
path: '/home',
component: Home
}
```
##### 后端路由
一个路径对应一个处理函数
```js
const express = require('express')
const app = express()
// 后端路由(接口)
app.get('/userinfo', (request, response) => {})
```
#### 如何理解路由
点击页面中链接的时候,让路径发生改变,匹配路由,改变页面中组件的渲染
### 怎么做 - router案例
#### 一级路由拆分
步骤:
* 拆分组件,准备好组件
创建一个.vue文件,将静态的html和css拆分出来即可(拆分出Home组件和About组件,这里把路由组件放到src/page目录下)
路由组件和之气普通组件都一样,都是.vue文件,都要定义、注册、使用,路由组件和普通组件的差异在注册和使用上,注册的时候之前使用组件的全局注册或局部注册,路由组件在路由器中注册,路由组件使用是通过router-view切换
* 安装路由
1. 安装
```js
npm i vue-router@3 -S
```
> 注意: 是3版本
2. 使用
```js
// src/router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter) // 调用插件的install方法
```
3. 暴露一个router对象(路由器对象)
```js
// src/router/index.js
export default new VueRouter({
routes: [] // 配置路由
})
```
4. 创建vm实例时候,关联router
```js
// src/main.js
import router from './router'
new Vue({
router
})
```
* 注册路由组件
```js
// 放在routes中
import Home from '@/page/Home'
export default new VueRouter({
// 配置路由
routes: [
{
path: '/home',
component: Home
}
]
})
```
* router-link router-view
在需要点击按钮的地方使用 ``替换原来的按钮
在需要渲染组件的地方使用`` ,相当于留一个坑位用来渲染组件
* 重定向
```js
// 放在routes中
{
path: '/',
redirect: '/home'
}
```
#### Home组件 - 二级路由
* 定义组件 - 拆分组件
拆分出来的组件,可以直接留一个 `` ,让页面一会切换
* 注册组件 - 路由中注册
```js
import Message from '../page/Message'
import News from '../page/News'
export default new VueRouter({
// 配置路由
routes: [
{
path: '/home',
component: Home,
// 注册二级路由
children: [
{
path: '/home/message',
component: Message
},
{
path: '/home/news',
component: News
},
// 重定向
{
path: '/home', // 这里的/就是为了理解成根路径,此时的路径是 /home
redirect: '/home/message'
},
]
}
]
})
```
* 使用
`` 把点击的a标签链接替换了,所有的属性和内容都不变,不要a标签的href属性,自己写 to 属性,to里面的属性写路径,这个路径要和路由中配置的路径一样
``点击要切换组件的位置,我们在拆组件的时候,就已经把这个坑位留下了
#### 二级路由简写 - 前提,必须有父路由
```js
import Message from '../page/Message'
import News from '../page/News'
{
path: '/home',
component: Home,
children: [
// 二级路由简写
// 注意: 简写的时候,path最前面不要带/,如果带/这里/会被理解成根路径
{
path: 'message', // 这里会拿着 父路由 /home 和子路由进行拼接 得到 /home/message
component: Message
},
{
path: 'news',
component: News
},
// 重定向
{
path: '',
redirect: 'message'
},
]
}
```
#### 三级路由传参
路由传参分三块
1. 点击链接的地方需要处理,传递参数
2. 点击完链接一定会过路由,需要配置路由,对参数进行处理
3. 匹配到路由后渲染对应组件,需要在对应组件中接参数
步骤:
Message -> 路由 -> MsgDetail
路径使用 params + query 的形式,进行传参
`/home/message/msgdetail/1001?content=高圆圆`
params传参就是参数是路径的一部分,这里的 `1001`是参数id,叫params传参
query是问号后面携带的参数,这里的`content=高圆圆`是参数,叫query传参
* 传
* router-link 拼接字符串(略)
* router-link 模板字符串
```html
```
* router-link 对象
1. path:
使用path来跳转,不能使用params配置项
/home/message/msgdetail?content=高圆圆
想要path跳转,不使用params参数,直接将params参数拼接到路径上即可
path: '/home/message/msgdetail/' + msg.id,
```html
{{ msg.content }}
```
2. name:
使用name属性的时候,name必须和【路由】中配置的name属性相同
```html
{{ msg.content }}
```
> 总结: path不能配params,name能配params
* 路由配置
接收params参数的时候路由需要一个占位符
```js
{
name: 'msgdetail',
// :msgId 是一个占位符,用来接收参数的
path: 'msgdetail/:msgId', // /home/message/msgdetail
component: MsgDetail,
}
```
* 接
默认可以在组件中直接使用 $route 对象接收
```html
消息的id: {{ $route.params.msgId }}
消息的内容: {{ $route.query.content }}
```
> 注意:
>
> $route 是当前路径的路由对象
>
> $router 是路由器对象
想在组件中接参数的时候少写点,可以将参数映射到组件的props属性上,需要配置路由:
```js
{
name: 'msgdetail',
// :msgId 是一个占位符,用来接收参数的
path: 'msgdetail/:msgId', // /home/message/msgdetail
component: MsgDetail,
// props 可以将参数映射组件的props属性上
// 1. 布尔值
// 设置为true的时候只能映射 params参数,不能映射query参数
// props: true,
// 2. 对象
// 设置为对象是为了添加额外的数据,params和query的参数映射不到组件中
// props: {
// text: "哈哈哈"
// }
// 3. 函数
// 函数的参数是route当前路由,函数返回一个对象,对象中的属性为映射的内容
props: (route) => {
// 返回的这个对象的所有内容都会被映射到组件里的props属性
return {
msgId: route.params.msgId,
content: route.query.content,
text: '哈哈哈'
}
}
}
```
组件中:
```html
消息的id: {{ msgId }}
消息的内容: {{ content }}
text的内容: {{ text }}
```
News -> 路由 -> NewsDetail
* 传
之前在message中传参使用的 router-link 是声明式导航
现在要点击按钮进行传参,使用 编程式导航
在跳转的时候,需要携带参数过去?
这里还是采用 params + query 的形式,id使用params传,content使用query传,路径长什么样子
/home/news/newsdetail/10001?content=俄乌冲突
使用 $router.push() 方法的时候需要携带参数
```js
// 三种写法
// 1. 字符串拼接
// this.$router.push('/home/news/newsdetail/' + newsItem.id + '?content=' + newsItem.content);
// 2. 模板字符串
// this.$router.push(`/home/news/newsdetail/${newsItem.id}?content=${newsItem.content}`);
// 3. 对象形式
this.$router.push({
// path:
// 就需要拼上 params 参数,不能单独配置params对象,需要拼接参数
// path: '/home/news/newsdetail/' + newsItem.id,
// name:
// name需要和路由中配置的name一样
name: 'newsdetail',
params: {
newsId: newsItem.id
},
query: {
content: newsItem.content
}
})
```
* 路由处理参数
params参数需要在路径有一个占位
```js
{
path: 'news',
component: News,
children: [
{
name: "newsdetail",
// :newsId 对路径中的id占位
path: 'newsdetail/:newsId',
component: NewsDetail
}
]
},
```
* 接参数
组件中可以接收到参数
```html
- 新闻的id: {{ $route.params.newsId }}
- 新闻的内容: {{ $route.query.content }}
- 新闻的类型: {{ type }}
```
#### 编程式导航 - 声明式导航
声明式导航
``
编程式导航
`$router.push()` 在跳转的时候有历史记录
`$router.replace()` 在跳转的时候没有历史记录
`$router.back()`返回上一个记录的路由,就是回退
> 注意:`$router.back()`返回的之后,会把params参数之前的Number类型变成String类型
#### $route 监听
$route 当前页面的路由信息,只要页面的url发生变化,这个$route就会变化,所以可以监听获取到路由中变化的内容
#### keep-alive
用来缓存组件使用的
当路由组件[频繁]切换的时候,每次切换回销毁上一个组件,创建下一个组件,这对于性能是不友好,可以是使用keep-alive缓存组件
```html
```
##### include、exclude、max
include 要缓存的组件
exclude 不要缓存的组件
max 最多缓存几个组件
```html
```
##### 组件name属性作用
1. 在浏览器devtools使用,可以查找组件
2. 全局注册的时候使用
`Vue.componet(Home.name, Home);`
这里的 `Home.name` 就是组件的name属性
3. 在 keep-alive 中,include、exclude 配置
## day10
### hash 和 history 模式的区别
#### hash
http://localhost:8080/#/home/message
#之前是后端路由,#之后是前端路由,一般在开发的过程中使用
```js
export default new VueRouter({
mode: 'hash',
routes: ...
})
```
#### history
http://localhost:8080/home/message
```js
export default new VueRouter({
mode: 'history',
routes: ...
})
```
history 路径中没有#号,分不清哪块是前端路由,哪块是后端路由,分不清造成刷新页面css丢失的问题?(这还是webpack已经优化的,在早之前刷新直接404),一般在生产环境使用
webpack做了什么事情(已经优化配置的,不需要自己去做,如果遇到老项目可能会看到):
1. devServer添加: `historyApiFallback: true,` // 任意的 404 响应都被替代为 index.html
2. output添加: `publicPath: '/',` // 引入打包的文件时路径以/开头
如何解决css丢失问题,需要把`public/index.html`中所有引入的css由相对路径改成绝对路径
> 注意:上线配置history模式的时候,需要单独配置(在服务器上配置),上线的时候再说
### 重复点击按钮 - $router.push()重复渲染报错问题
这个bug之前是没有的,后来router发了新版本之后有了,为了防止重复渲染组件报的一个错误(警告)
两种解决办法:
1. `$router.push().then().catch()`,使用`.catch`将错误捕获到,不再往外抛出
2. 重写`push`方法
```js
var originPush = VueRouter.prototype.push; // 暂存一个push方法
VueRouter.prototype.push = function push(location){//location是调用$router.push传的参数
return originPush.call(this, location).catch(() => {})
}
var originReplace = VueRouter.prototype.replace; // 暂存一个push方法
VueRouter.prototype.replace = function replace(location){
return originReplace.call(this, location).catch(() => {})
}
```
## 源码分析
这里我们分析的是大佬写vue核心功能版本,不是原版vue.js,因为原版的源码太多分析不完
> 注意: data数据必须使用对象形式,指令必须是全写
### 准本知识点
1. `[].slice.call(lis)`
lis是获取的li的DOM伪数组,这里是将伪数组转成真数组
2. node.nodeType 得到节点类型
在页面中所有的东西都是节点,总共由12种节点,需要关注的是4中节点
| | nodeName | nodeType | nodeValue |
| -------- | ---------- | -------- | --------- |
| 元素节点 | 元素名大写 | 1 | null |
| 文本节点 | #text | 3 | 文本内容 |
| 注释节点 | #comment | 8 | 注释内容 |
| 属性节点 | 属性名 | 2 | 属性值 |
3. Object.defineProperty(obj, propName, {}): 给对象添加/修改属性(指定描述符)
1. 功能:给对象添加配置属性
参数一:是目标对象
参数二: 属性名
参数三:配置对象,配置当前属性,配置对象有以下几个可选项
* value 配置值
* writable 配置属性是否可修改
* configurable 配置属性是否能重新定义(是否可删除)
* enumerable 配置属性是否可枚举
* get 方法 获取该属性值的时候一定会走该方法
* set 方法 设置该属性值的时候一个会都该方法
```js
var obj = {
name: "华为P50"
}
var p = 3999;
Object.defineProperty(obj, 'price', {
// value: 3999, // 设置属性的值
// writable: true, // value值是否可修改
configurable: true, // 是否可以重新define的意思(可以理解成删除)
enumerable: true, // 是否可枚举
get() {
return p;
},
set(val) {
p = val;
}
})
```
> 注意:value、writable 和 get、set是冲突的,不能同时配置
4. Object.keys(obj): 得到对象自身可枚举的属性名的数组,从而遍历拿到对象中所有的属性
for js中的最基本的遍历方法 主要用来遍历数组 它可以使用 break 和 continue
for...in 用来遍历对象的 for...in循环不但遍历自身,还要遍历对象的原型
forEach 专本针对数组的遍历方法 不能使用break和continue
for...of 用来遍历可迭代数据,伪数组可以使用,可以使用break和continue
```js
var obj = {
name: "张三",
age: 18
}
Object.keys(obj).forEach(key => {
console.log(key, obj[key])
})
```
5. obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性
```js
var obj = {
name: "张三",
age: 18
}
Object.prototype.sex = "男";
obj.hasOwnProperty('name'); // true
obj.hasOwnProperty('age'); // true
obj.hasOwnProperty('sex'); // false,原型上的属性
```
6. documnetfragment
现在将页面种所有的内容都放到 fragment 容器中
fragment 是在内存的,不在页面上,当页面内容放到这个容器中的时候
再去修改内容的时候,是在内存中修改的
不会频繁的触发重排和重绘
当修改内容完毕后再将 fragment 容器中内容放到页面即可
```js
var fragment = document.createDocumentFragment(); // 创建容器
var app = document.getElementById('app');
var child;
// 将app内容转移到frament中
while (child = app.childNodes[0]) {
fragment.appendChild(child);
}
[].slice.call(fragment.children[0].children).forEach(el => {
el.innerText = "哈哈哈"; // 这里在内存修改的
})
// 再把容器中内容交还给原来的app(容器中的内容一次性全部给app)
app.append(fragment);
```
### 数据代理 *
什么是数据代理 ?
像中介一样当前对象代理别的对象的数据
怎么做的 ?
```js
var vm = new MVVM({
el: "#app",
data: {
msg: "i love you"
}
})
// 在vue中把data中的数据都代理到vm实例上
function MVVM (options) {
var data = this._data = options.data; // 拿到配置对象中的data属性,是存数据的对象
var me = this; // 存了以下this
Object.keys(data).forEach(key => {
me._proxy(vm, key)
})
}
MVVM.prototype = {
_proxy: (vm, key) => {
Object.defineProperty(vm, key, {
configurable: false, // 是否可以重新define的意思(可以理解成删除)
enumerable: true, // 是否可枚举
get() {
return this._data[key];
},
set(val) {
this._data[key] = val;
}
})
}
}
```
### 数据劫持 *
什么是数据劫持?
将对象中的属性使用 `Object.defineProperty`方法进行重新定义,可以在获取值和设置值的时候进行拦截
怎么做的 ?
拿到数据 data 对象,对这个对象进行递归,取到每一层的数据都进行重新定义,使用`Object.defineProperty`方法重新定义,让所有层级的数据变成响应式的
并且在set方法中设置了重新调用,当给数据data中属性赋值成一个对象的时候,也给这个对象进行递归重新定义该对象下所有属性,变成可拦截的
### 模板解析
首先将模板中所有内容都放到 fragment 容器中,在容器中去操作(在内存中)
去fragment拿到所有元素开始解析,需要递归去找所有的节点,判断节点是不是文本节点,是文本节点的时候,判断该文本是否有插值语法(用正则去匹配),截取出插值语法中的内容,例如:`{{msg}}`,当匹配到插值语法的时候先拿到 `msg` 这个文本,然后去数据中去找这个数据(找数据的时候每次都会经过数据劫持),找到之后进行替换
将替换好的fragment全部移回模板中
## day11
### 普通指令解析
### 事件指令解析
1. 首先将模板中所有内容都放到 fragment 容器中,在容器中去操作(在内存中)
2. 去fragment拿到所有元素开始解析,需要递归去找所有的节点
* 判断节点是不是元素节点,是元素节点的时候,对元素上的所有属性进行遍历
* 判断是不是以 `v-` 开头的指令,如果是就以指令的形式处理
* 判断是不是事件指令,如果是以事件的形式处理,如果不是以普通指令处理
* 普通指令 `v-text="msg"` 截取属性名`v-`后面呢的`text`,拿到属性值`msg`,然后替换当前元素节点中的内容,替换的时候数据去vm实例中去拿(vm去拿msg数据)
* 事件指令 `v-on:click="clickHandler"`截取属性名`v-`后面的`on:click`,然后截取`on:`后面的`click`拿到事件类型,同时去vm实例的methods属性中拿属性值`clickHandler`这个方法,拿到事件类型和vm中methods的函数使用 DOM2 事件绑定
* 解析完指令,在判断是不是指令的函数中把刚刚解析过的指令进行删除(页面上元素并没有指令)
3. 将fragment中所有内容交还给`#app`模板
### 数据绑定 - 模板渲染
