1.创建项目
我选择用vue-cli图形化界面初始化项目
在文件夹下cmd打开命令行,输入vue ui
打开vue-cli图形化界面,如下,选择创建
填写项目名称,并初始化仓库
下一步是预设
这里可以选择之前的,也可以自己手动进行配置,我选择手动,我选择了这些配置
然后创建项目,可以保存为预设,下次可以直接用,就不用再一步步配置了
创建时间会有点久,创建完成以后就是这种界面
接下来是运行项目,选择左侧的任务-serve-运行
当如下图显示时就点击启动app
出现如下界面表示项目创建成功
项目创建好以后,用vscode打开,来看一下项目的文件构成
node-modules:项目依赖
public:一般放置一些静态资源(图片) ,这些资源在webpack进行打包的时候会原封不动的打包到dist文件夹中
src:程序员源代码文件夹
assets:一般也是放置静态资源(一般放置多个组件公用的静态资源),这里面的资源在 webpack进行打包的时候会被当做一个模块,打包在js文件里面
components:一般放置的是非路由组件(或是一些常用的全局组件)
App.vue:唯一的根组件
main.js:程序入口文件,整个程序当中最先执行的文件
babel.config.js:配置文件(babel相关)
package.json:记录项目叫什么,项目当中有哪些依赖.. 项目怎么运行
package-lock.json:缓存性文件
还需要对项目进行的其他一些配置
①项目运行起来以后,浏览器自动打开
②关闭eslint代码语法校验工具
在项目根目录下创建vue.config.js文件(一定要是这个名字),并写入以下内容
module.exports = {
// 关闭eslint
lintOnSave: false,
};
③src文件夹简写方法,配置别名@
在项目根目录下创建jsconfig.json文件,并进行以下配置
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
]
}
在进行完以上配置后,建议重新运行一下项目,看项目是否可以正常运行,也就是在图形化面板中的serve那点击停止然后再点击运行,运行完成后再点击启动app,如果跟上面运行的一样,证明配置正确。
在开发项目的时候步骤:
1.书写静态页面(html+css)
2.拆分组件
3.获取服务器的数据动态展示
4.完成响应的动态业务逻辑
2.开发项目时遇到的问题
1.项目组件中采用了less样式,但是浏览器不识别less样式,需要安装less和less-loader处理less,把less变成css
因为less-loader版本太高也不太好,所以我选择用命令行安装指定版本
npm i less less-loader@5
如果想让组件识别less,就要在style标签内加上lang="less",即<style lang="less">
2.使用组件的步骤
①创建/定义
②引入
③注册
④使用
3.搭建路由组件
需要安装vue-router,这里我使用命令行安装
npm i --save vue-router
在src目录下新建pages/views文件夹,这个文件夹通常放置路由组件
4.配置路由
在src目录下新建一个router文件夹,项目中配置的路由一般放置在router文件夹内
配置好路由以后需要指定路由组件展示的位置
5.路由组件与非路由组件的区别
①路由组件一般放在pages/views文件夹,非路由组件一般放在component文件夹
②路由组件一般在router文件夹中进行注册(使用的就是组件名字),非路由组件在使用的时候一般都是以标签的形式
③注册完路由,不管是路由还是非路由组件,身上都有$route、$router属性
$route:一般获取路由信息(路径、query、params)
$router:一般进行编程式导航路由跳转(push/replace)
6.路由的跳转形式
声明式导航:router-link,可以进行路由的跳转
编程式导航:利用组件实例的$router.push/replace,可以进行路由的跳转(声明式导航能做的,编程式导航都能做)
7.控制组件的显示与隐藏
v-if:真正的条件渲染,每次切换都是组件的销毁和重建,有较高的切换开销,如果切换较少,推荐使用v-if
v-show:实质上是通过布尔值true和false来控制组件的显示与隐藏,即display:none和display:block;如果需要频繁使用切换,选择v-show更好,有较高的初始渲染开销
给路由添加路由元信息【meta】,不能瞎写
8.路由传参,参数有几种写法
params参数:属于路径当中的一部分,在配置路由的时候需要占位,即/search/params参数
query参数:不属于路径当中的一部分,类似于ajax中的queryString /home?k=v&k=v,不需要占位,
路由传递参数:
第一种:字符串形式
this.$router.push("/search/"+this.keyword+"?k="+this.keyword)
第二种:模板字符串
this.$router.push(`/search/${this.keyword}?k=${this.keyword}`)
第三种:对象
// 这里的name属性需要在配置路由的index.js文件中配置
//即{path:'/search',component:Search,name:'search'}
this.$router.push({name:'search',params:{keyword:this.keyword},query:{k:this.keyword}})
9.路由传参相关面试题
①路由传递参数(对象写法)path是否可以结合params参数一起使用?
答:路由跳转传参的时候,对象的写法可以是name/path形式,但是path这种写法是不能与params参数一起使用的,会出错,可以与query一起使用
// "/search"后面带了/:keyword说明是要求传params参数的意思
{ path: "/search/:keyword", component: Search, meta: { show: true } },
this.$router.push({
path: '/search',
params: { keyword: this.keyword },
query: { k: this.keyword.toUpperCase() }
})
②如何指定params参数可传不可传?
答:在配置路由的时候,在占位后面加上一个?就可以了
如果路由要求传递params参数,但是就不传,会导致跳转的url出错 ,不加?之前:
// :keyword后面加上?说明这个params参数keyword可传可不传
{ path: "/search/:keyword?", component: Search, meta: { show: true } },
在keyword后面加上?以后 :
③params参数可以传递也可以不传递,但是如果传递是空串,如何解决?
答:使用undefined解决
this.$router.push({name:'search',params:{keyword:''||undefined},query:{k:this.keyword}})
如果传的params为空,且没有加undefined,则url会出错
④路由组件能不能传递props数据?
能传递,有三种方式:
(1)布尔值写法:只能传递params
{
path: "/search/:keyword?",
component: Search,
meta: { show: true },
name: "search",
props:true
}
(2)对象写法 :props:{a:1,b:2}
// 路由配置文件index.js
{
path: "/search/:keyword?",
component: Search,
meta: { show: true },
name: "search",
props:{a:1,b:2}
}
// 组件.vue
<script>
export default {
// 在组件这里通过props接收,可以通过插值表达式{{}}直接渲染在页面上
props:['a','b']
}
</script>
(3)函数写法:可以把params参数,query参数通过props传给路由组件
{
path: "/search/:keyword?",
component: Search,
meta: { show: true },
name: "search",
props:($route)=>({keyword:$route.params.keyword,k:$route.query.k})
}
10.重写push和replace方法(可以解决编程式路由跳转到当前路由参数不变,多次执行抛出NavigationDuplicated的警告)
// 路由配置文件index.js
//需要重写VueRouter.prototype原型对象身上的push|replace方法
//先把VueRouter.prototype身上的push|replace方法进行保存一份
let originPush = VueRouter.prototype.push;
let originReplace = VueRouter.prototype.replace;
//重写VueRouter.prototype身上的push方法
VueRouter.prototype.push = function (location, resolve, reject) {
//第一个形参:路由跳转的配置对象(query|params)
//第二个参数:undefined|箭头函数(成功的回调)
//第三个参数:undefined|箭头函数(失败的回调)
if (resolve && reject) {
//push方法传递第二个参数|第三个参数(箭头函数)
//originPush:利用call修改上下文,变为(路由组件.$router)这个对象,第二参数:配置对象、第三、第四个参数:成功和失败回调函数
originPush.call(this, location, resolve, reject);
} else {
//push方法没有产地第二个参数|第三个参数
originPush.call(
this,
location,
() => {},
() => {}
);
}
};
//重写VueRouter.prototype身上的replace方法
VueRouter.prototype.replace = function (location, resolve, reject) {
//第一个形参:路由跳转的配置对象(query|params)
//第二个参数:undefined|箭头函数(成功的回调)
//第三个参数:undefined|箭头函数(失败的回调)
if (resolve && reject) {
//push方法传递第二个参数|第三个参数(箭头函数)
//originPush:利用call修改上下文,变为(路由组件.$router)这个对象,第二参数:配置对象、第三、第四个参数:成功和失败回调函数
originReplace.call(this, location, resolve, reject);
} else {
//push方法没有产地第二个参数|第三个参数
originReplace.call(
this,
location,
() => {},
() => {}
);
}
};
11.call和apply相同点与不同点
相同点:都可以调用函数一次,都可以篡改函数的上下文一次
不同点:call与apply传递参数,call传递多个参数用逗号分隔,apply传递数组
12.注册全局组件
当页面拆分出来的某个组件多次出现在别的页面的时候,就把这个组件注册为全局组件,只需要注册一次,就可以在项目的任意地方使用。
在main.js中注册全局组件TypeNav
13.利用postman工具测试接口
14.axios的二次封装
请求拦截器:可以在发起请求之前处理一些业务
响应拦截器:当服务器返回数据之后,可以处理一些业务
①先下载axios
npm i --save axios
②在src下新建api文件夹,再新建request.js文件对axios进行二次封装
// 对于axios进行二次封装
import axios from "axios";
// 利用axios对象的方法create,创建一个axios实例
// request其实就是axios,就是稍微配置一下
const requests = axios.create({
// 配置对象
// 基础路径,发请求的时候,路径中会出现api
baseURL: "/api",
// 代表请求超时时间为5s
timeout: 5000,
});
// 请求拦截器
requests.interceptors.request.use((config) => {
// config:配置对象,对象里面有一个属性很重要,headers请求头
return config;
});
// 响应拦截器
requests.interceptors.response.use(
(res) => {
// 响应成功的回调函数,返回获取到的数据
return res.data;
},
(error) => {
// 响应失败的回调函数
return Promise.reject(new Error("false"));
}
);
// 对外暴露
export default requests;
15.接口的统一管理
项目很小:完全可以在组件的生命周期函数中发请求
项目很大:axios.get('xxx')
①在src下的api文件夹下新建index.js对所有的api进行统一管理
// 对api进行统一管理
import requests from "./request";
// 三级联动接口
// 请求地址 /api/product/getBaseCategoryList
// 请求方式 get
// 请求参数 无
// axios发送请求返回结果Promise对象
// 这种是不传递参数的请求
export const reqCategoryList = () => {
return requests({ url: "/product/getBaseCategoryList", method: "get" });
};
// 获取搜索模块数据 需要传递参数
// 当前这个接口,给服务器传递一个默认参数,至少是个空对象
export const reqGetSearchInfo = (params) => {
return requests({ url: "/list", method: "post", data: params });
};
16.跨域问题
什么是跨域:协议、域名、端口号不同请求,称之为跨域
http://localhost:8080/#/home ----前端项目本地服务器
http://39.38.123.211 ----后台服务器
像上面的本地服务器和后台服务器之间就存在跨域问题
跨域的解决方案:JSONP、CROS、代理
例:使用代理服务器解决
在项目根目录下的webpack.config.js/vue.config.js文件中进行如下配置
// 代理跨域
devServer: {
proxy: {
// 当浏览器的url地址中遇到api时
//会通过这个代理跨域去寻找target这个后台服务器去获取数据
"/api": {
target: "http://39.98.123.211",
},
},
},
注:更改配置文件后记得重新启动一下项目
17.nprogress进度条的使用
①首先下载nprogress
npm i --save nprogress
②因为是请求发起的时候使用进度条,服务器的数据返回后进度条消失,所以要把nprogress进度条放在请求拦截器和响应拦截器中使用
// 在request.js文件中
// 引入进度条nprogress
import nprogress from "nprogress";
// 引入进度条样式
import "nprogress/nprogress.css";
// 请求拦截器
requests.interceptors.request.use((config) => {
// 进度条开始
nprogress.start();
// config:配置对象,对象里面有一个属性很重要,headers请求头
return config;
});
// 响应拦截器
requests.interceptors.response.use(
(res) => {
// 进度条结束
nprogress.done();
// 响应成功的回调函数,返回获取到的数据
return res.data;
},
(error) => {
// 响应失败的回调函数
return Promise.reject(new Error("false"));
}
);
plus:进度条的颜色可以更改,在node_modules文件夹下的nprogress文件夹下的nprogress.css中更改如下的样式
18.vuex的使用
vuex:状态管理模式,采用集中式存储管理所有组件状态
①下载安装vuex
npm i --save vuex
②在src目录下创建store文件夹,在store文件夹下创建index.js文件
import Vue from "vue";
import Vuex from "vuex";
// 需要使用插件一次
Vue.use(Vuex);
// 引入小仓库
import home from "./home/index.js";
import search from "./search/index.js";
// 对外暴露Store类的实例
export default new Vuex.Store({
// 模块:把小仓库进行合并变成大仓库
modules: {
home,
search,
},
});
③在入口文件main.js中注册仓库
④采用集中式管理,为了方便所以创建各个模块的小仓库,比方说下面建立的home模块的仓库
// home模块的小仓库
import { reqCategoryList } from "@/api";
// state:仓库存储数据的地方
const state = {
// 分类列表
categoryList: [],
};
// mutations:修改state的唯一手段
const mutations = {
CATEGORYLIST(state, categoryList) {
state.categoryList = categoryList;
},
};
// action:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {
// 通过api里面的接口函数调用,向服务器发请求,获取服务器的数据
async categoryList({ commit }) {
let result = await reqCategoryList();
console.log(result);
if (result.code == 200) {
commit("CATEGORYLIST", result.data);
}
},
};
// getters:理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
const getters = {};
export default {
state,
mutations,
actions,
getters,
};
19.在统一管理接口的背景下,项目发起请求,获取服务器数据的过程和步骤
①在当前需要获取数据的组件内
plus:当请求要传递参数时
②store文件夹下的仓库(看当前需要获取数据的组件使用哪个仓库)
plus:当使用到getters时
③在当前需要获取数据的组件内
plus:使用到getters时,组件中获取数据就变成了
页面渲染
20.防抖与节流
卡顿现象:事件触发非常频繁,而且每一次的触发,回调函数都要去执行,如果时间很短,而回调函数内部有计算,可能会出现浏览器卡顿
节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。用户操作很频繁,但是把频繁的操作变为少量操作,可以让浏览器有充裕的事件解析代码。
防抖:前面所有的触发都被取消,最后一次执行在规定的时间之后才会触发,即如果连续快速的触发,只会执行一次。用户操作很频繁,但是只执行一次。
lodash插件:里面封装函数的防抖与节流业务【闭包+延迟器】
21.路由跳转与传递参数
在这里避免因为鼠标滑动过于频繁导致页面出现卡顿现象,使用了编程式导航,点击事件结合自定义属性,把点击事件放在了a标签的父标签上,为a标签添加了自定义属性categoryname和category*id
22.过渡动画
前提:组件/元素务必要有v-if/v-show指令才可以进行过渡动画
在要执行过渡动画效果的标签外包裹<transition>标签,如下
23.mockjs模拟数据
如果想mock一些虚拟数据,需要用到mockjs插件
mockjs发的请求不会向后台服务器发送,会被浏览器拦截,模拟的数据用的时候当做真实数据就好
①下载安装mockjs插件
npm install mockjs
②使用步骤
第一步:在src目录下创建mock文件夹,这个文件夹是用来提供假数据的
第二步:准备JSON假数据,在mock文件夹中创建相应的json文件(数据弄完之后记得格式化一下json文件,有空格的话项目跑不起来)
第三步:把mock中数据需要的图片放置在public文件夹中(public文件夹在打包的时候,会把相应的资源原封不动的打包到dist文件夹中)
第四步:在mock文件夹下创建mockServe.js文件,通过mockjs模块实现模拟数据
// 先引入mockjs模块
import Mock from "mockjs";
// 把JSON数据格式引入进来
// banner.json和floor.json没有对外暴露,但是在这里可以引入
// 原因:webpack默认对外暴露:图片、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中引入mockServe.js文件(至少需要执行一次才能模拟数据)
注:mock发请求不能再利用之前的request.js文件,因为它们的根路径不同,一个/mock,一个/api,所以创建mock专属的mockRequest.js文件
// 对于axios进行二次封装
import axios from "axios";
// 引入进度条nprogress
import nprogress from "nprogress";
// 引入进度条样式
import "nprogress/nprogress.css";
// 利用axios对象的方法create,创建一个axios实例
// request其实就是axios,就是稍微配置一下
const mockRequests = axios.create({
// 配置对象
// 基础路径,发请求的时候,路径中会出现api
baseURL: "/mock",
// 代表请求超时时间为5s
timeout: 5000,
});
// 请求拦截器
mockRequests.interceptors.request.use((config) => {
// 进度条开始
nprogress.start();
// config:配置对象,对象里面有一个属性很重要,headers请求头
return config;
});
// 响应拦截器
mockRequests.interceptors.response.use(
(res) => {
// 进度条结束
nprogress.done();
// 响应成功的回调函数,返回获取到的数据
return res.data;
},
(error) => {
// 响应失败的回调函数
return Promise.reject(new Error("false"));
}
);
// 对外暴露
export default mockRequests;
在index.js中处理请求
24.swiper轮播图插件的使用
(1)html页面使用swiper插件
①下载安装
②使用步骤
第一步:引包(js/css)swiper-bundle.min.js和swiper-bundle.min.css
<link rel="stylesheet" href="dist/css/swiper-bundle.min.css">
<script src="dist/js/swiper-bundle.min.js"></script>
第二步:页面中的结构务必要有
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">Slide 1</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- 如果需要滚动条 -->
<div class="swiper-scrollbar"></div>
</div>
第三步(前提:页面当中务必要有结构):new swiper实例(轮播图添加动态效果)
(2)组件中使用swiper插件
①下载安装
npm i --save swiper@5
②使用步骤
第一步:引包
在使用轮播图的组件中引入swiper
因为在项目中有多个组件使用轮播图,所以在入口文件main.js中引入样式
第二步:页面中的轮播图结构要有
第三步(前提:页面当中务必要有结构):new swiper实例(轮播图添加动态效果)
这里有个问题,new swiper实例应该放在哪里,因为dispatch当中涉及到异步语句,导致v-for遍历的时候页面结构还不完整,因此不可以直接放到mounted()生命周期函数里
解决方法一:在mounted()生命周期函数里使用setTimeout定时器
setTimeout(()=>{
var mySwiper = new Swiper ('.swiper', {
direction: 'vertical', // 垂直切换选项
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
},2000)
解决方法二(最完美的方法):使用watch监听器+nextTick()(watch监听器与methods并排写)
// 使用watch监听器(watch监听器的使用只能保证数据已经有了,但是不能保证v-for执行完毕,即结构渲染结束)
watch: {
// 监听bannerList数据的变化,数据从空到有四个元素
bannerList: {
handler(newValue, oldValue) {
// 如果执行了handler方法,代表数据已经发生了变化
// nextTick():在下次DOM更新,循环结束以后,执行延迟回调,在修改数据之后,立即使用这个方法,获取更新后的DOM
this.$nextTick(() => {
var mySwiper = new Swiper('.swiper-container', {
// direction: 'vertical', // 垂直切换选项
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
// 点击小圆圈也可以进行切换图片
clickable: true
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
})
}
}
}
25.日常工作中的工作步骤
①书写静态页面+静态组件拆分(pages/views文件夹)
②发请求获取数据(写api)(api文件夹)
③仓库存储数据(vuex三连环)(store文件夹)
④组件获取仓库数据,动态展示数据(pages/views文件夹)
26.路由组件被复用的时候,要在复用他的父组件中进行向仓库请求获取数据的操作
父组件向子组件传值
27.组件通信的方式有哪些?
props:用于父子组件通信
自定义事件:@on @emit 可以实现子给父通信
全局事件总线:$bus 全能
pubsub-js:vue当中几乎不用 全能
插槽
vuex