1、vue文件目录分析
默认环境
node + webpack + VScode + 谷歌浏览器 + git
创建项目
vue create app
public文件夹:静态资源,webpack进行打包的时候会原封不动打包到dist文件夹中。
pubilc/index.html是一个模板文件,作用是生成项目的入口文件,webpack打包的js,css也会自动注入到该页面中。我们浏览器访问项目的时候就会默认打开生成好的index.html。
src文件夹(程序员代码文件夹)
assets: 存放公用的静态资源
components: 非路由组件(全局组件),其他组件放在views或者pages文件夹中
App.vue: 唯一的跟组件
main.js: 程序入口文件,最先执行的文件
babel.config.js: 配置文件(babel相关) package.json: 项目的详细信息记录 package-lock.json: 缓存性文件(各种包的来源)
2、项目配置
2.1 项目运行,浏览器自动打开
package.json
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
2.2 关闭eslint校验工具(不关闭会有各种规范,不按照规范就会报错)
-
根目录下创建vue.config.js,进行配置
module.exports = {
//关闭eslint
lintOnSave: false
}
2.3 src文件夹配置别名,创建jsconfig.json,用@/代替src/,exclude表示不可以使用该别名的文件
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
]
}
3、组件页面样式
组件页面的样式使用的是less样式,浏览器不识别该样式,需要下载相关依赖
npm install --save less less-loader@5
如果想让组件识别less样式,则在组件中设置 <script scoped lang="less">
4、清除vue页面默认的样式
vue是单页面开发,我们只需要修改public下的index.html文件
<link rel="stylesheet" href="reset.css">
5 路由的一个分析 确定项目结构顺序:
上中下 -----只有中间部分的V在发生变化,
中间部分应该使用的是路由组件 2个非路由组件|四个路由组件
两个非路由组件:Header 、Footer
路由组件:Home、Search、Login(没有底部的Footer组件,带有二维码的)、Register(没有底部的Footer组件,带二维码的)
5.1 创建非路由组件 header ,footer
5.2 在根组件中使用header,footer
5、pages文件夹
npm i vue-router@3.0
创建pages文件夹,并创建路由组件 5.1创建router文件夹,并创建index.js进行路由配置,最终在main.js中引入注册
5.2 总结 路由组件和非路由组件区别:
-
非路由组件放在components中,路由组件放在pages或views中
-
非路由组件通过标签使用,路由组件通过路由使用
-
在main.js注册玩路由,所有的路由和非路由组件身上都会拥有$router $route属性
-
$router:一般进行编程式导航进行路由跳转
-
$route: 一般获取路由信息(name path params等)
5.3 路由跳转方式
-
声明式导航router-link标签 <router-link to="path">,可以把router-link理解为一个a标签,它 也可以加class修饰
-
编程式导航 :声明式导航能做的编程式都能做,而且还可以处理一些业务
代码:
1 创建四个路由组件
2 配置路由
3 在main.js当中注册
4 书写路由组件出口
6、footer组件显示与隐藏
-
footer在登录注册页面是不存在的,所以要隐藏,v-if 或者 v-show
-
这里使用v-show,因为v-if会频繁的操作dom元素消耗性能,v-show只是通过样式将元素显示或隐藏
-
配置路由的时候,可以给路由配置元信息meta,
-
在路由的原信息中定义show属性,用来给v-show赋值,判断是否显示footer组件
方法1:
但是如果页面多的话,就特别恶心,不是最终解决方案,用路由元信息解决
方法2:
7、路由传参
7.1、query、params
-
query、params两个属性可以传递参数
-
query参数:不属于路径当中的一部分,类似于get请求,地址栏表现为 /search?k1=v1&k2=v2 query参数对应的路由信息
path: "/search"
-
params参数:属于路径当中的一部分,需要注意,在配置路由的时候,需要占位 ,地址栏表现为 /search/v1/v2
params参数对应的路由信息要修改为path: "/search/:keyword"
这里的/:keyword就是一 个params参数的占位符
代码:
点击搜索,跳转搜索组件,
参数传递到搜索组件后进行获取 参数
-
7.2 params传参问题
(1)如何指定params参数可传可不传
如果路由path要求传递params参数,但是没有传递,会发现地址栏URL有问题,详情如下: Search路由项的path已经指定要传一个keyword的params参数,如下所示: path: "/search/:keyword", 执行下面进行路由跳转的代码: this.$router.push({name:"Search",query:{keyword:this.keyword}}) 当前跳转代码没有传递params参数 地址栏信息:http://localhost:8080/#/?keyword=asd 此时的地址信息少了/search 正常的地址栏信息: http://localhost:8080/#/search?keyword=asd 解决方法:可以通过改变path来指定params参数可传可不传 path: "/search/:keyword?",?表示该参数可传可不传
参考连接:vue中this.$route.query和this.$route.params & query传参和params传参的使用和区别_viceen的博客-CSDN博客
(2)由(1)可知params可传可不传,但是如果传递的时空串,如何解决 。
this.$router.push({name:"Search",query:{keyword:this.keyword},params:{keyword:''}}) 出现的问题和1中的问题相同,地址信息少了/search 解决方法: 加入||undefined,当我们传递的参数为空串时地址栏url也可以保持正常 this.$router.push({name:"Search",query:{keyword:this.keyword},params:{keyword:''||undefined}})
(3)路由组件能不能传递props数据? 可以,但是只能传递params参数,具体知识为props属性 。
7.3、传参方法
-
字符串形式 this.$router.push("/search/"+this.params传参+"?k="+this.query传参)
-
模板字符串 this.$router.push("/search/+${this.params传参}?k=${this.query传参}") 注意: 上面字符串的传参方法可以看出params参数和'/'结合,query参数和?结合
http://localhost:8080/#/search/asd?keyword=asd
上面url中asd为params的值,keyword=asd为query传递的值。 -
对象(常用) this.$router.push({name:"路由名字",params:{传参},query:{传参})。 以对象方式传参时,如果我们传参中使用了params,只能使用name,不能使用path,如果只是使用query传参,可以使用path 。
8、多次执行相同的push问题
多次执行相同的push问题,控制台会出现警告 例如:使用this.$router.push({name:'Search',params:{keyword:".."||undefined}})时,如果多次执行相同的push,控制台会出现警告。
let result = this.$router.push({name:"Search",query:{keyword:this.keyword}}) console.log(result)
执行一次上面代码: 多次执行出现警告:
原因:push是一个promise,promise需要传递成功和失败两个参数,我们的push中没有传递。
方法:this.$router.push({name:'Search',params:{keyword:".."||undefined}},()=>{},()=>{})后面两项分别代表执行成功和失败的回调函数。(只能在当前方式中有用)
这种写法治标不治本,将来在别的组件中push|replace,编程式导航还是会有类似错误 push是VueRouter.prototype的一个方法,在router中的index重写该方法即可(看不懂也没关系,这是前端面试题)
//1、先把VueRouter原型对象的push,保存一份
let originPush = VueRouter.prototype.push;
//2、重写push|replace
//第一个参数:告诉原来的push,跳转的目标位置和传递了哪些参数
VueRouter.prototype.push = function (location,resolve,reject){
if(resolve && reject){
originPush.call(this,location,resolve,reject)
}else{
originPush.call(this,location,() => {},() => {})
}
}
9、定义全局组件
好几个页面都用了三级联动,所以把他弄成全局组件
1创建组件
2 我们的三级联动组件是全局组件,全局的配置都需要在main.js中配置2
//将三级联动组件注册为全局组件
import TypeNav from '@/pages/Home/TypeNav';
//第一个参数:全局组件名字,第二个参数:全局组件
Vue.component(TypeNav.name,TypeNav);
3 在Home组件中使用该全局组件
<template>
<div>
<!-- 三级联动全局组件已经注册为全局组件,因此不需要引入-->
<TypeNav/>
</div>
</template>
全局组件可以在任一页面中直接使用,不需要导入声明 下面全部商品分类就是三级联动组件
10、代码改变时实现页面自动刷新
根目录下vue.config.js文件设置
module.exports = {
//关闭eslint
lintOnSave: false,
devServer: {
// true 则热更新,false 则手动刷新,默认值为 true
inline: true,
// development server port 8000
port: 8001,
}
}
注意:修改完该配置文件后,要重启一下项目
11、Home首页其它组件
home文件夹index.vue
<template>
<div>
<!-- 三级联动全局组件已经注册为全局组件,因此不需要引入-->
<TypeNav/>
<!-- 轮播图列表-->
<ListContainer/>
<!-- 今日推荐-->
<Recommend/>
<!-- 商品排行-->
<Rank/>
<!-- 猜你喜欢-->
<Like/>
<!-- 楼层 -->
<Floor/>
<Floor/>
<!-- 商标-->
<Brand/>
</div>
</template>
<script>
import ListContainer from './ListContainer'
import Recommend from './Recommend'
import Rank from './Rank'
import Like from './Like'
import Floor from './Floor'
import Brand from './Brand'
export default {
name: "index",
components: {
ListContainer,
Recommend,
Rank,
Like,
Floor,
Brand,
}
}
</script>
<style scoped>
</style>
12、封装axios
axios中文文档,包含详细信息。 使用说明 · Axios 中文说明 · 看云 在根目录下创建api文件夹,创建request.js文件。 内容如下,当前文件代码还比较少,后续有需求可以增添内容。
import axios from "axios";
//1、对axios二次封装
const requests = axios.create({
//基础路径,requests发出的请求在端口号后面会跟改baseURl
baseURL:'/api',
timeout: 5000,
})
//2、配置请求拦截器
requests.interceptors.request.use(config => {
//config内主要是对请求头Header配置
//比如添加token
return config;
})
//3、配置相应拦截器
requests.interceptors.response.use((res) => {
//成功的回调函数
return res.data;
},(error) => {
//失败的回调函数
console.log("响应失败"+error)
return Promise.reject(new Error('fail'))
})
//4、对外暴露
export default requests;
13、前端通过代理解决跨域问题
在根目录下的vue.config.js中配置,proxy为通过代理解决跨域问题。 我们在封装axios的时候已经设置了baseURL为api,所以所有的请求都会携带/api,这里我们就将/api进行了转换。如果你的项目没有封装axios,或者没有配置baseURL,建议进行配置。要保证baseURL和这里的代理映射相同,此处都为'/api'。
module.exports = {
//关闭eslint
lintOnSave: false,
devServer: {
// true 则热更新,false 则手动刷新,默认值为 true
inline: false,
// development server port 8000
port: 8001,
//代理服务器解决跨域
proxy: {
//会把请求路径中的/api换为后面的代理服务器
'/api': {
//提供数据的服务器地址
target: 'http://39.98.123.211',
}
},
}
}
webpack官网相关知识解读 网站中的webpack.config.js就是vue.config.js文件。
14、请求接口统一封装
在文件夹api中创建index.js文件,用于封装所有请求 将每个请求封装为一个函数,并暴露出去,组件只需要调用相应函数即可,这样当我们的接口比较多时,如果需要修改只需要修改该文件即可。
如下所示:
//当前模块,API进行统一管理,即对请求接口统一管理
import requests from "@/api/request";
//首页三级分类接口
export const reqCateGoryList = () => {
return requests({
url: '/product/getBaseCategoryList',
method: 'GET'
})
}
当组件想要使用相关请求时,只需要导入相关函数即可,以上图的reqCateGoryList 为例:
import {reqCateGoryList} from './api'
//发起请求
reqCateGoryList();
15、nprogress进度条插件
打开一个页面时,往往会伴随一些请求,并且会在页面上方出现进度条。它的原理时,在我们发起请求的时候开启进度条,在请求成功后关闭进度条,所以只需要在request.js中进行配置。 如下图所示,我们页面加载时发起了一个请求,此时页面上方出现蓝色进度条
安装:
cnpm install --save nprogress
对应的request.js设置
import axios from "axios";
//引入进度条
import nprogress from 'nprogress';
//引入进度条样式
import "nprogress/nprogress.css";
//1、对axios二次封装
const requests = axios.create({
//基础路径,requests发出的请求在端口号后面会跟改baseURl
baseURL:'/api',
timeout: 5000,
})
//2、配置请求拦截器
requests.interceptors.request.use(config => {
//config内主要是对请求头Header配置
//比如添加token
//开启进度条
nprogress.start();
return config;
})
//3、配置相应拦截器
requests.interceptors.response.use((res) => {
//成功的回调函数
//响应成功,关闭进度条
nprogress.done()
return res.data;
},(error) => {
//失败的回调函数
console.log("响应失败"+error)
return Promise.reject(new Error('fail'))
})
//4、对外暴露
export default requests;
可以通过修改nprogress.css文件的background来修改进度条颜色。
16、手动引入vuex
安装:
cnmp install --save vuex
首先确保安装了vuex,根目录创建store文件夹,文件夹下创建index.js,内容如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//对外暴露store的一个实例
export default new Vuex.Store({
state:{},
mutations:{},
actions:{},
})
如果想要使用vuex,还要再main.js中引入 main.js: (1) 引入文件 (2) 注册store 但凡是在main.js中的Vue实例中注册的实体,在所有的组件中都会有(this.$.实体名)属性
import store from './store'
new Vue({
render: h => h(App),
//注册路由,此时组件中都会拥有$router $route属性
router,
//注册store,此时组件中都会拥有$store
store
}).$mount('#app')
17、async await使用
如果我们没有封装请求api,而是直接调用axios,就不需要使用async await。 案例:我们将一个axios请求封装为了函数,我们在下面代码中调用了该函数:
import {reqCateGoryList} from '@/api'
export default {
actions:{
categoryList(){
let result = reqCateGoryList()
console.log(result)
}
}
}
浏览器结果 返回了一个promise,证明这是一个promise请求,但是我们想要的是图片中的data数据。 没有将函数封装前我们都会通过then()回调函数拿到服务器返回的数据,现在我们将其封装了,依然可以使用then获取数据,代码如下
actions:{
categoryList(){
let result = reqCateGoryList().then(
res=>{
console.log("res")
console.log(res)
return res
}
)
console.log("result")
console.log(result)
}
}
结果 由于我们的promis是异步请求,我们发现请求需要花费时间,但是它是异步的,所有后面的console.log("result");console.log(result)会先执行,等我们的请求得到响应后,才执行console.log("res");console.log(res),这也符合异步的原则,但是我们如果在请求下面啊执行的是将那个请求的结果赋值给某个变量,这样就会导致被赋值的变量先执行,并且赋值为undefine,因为此时promise还没有完成。 所以我们引入了async await,async写在函数名前,await卸载api函数前面。await含义是async标识的函数体内的并且在await标识代码后面的代码先等待await标识的异步请求执行完,再执行。这也使得只有reqCateGoryList执行完,result 得到返回值后,才会执行后面的输出操作。
async categoryList(){
let result = await reqCateGoryList()
console.log("result")
console.log(result)
}
结果
18、vuex(*)
state、actions、mutations、getters的辅助函数使用,当多次访问store中的上述属性时,要使用个属性的辅助函数,可以减少代码量。 在使用上面的函数时,如果需要传递多个参数,需要把多个参数组合为一个对象传入(vuex是不允许多个参数分开传递的)。
async addOrUpdateShopCart({commit},{skuId,skuNum}){
let result = await reqAddOrUpdateShopCart(skuId,skuNum)
console.log(result)
if(result.data === 200){
}
辅助函数官网链接 注意:使用action时,函数的第一个参数,必须是{commit},即使不涉及到mutations操作,也必须加上该参数,否则会报错。
动态展示三级联动数据
2 移入添加样式,
鼠标移除:
3 控制二三级商品分类的显示和隐藏
19、loadsh插件防抖和节流
在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。 安装lodash插件,该插件提供了防抖和节流的函数,我们可以引入js文件,直接调用。当然也可以自己写防抖和节流的函数 lodash官网 防抖函数 节流函数
即:
防抖:用户操作很频繁,但是只执行一次,减少业务负担。
节流:用户操作很频繁,但是把频繁的操作变为少量的操作,使浏览器有充分时间解析代码
防抖和节流简述 例如:下面代码就是将changeIndex设置了节流,如果操作很频繁,限制50ms执行一次。这里函数定义采用的键值对形式。throttle的返回值就是一个函数,所以直接键值对赋值就可以,函数的参数在function中传入即可。
项目中有ladash ,不需要安装了
项目中使用:
import {throttle} from 'lodash'
methods: {
//鼠标进入修改响应元素的背景颜色
//采用键值对形式创建函数,将changeIndex定义为节流函数,该函数触发很频繁时,设置50ms才会执行一次
changeIndex: throttle(function (index){
this.currentIndex = index
},50),
//鼠标移除触发时间
leaveIndex(){
this.currentIndex = -1
}
}
20、编程式导航+事件委托实现路由跳转
如上图所示,三级标签列表有很多,每一个标签都是一个页面链接,我们要实现通过点击表现进行路由跳转。 路由跳转的两种方法:导航式路由,编程式路由。
对于导航式路由,我们有多少个a标签就会生成多少个router-link标签,这样当我们频繁操作时会出现卡顿现象。 对于编程式路由,我们是通过触发点击事件实现路由跳转。同理有多少个a标签就会有多少个触发函数。虽然不会出现卡顿,但是也会影响性能。
上面两种方法无论采用哪一种,都会影响性能。我们提出一种:编程时导航+事件委派 的方式实现路由跳转。
事件委派即把子节点的触发事件都委托给父节点。这样只需要一个回调函数goSearch就可以解决。
事件委派问题:
(1)如何确定我们点击的一定是a标签呢?如何保证我们只能通过点击a标签才跳转呢?
(2)如何获取子节点标签的商品名称和商品id(我们是通过商品名称和商品id进行页面跳转的)
解决方法:
对于问题1:为三个等级的a标签添加自定义属性date-categoryName绑定商品标签名称来标识a标签(其余的标签是没有该属性的)。
对于问题2:为三个等级的a标签再添加自定义属性data-category1Id、data-category2Id、data-category3Id来获取三个等级a标签的商品id,用于路由跳转。 我们可以通过在函数中传入event参数,获取当前的点击事件,通过event.target属性获取当前点击节点,再通过dataset属性获取节点的属性信息。
<div class="all-sort-list2" @click="goSearch" @mouseleave="leaveIndex">
<div class="item" v-for="(c1,index) in categoryList" v-show="index!==16" :key="c1.categoryId" :class="{cur:currentIndex===index}">
<h3 @mouseenter="changeIndex(index)" >
<a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId" >{{c1.categoryName}}</a>
</h3>
<div class="item-list clearfix" :style="{display:currentIndex===index?'block':'none'}">
<div class="subitem" v-for="(c2,index) in c1.categoryChild" :key="c2.categoryId">
<dl class="fore">
<dt>
<a :data-categoryName="c2.categoryName" :data-category2Id="c2.categoryId">{{c2.categoryName}}</a>
</dt>
<dd>
<em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
<a :data-categoryName="c2.categoryName" :data-category3Id="c3.categoryId">{{c3.categoryName}}</a>
</em>
</dd></dl></div></div></div></div>
注意:event是系统属性,所以我们只需要在函数定义的时候作为参数传入,在函数使用的时候不需要传入该参数。
//函数使用
<div class="all-sort-list2" @click="goSearch" @mouseleave="leaveIndex">
//函数定义
goSearch(event){
console.log(event.target)
}
对应的goSearrch函数
goSearch(event){
let element = event.target
//html中会把大写转为小写
//获取目前鼠标点击标签的categoryname,category1id,category2id,category3id,
// 通过四个属性是否存在来判断是否为a标签,以及属于哪一个等级的a标签
let {categoryname,category1id,category2id,category3id} = element.dataset
//categoryname存在,表示为a标签
if(categoryname){
//category1id一级a标签
//整理路由跳转的参数
let location = {name:'Search'}//跳转路由name
let query = {categoryName:categoryname}//路由参数
if(category1id){
query.category1Id = category1id
}else if(category2id){
//category2id二级a标签
query.category2Id = category2id
}else if(category3id){
//category3id三级a标签
query.category3Id = category3id
}
//整理完参数
location.query = query
//路由跳转
this.$router.push(location)
}
},
跳转路径:
21、Vue路由销毁问题
Vue在路由切换的时候会销毁旧路由。 我们在三级列表全局组件TypeNav中的mounted进行了请求一次商品分类列表数据。 由于Vue在路由切换的时候会销毁旧路由,当我们再次使用三级列表全局组件时还会发一次请求。 如下图所示:当我们在包含三级列表全局组件的不同组件之间进行切换时,都会进行一次信息请求。 由于信息都是一样的,出于性能的考虑我们希望该数据只请求一次,所以我们把这次请求放在App.vue的mounted中。(根组件App.vue的mounted只会执行一次) 注意:虽然main.js也是只执行一次,但是不可以放在main.js中。因为只有组件的身上才会有$store属性。