# 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 的关系? ![](note/day06-vc和vm的关系.png) 主要就是一行代码: ```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() }) ``` ### 前端解决跨域 ![day07-代理解决跨域](note/day07-代理解决跨域.png) 前端通过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原理图 ![](note/day08-vuex.png) ## 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 ``` #### 编程式导航 - 声明式导航 声明式导航 ​ `` 编程式导航 ​ `$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`模板 ### 数据绑定 - 模板渲染 ![day11-dep&watcher](note/day11-dep&watcher.png)