vue create 项目名称
目录分析
public
文件夹:静态资源,webpack进行打包的时候会原封不动打包到dist文件夹中。
pubilc/index.html
是一个模板文件,作用是生成项目的入口文件,webpack打包的js,css也会自动注入到该页面中。我们浏览器访问项目的时候就会默认打开生成好的index.html。
src
文件夹(程序员代码文件夹)
src/assets
: 存放公用的静态资源
src/components
: 非路由组件(全局组件),其他组件放在views或者pages文件夹中
src/App.vue
: 唯一的根组件
src/main.js
: 程序入口文件,最先执行的文件
babel.config.js
:babel配置文件,把es6翻译成es5
package.json
:看到项目描述、项目依赖、项目运行指令
package-lock.json
: 缓存性文件(各种包的来源)
README.md
:项目说明文件
package.json
中 "serve": "vue-cli-service serve --open",
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
vue.config.js
,进行配置
module.exports = {
//默认打开地址http://localhost:8080/
devServer: {
host: 'localhost',
port: 8080,
},
//关闭eslint
lintOnSave: false
}
jsconfig.json
,用@/
代替src/
,exclude
表示不可以使用该别名的文件 {
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
]
}
路由的配置
2个非路由组件,四个路由组件
两个非路由组件:
Header
【首页、搜索页】
Footer
【在首页、搜索页】在登录页是没有的
路由组件:
Home
、
Search
、
Login
(没有底部的Footer组件,带有二维码的)、
Register
(没有底部的Footer组件,带二维码的)
开发一个前端模块可以概括为以下几个步骤:
(1)写静态页面、拆分为静态组件;
(2)发请求(API);
(3)vuex(actions、mutations、state三连操作);
(4)组件获取仓库数据,动态展示;
组件页面样式
组件页面的样式使用的是less样式,浏览器不识别该样式,需要下载相关依赖
npm install --save less less-loader@5
如果想让组件识别less样式,则在组件中设置
清除vue页面默认的样式
vue是单页面开发,我们只需要修改public下的index.html文件
写footer
组件和header
非路由组件
非路由组件使用分为几步:
第一步:定义footer/index.vue
第二步:引入import myHeader from "./components/Header"
第三步:注册components: { myHeader}
第四步:使用
npm install --save vue-router@3
新建pages
文件夹,并创建四个路由组件Home
、Search
、Login
、Register
创建router
文件夹,并创建index.js
进行路由配置,最终在main.js
中引入注册
1. router/index.js
//配置路由的地方
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);//使用插件
//引入路由文件
import Home from '@/pages/Home'
import Search from '@/pages/Search'
import Login from '@/pages/Login'
import Register from '@/pages/Register'
//配置路由
export default new VueRouter({
routes:[
{
path:"/home",
component:Home
},
{
path:"/search",
component:Search
},
{
path:"/login",
component:Login
},
{
path:"/register",
component:Register
},
]
})
2. main.js引入注册
import Vue from 'vue'
import App from './App.vue'
//引入路由
import router from '@/router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
//注册路由,底下的写法是kv一致,省略v【router小写的】
//注册路由信息:当这里写router的时候,组件身上都拥有$route,$router属性
router
}).$mount('#app')
3. App.vue
中写出口
<!-- 路由组件出口的地方 -->
<router-view></router-view>
components
中,路由组件放在pages
或views
中$router
$route
属性$router
:一般进行编程式导航进行路由跳转【push|replace】
$route
: 一般获取路由信息(name path params等)
router-link
标签 ,可以把router-link理解为一个a标签,它 也可以加class修饰编程式导航除了跳转业务外,还可以进行其他的业务逻辑
Header/index.vue声明式导航
<router-link to="/login">登录</router-link>
<router-link class="register" to="/register">前往注册</router-link>
编程式导航
<button class="sui-btn btn-xlarge btn-danger" type="button" @click="goSeacrh">搜索</button>
methods:{
//搜索按钮的回调函数,需要向search路由进行跳转
goSeacrh(){
this.$router.push('/search')
}
}
query、params
query
、params
两个属性可以传递参数
query
参数:不属于路径当中的一部分,类似于get请求,地址栏表现为 /search?k1=v1&k2=v2
query
参数对应的路由信息 path: "/search"
params
参数:属于路径当中的一部分,需要注意,在配置路由的时候,需要占位 ,地址栏表现为 /search/v1/v2
params
参数对应的路由信息要修改为path: “/search/:keyword
” 这里的/:keyword
就是一个params参数的占位符
有几种写法:
第一种 字符串,parms参数和query参数
this.$router.push('/search/' +this.keyword +"?k=" +this.keyword.toUpperCase())
第二种:模板字符串
this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()}`)
第三种:对象(常用)
this.$router.push({name:"search",params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}})
以对象方式传参时,如果我们传参中使用了params,只能使用name
,不能使用path,如果只是使用query传参,可以使用path
{
// 占位
path:"/search/:keyword",
component:Search,
meta:{show:true},
name:"search"
},
答:路由跳转参数的时候,对象的写法可以是name,path形式,但需要注意的是path这种写法不能与params参数一起使用
this.$router.push({path:"/search",params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}})
如果路由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?",?表示该参数可传可不传
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}})
可以,而且有三种写法
//布尔写法
props:true,
// 对象写法
props:{a:1,b:2}
// 函数写法:可以params参数,query参数,通过props传递给路由组件
props:($route)=>{
return {keyword:$route.params.keyword,k:$route.query.k};
}
接收props参数
props:['keyword','k']
params
传参问题
(1)、如何指定params参数可传可不传
footer在登录注册页面是不存在的,所以要隐藏,v-if
或者 v-show
这里使用v-show
,因为v-if
会频繁的操作dom元素消耗性能,v-show
只是通过样式将元素显示或隐藏即display:show|none
meta,
v-show
赋值,判断是否显示footer组件代码:
1. router/index.js配置元信息meta,
{
path:"/search",
component:Search,
meta:{show:true}
},
{
path:"/login",
component:Login,
meta:{show:false}
},
2. v-show
判断是否显示footer组件
<!-- 在home和search中显示,在登录、注册隐藏 -->
<my-footer v-show="$route.meta.show"></my-footer>
多次执行相同的push问题,控制台会出现警告
例如:使用this.$router.push({name:‘Search’,params:{keyword:“…”||undefined}})时,如果多次执行相同的push,控制台会出现警告。
编程式导航(push|replace)才会有这种情况的异常,声明式导航是没有这种问题,因为声明式导航内部已经解决这种问题
原因:push是一个promise
,promise需要传递成功和失败两个参数,我们的push中没有传递
方法:this.$router.push({name:‘Search’,params:{keyword:"…"||undefined}},()=>{},()=>{})
后面两项分别代表执行成功和失败的回调函数
这种写法治标不治本,将来在别的组件中push|replace,编程式导航还是会有类似错误
push是VueRouter.prototype的一个方法,在router中的index重写该方法即可(看不懂也没关系,这是前端面试题)
this
:当前组件实例(search)
this.$router
属性:当前这个属性,属性值VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加$router
、$route
属性
push:VueRouter类的一个实例
//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,() => {},() => {})
}
}
相同点:都可以调用函数一次,都可以篡改函数的上下文一次
不同点:call和apply传递参数:
call
传递参数用逗号隔开,apply
方法执行,传递数组
我们的三级联动组件是全局组件,全局的配置都需要在main.js中配置
//将三级联动组件注册为全局组件
import TypeNav from '@/pages/Home/TypeNav';
//第一个参数:全局组件名字,第二个参数:全局组件
Vue.component(TypeNav.name,TypeNav);
在Home组件中使用该全局组件
发请求的方式:XMLHttpRequest、$、fetch、axios
AJAX:客户端可以’敲敲的’向服务器端发请求,在页面没有刷新的情况下,实现页面的局部更新。
为什么要二次封装axios
请求拦截器、响应拦截器。
请求拦截器:可以在发请求之前可以处理一些业务
响应拦截器:当服务器返回数据以后,可以处理一些事情
axios文档:https://www.kancloud.cn/yunye/axios/234845
安装:npm install --save axios
工作的时候src
目录下的API
文件夹,一般关于axios
二次封装的文件
baseURL:'/api'
是配置基础路径,发请求中路径中会出现api
// 对axios二次封装
import axios from "axios";
//1. 利用axios对象的方法create,去创建一个axios实例
// 2. request就是axios,只是稍微配置了一下
const requests = axios.create({
//基础路径,requests发出的请求在端口号后面会跟改baseURl
baseURL:'/api',
timeout: 5000,//超时5秒,5秒没有响应就失败了
})
// 请求拦截器:在请求之前,请求拦截器可以检测到,可以在请求之前做一些事情
requests.interceptors.request.use((config)=>{
return config;
})
// 响应拦截器
requests.interceptors.response.use((res)=>{
//成功的回调函数
return res.data;
},(error)=>{
//失败的回调函数
console.log("响应失败"+error)
return Promise.reject(new Error('fail'))
})
// 对外暴露
export default requests;
在文件夹api
中创建index.js
文件,用于封装所有请求
将每个请求封装为一个函数,并暴露出去,组件只需要调用相应函数即可,这样当我们的接口比较多时,如果需要修改只需要修改该文件
即可。
index.js
代码如下:
// 当前模块:API统一管理
import requests from "./request";
// 三级联动接口文档
// /api/product/getBaseCategoryList get 无参数
// 发请求:axios发情期返回结果Promise对象
export const reqCategoryList = ()=>requests({
url:'/product/getBaseCategoryList',
method:'get'
})
方法:jsonp
、cros
、proxy代理
vue.config.js
中 devServer
中配置,proxy
为通过代理解决跨域问题。vue.config.js
代码如下:module.exports = {
//默认打开地址http://localhost:8080/
devServer: {
host: 'localhost',
port: 8080,
proxy: { # 配置代理
# 1 会把请求路径中的/api换为后面的代理服务器
'/api': {
# 2 提供数据的服务器地址
// target: 'http://39.98.123.211',
target: 'http://gmall-h5-api.atguigu.cn',
}
},
},
//关闭eslint
lintOnSave: false
}
打开一个页面时,往往会伴随一些请求,并且会在页面上方出现进度条。它的原理时,在我们发起请求的时候开启进度条,在请求成功后关闭进度条,所以只需要在request.js
中进行配置。
如下图所示,我们页面加载时发起了一个请求,此时页面上方出现蓝色进度条
npm install --save nprogress
:import nprogress from 'nprogress';
start
:进度条开始,done
:进度条结束request.js
中增加代码:// 引入进度条
import nprogress from 'nprogress';
// 引入进度条样式
import "nprogress/nprogress.css"
// 请求拦截器:在请求之前,请求拦截器可以检测到,可以在请求之前做一些事情
requests.interceptors.request.use((config)=>{
# 1. 进度条开始
nprogress.start()
return config;
})
// 响应拦截器
requests.interceptors.response.use((res)=>{
# 2.进度条结束
nprogress.done()
//成功的回调函数
console.log('响应成功',res.data)
return res.data;
},(error)=>{
//失败的回调函数
console.log("响应失败"+error)
return Promise.reject(new Error('fail'))
})
vuex
:Vue官方提供的一个插件,插件可以管理项目共用数据
书写任何项目都需要vuex
?
项目大的时候,组件多,模块多,需要有一个地方‘统一管理数据’即为仓库store,可以集中式管理数据。
import Vue from "vue";
import Vuex from "vuex";
// 使用Vuex
Vue.use(Vuex);
# state:是仓库,存储数据的地方
const state ={};
# mutations:修改state的唯一手段
const mutations ={};
#action:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions={};
# getters:理解成计算属性,用于简化仓库数据,让组件获取仓库数据更加方便
const getters={};
// 对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
main.js
中引入import store from './store'
new Vue({
render: h => h(App),
//注册路由,此时组件中都会拥有$router $route属性
router,
# 注册store,此时组件中都会拥有$store
store
}).$mount('#app')
在其他组件中使用store中的数据
<button @click="add">点我加1</button>
<span>仓库的数据{{count}}</span>
<button>点我减1</button>
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['count'])
},
methods:{
add(){
// 派发action
this.$store.dispatch('add');
}
}
}
store/index.js
中:const state ={
count:1
};
// mutations:修改state的唯一手段
const mutations ={
# 2.
ADD(state){
state.count++;
}
};
// action:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions={
#1. 这里可以书写业务逻辑,但是不能修改state
add({commit}){
commit('ADD')
}
};
const getters={};
// 对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store
对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)
。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
// search的小仓库
const state ={};
const mutations ={};
const actions={};
const getters={};
export default{
state,
mutations,
actions,
getters
}
store/index.js
中引入两个仓库,并注册// 引入小仓库
import home from './home'
import search from "./search";
// 对外暴露Store类的一个实例
export default new Vuex.Store({
# 实现Vuex仓库模块化开发存储数据
modules:{
home,
search
}
})
全局组件放在components下,在mian.js中引入
TypeNav
获取三级联动的数据
export default {
name: 'TypeNav',
// 组件挂载完毕,可以向服务器发请求
mounted(){
// 通知Vuex发请求,获取数据,存储在仓库中
this.$store.dispatch('categoryList')
console.log(state)
},
computed: {
// 右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立马执行一次
// 注入一个参数state,其实即为大仓库中的数据
...mapState({categoryList: state => state.home.categoryList}),
},
}
home/index.js
代码
// 引入三级联动发请求的
import{reqCategoryList} from '@/api'
// home的小仓库
const state ={
// 服务器返回的是对象,初始化就是对象,服务器返回是数组,初始化就是数组
categoryList:[]
};
const mutations ={
GATEGORYLIST(state,categoryList){
state.categoryList= categoryList
}
};
const actions={
// 通过API里面的接口函数调用,向服务器发请求,获取服务器数据
async categoryList({commit}){
let result =await reqCategoryList();// reqCategoryList()返回的是promise
if(result.code==200){
commit("GATEGORYLIST",result.data);
}
}
};
鼠标放上去是浅蓝色
第一种方案,采用样式
.item:hover{
background:skyblue;
}
第二种方案,使用js
<h3 @mouseenter="changIndex(index)" >
<a href="">{{c1.categoryName}}-{{index}}</a>
</h3>
data(){
return {
currentIndex:-1,
}
},
methods:{
// 鼠标进入,修改currentIndex
changIndex(index){ //index 就是鼠标移上一级菜单的索引
this.currentIndex=index
},
leaveIndex(){
this.currentIndex=-1
}
}
事件委派,
@mouseleave="leaveIndex
写在外层div
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
<div class="sort">
</div>
</div>
控制二级、三级分类显示与隐藏
<!-- 二级、三级分类 -->
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
正常:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短
,而回调函数内部有计算,那么很可能出现浏览器卡顿
)(鼠标划太快,不会每次都打印)
防抖:前面的所有的触发都被取消
,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发,只会执行最后一次,减少业务负担。
节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间
间隔才会触发回调,把频繁触发变为少量
触发。(比如1秒就执行1次)原生js闭包+定时器实现
安装lodash
插件,该插件提供了防抖
和节流
的函数
我们可以引入js文件,直接调用。当然也可以自己写防抖和节流的函数
下面代码就是将changeIndex
设置了节流,如果操作很频繁,限制50ms执行一次。这里函数定义采用的键值对形式。throttle的返回值就是一个函数,所以直接键值对赋值就可以,函数的参数在function中传入即可。
import {throttle} from 'lodash'
methods: {
//鼠标进入修改响应元素的背景颜色
//采用键值对形式创建函数,将changeIndex定义为节流函数,该函数触发很频繁时,设置50ms才会执行一次
changeIndex: throttle(function (index){
this.currentIndex = index
},50),
//鼠标移除触发时间
leaveIndex(){
this.currentIndex = -1
}
}
三级标签列表有很多,每一个标签都是一个页面链接,我们要实现通过点击表现进行路由跳转。
路由跳转的两种方法:导航式路由,编程式路由
。
对于导航式路由,我们有多少个
a
标签就会生成多少个router-link
标签,这样当我们频繁操作时会出现卡顿现象。
对于编程式路由,我们是通过触发点击事件实现路由跳转。同理有多少个
a
标签就会有多少个触发函数
。虽然不会出现卡顿,但是也会影响性能。
上面两种方法无论采用哪一种,都会影响性能。
我们提出一种:编程时导航+事件委派
的方式实现路由跳转。事件委派即把子节点的触发事件都委托给父节点
。这样只需要一个回调函数goSearch
就可以解决。
(1)如何确定我们点击的一定是a
标签呢?如何保证我们只能通过点击a
标签才跳转呢?
为三个等级的a标签添加自定义属性date-categoryName绑定商品标签名称来标识a标签(其余的标签是没有该属性的)。
(2)如何获取子节点标签的商品名称和商品id
(我们是通过商品名称和商品id进行页面跳转的)
为三个等级的a标签再添加自定义属性data-category1Id、data-category2Id、data-category3Id来获取三个等级a标签的商品id,用于路由跳转。
<!-- 利用事件委派+编程式导航实现路由的跳转与传递参数 -->
<div class="all-sort-list2" @click="goSearch">
<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId"
:class="{cur:currentIndex==index}">
<h3 @mouseenter="changIndex(index)" >
<a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{c1.categoryName}}-{{index}}</a>
</h3>
<!-- 二级、三级分类 -->
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
<div class="subitem" v-for="c2 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 in c2.categoryChild" :key="c3.categoryId">
<a :data-categoryName="c3.categoryName" :data-category3Id="c3.categoryId">{{c3.categoryName}}
我们可以通过在函数中传入event
参数,获取当前的点击事件,通过event.target
属性获取当前点击节点,再通过dataset属性获取节点的属性信息。
对应的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)
}
},
注意:过渡动画的前提是组件、元素必须要有v-if|v-show命令,才能进行过渡动画
mounted(){
// 通知Vuex发请求,获取数据,存储在仓库中
this.$store.dispatch('categoryList')
// console.log('挂载完毕',state)
// 如果路由不是home,将typeNav隐藏
if(this.$route.path!='/home'){
this.show = false;
}
},
// 当鼠标离开的时候,让商品分类隐藏
leaveshow(){
this.currentIndex=-1;
if(this.$route.path!='/home'){
this.show = false;
}
},
// 鼠标进入的时候,显示
entershow(){
if(this.$route.path!='/home'){
this.show = true;
}
}
.sort-enter{
height: 0px;
}
.sort-enter-to{
height: 461px;
}
.sort-enter-active{
transition: all .5s linear;
}
Vue在路由切换的时候会销毁旧路由
我们在三级列表全局组件TypeNav
中的mounted
进行了请求一次商品分类列表数据。
由于Vue在路由切换的时候会销毁
旧路由,当我们再次使用三级列表全局组件时还会发一次请求。
由于信息都是一样的,出于性能的考虑我们希望该数据只请求一次
,所以我们把这次请求放在App.vue
的mounted
中。
【根组件App.vue
的mounted
只会执行一次】
注意:虽然main.js
也是只执行一次,但是不可以放在main.js
中。因为只有组件的身上才会有$store
属性。
searh
搜索跳转前,判断有没有query
参数
if(this.$route.query){// 如果有query参数也要带上
let location = {name:'name',params:{keyword:this.keyword||undefined}};
location.query=this.$route.query;
this.$router.push(location);
}
三级标签跳转前判断有没有params
参数
// 判断: 如果路由跳转的时候,带有params参数,要一起带过去
if(this.$route.params){
this.params=this.$route.params;
}
// 动态给location配置对象添加query属性
location.query = query;
// 路由跳转
this.$router.push(location);
mock
用来拦截前端ajax
请求,返回我么们自定义的数据用于测试前端接口
第一步:安装依赖包mockjs
第二步:在src
文件夹下创建一个文件夹,文件夹mock
文件夹。
第三步:准备模拟的数据
把mock
数据需要的图片放置于public/images
文件夹中【public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹】
比如:listContainer中的轮播图的数据
[
{id:1,imgUrl:'xxxxxxxxx'},
{id:2,imgUrl:'xxxxxxxxx'},
{id:3,imgUrl:'xxxxxxxxx'},
]
第四步:在mock文件夹中创建一个mockServer.js
文件
注意:在mockServer.js
文件当中对于banner.json||floor.json
的数据没有暴露,但是可以在server模块中使用。
对于webpack当中一些模块:图片、json,不需要对外暴露,因为默认就是对外暴露。
mockServer.js
// 引入morkjs模块
import Mock from "mockjs";
// 把json数据格式引进来
import banner from './banner.json'
import floor from './floor.json'
// mock 数据: 第一个参数 请求地址,第二个参数:请求数据
Mock.mock("/mock/banner",{code:200,data:banner});
Mock.mock("/mock/floor",{code:200,data:floor});
在main.js
入口引入MockSever.js
第五步:通过mock模块模拟出数据
通过Mock.mock方法进行模拟数据
第六步:回到入口文件,引入serve.js
mock需要的数据|相关mock代码页书写完毕,关于mock当中serve.js需要执行一次,
如果不执行,和你没有书写一样的。
// 引入MockSever.js----mock数据
import '@/mock/mockServer'
**第七步:**在API文件夹中创建mockRequest
【axios实例:baseURL:‘/mock’】
专门获取模拟数据用的axios实例。
在开发项目的时候:切记,单元测试,某一个功能完毕,一定要测试是否OK
我们会把公共的数据放在store中,然后使用时再去store中取。
以我们的首页轮播图数据为例。
mounted(){
// 派发action:通过Vuex发起Ajax请求,将数据存储在仓库中
this.$store.dispatch('getBannerList')
}
store
中的actions
中完成的store/home.index.js
const actions={
//获取首页轮播图的数据
async getBannerList({commit}){
let result = await reqGetBannerList();
console.log(result);
if(result.code==200){
// console.log('------',result.data)
commit("GATBANNERLIST",result.data);
}
}
};
store
仓库,在mutations
完成const mutations ={
GATBANNERLIST(state,bannerList){
state.bannerList= bannerList
}
};
ListContainer.vue
组件在store
中获取轮播图数据。由于在这个数据是通过异步请求获得的,所以我们要通过计算属性computed
获取轮播图数据。ListContainer.vue
代码
computed:{
...mapState({
bannerList:state=>state.home.bannerList
})
}
(1)安装swiper
(2)在需要使用轮播图的组件内导入swpier和它的css样式
(3)在组件中创建swiper需要的dom标签(html代码,参考官网代码)
(4)创建swiper实例
import Swiper from 'swiper'
import 'swiper/css/swiper.css'
swiper
,我们第一时间想到的是在mounted
中创建这个实例。我们在mounted
中先去异步请求了轮播图数据,然后又创建的swiper实例。由于请求数据是异步
的,所以浏览器不会等待该请求执行完再去创建swiper
,而是先创建了swiper
实例,但是此时我们的轮播图数据
还没有获得,就导致了轮播图展示失败。
解决方法一:等我们的数据请求完毕后再创建swiper实例。只需要加一个1000ms
时间延迟再创建swiper实例.。(实际中可以先这么写,以后再优化)
mounted() {
this.$store.dispatch("getBannerList")
setTimeout(()=>{
let mySwiper = new Swiper(document.getElementsByClassName("swiper-container"),{
pagination:{
el: '.swiper-pagination',
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
},1000)
},
解决方法二:我们可以使用watch
监听bannerList
轮播图列表属性,因为bannerList
初始值为空,当它有数据时,我们就可以创建swiper
对象。
watch:{
bannerList(newValue,oldValue){
let mySwiper = new Swiper(this.$refs.cur,{
pagination:{
el: '.swiper-pagination',
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
}
我们的watch
只能保证在bannerList
变化时创建swiper
对象,但是并不能保证此时v-for
已经执行完了。假如watch
先监听到bannerList
数据变化,执行回调函数创建了swiper
对象,之后v-for
才执行,这样也是无法渲染轮播图图片
【因为swiper对象生效的前提是html即dom结构已经渲染好了】
watch+this.$nextTick()
官方介绍:this. $nextTick它会将回调延迟到下次 DOM 更新循环之后执行(循环就是这里的v-for)。
个人理解:无非是等我们页面中的结构都有了再去执行回调函数
.$nextTick()
将回调延迟到下次 DOM 更新循环
之后执行。在修改数据之后
立即使用它,然后等待 DOM 更新。
文档:https://cn.vuejs.org/v2/api/#vm-nextTick
watch:{
// 监听bannerList数据的变化:因为这条数据发生了变化
bannerList(newValue,oldValue){
//this.$nextTick()使用###################
this.$nextTick(()=>{
let mySwiper = new Swiper(document.getElementsByClassName("swiper-container"),{
pagination:{
el: '.swiper-pagination',
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
})
}
}
之前我们在学习watch
时,一般都是监听的定义在data
中的属性,但是我们这里是监听的computed
中的属性,这样也是完全可以的,并且如果你的业务数据也是从store
中通过computed
动态获取的,也需要watch
监听数据变化执行相应回调函数,完全可以模仿上面的写法。