前端Vue核心
开发一个前端模块可以概括为以下几个步骤:
(1)写静态页面、拆分为静态组件;
(2)发请求(API);
(3)vuex(actions、mutations、state三连操作);
(4)组件获取仓库数据,动态展示;
1、vue文件目录分析
node_modules文件夹 项目依赖 比如babel:es6语法翻译为es5兼容性更好 以及vue框架等
public文件夹:静态资源 ,webpack进行打包的时候会原封不动打包到dist文件夹中。
pubilc/index.html:是一个模板文件,作用是生成项目的入口文件,webpack打包的js,css也会自动注入到该页面中。我们浏览器访问项目的时候就会默认打开生成好的index.html。
src文件夹(程序员代码文件夹)
assets:一般也是放置静态资源(一般放置多个组件共用的静态资源),需要注意,放置在assets文件夹里面静态资源,在webpack打包的时候,webpack会把静态资源当做一个模块,打包Js文件里面。 components: 非路由组件(全局组件),其他组件放在views或者pages文件夹中 App.vue: 唯一的根组件 main.js: 程序入口文件,最先执行的文件
babel.config.js: 配置文件(babel相关)
package.json: 项目的详细信息记录 项目中有哪些依赖 项目怎么运行
package-lock.json: 缓存性文件(各种包的来源)
两者的区别解释1
package.json
里面定义的是版本范围(比如^1.0.0
),具体跑npm install
的时候安的什么版本,要解析后才能决定,这里面定义的依赖关系树,可以称之为逻辑树(logical tree)。
node_modules
文件夹下才是npm实际安装的确定版本的东西,这里面的文件夹结构我们可以称之为物理树(physical tree)。安装过程中有一些去重算法,所以你会发现逻辑树结构和物理树结构不完全一样。
package-lock.json
可以理解成对结合了逻辑树和物理树的一个快照(snapshot),里面有明确的各依赖版本号,实际安装的结构,也有逻辑树的结构。其最大的好处就是能获得可重复的构建(repeatable build),当你在CI(持续集成)上重复build的时候,得到的artifact是一样的,因为依赖的版本都被锁住了。在npm5以后,其内容和
npm-shrinkwrap.json
一模一样。两者的区别解释2
package-lock.json是在运行“npm install”时生成的一个文件,用于记录当前状态下项目中实际安装的各个package的版本号、模块下载地址、及这个模块又依赖了哪些依赖。
为什么有了package.json,还需要package-lock.json文件呢,当node_modules文件夹并不存在或被删除时,需要用到npm install重新装载全部依赖时,通过package-lock.json可以直接表明下载地址和相关依赖,相对下载速度也更快,也不容易报错。
2、项目配置
2.1 项目运行,浏览器自动打开
找到package.json 文件 加上 --open
"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、项目路由的分析(整体划分组件)
路由组件和非路由组件的区别
组件切换基于组件内的状态变化(data)
路由切换基于浏览器URL的变化
最大的区别在于刷新浏览器后:
vue会匹配当前URL,并且渲染匹配到的路由定义的组件
而基于组件内部状态切换的组件
会被重置成初始状态
两个非路由组件定义在components当中 Headeer组件和Footer组件
- 找静态组件将样式等复制出来(由此可见less和scss等的代码健壮性)
- 安装less等插件
4、配置组件页面样式(less 第一个问题)
组件页面的样式使用的是less样式,浏览器不识别该样式,需要下载相关依赖
npm install --save less less-loader@5
注意 如果安装最新版的less 会报函数错误
建议直接安装指定的低版本
如果想让组件识别less样式,则在组件中设置
<script scoped lang="less">
注意导入的组件需要大写
5、清除vue页面默认的样式
vue是单页面开发,我们只需要修改public下的index.html文件
<link rel="stylesheet" href="<%= BASE_URL %>reset.css">
@import "./iconfont.css";
/* 清除内外边距 */
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote,
dl, dt, dd, ul, ol, li,
pre,
fieldset, lengend, button, input, textarea,
th, td {
margin: 0;
padding: 0;
}
/* 设置默认字体 */
body,
button, input, select, textarea { /* for ie */
/*font: 12px/1 Tahoma, Helvetica, Arial, "宋体", sans-serif;*/
font: 12px/1.3 "Microsoft YaHei",Tahoma, Helvetica, Arial, "\5b8b\4f53", sans-serif; /* 用 ascii 字符表示,使得在任何编码下都无问题 */
color: #333;
}
h1 { font-size: 18px; /* 18px / 12px = 1.5 */ }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
h4, h5, h6 { font-size: 100%; }
address, cite, dfn, em, var, i{ font-style: normal; } /* 将斜体扶正 */
b, strong{ font-weight: normal; } /* 将粗体扶细 */
code, kbd, pre, samp, tt { font-family: "Courier New", Courier, monospace; } /* 统一等宽字体 */
small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */
/* 重置列表元素 */
ul, ol { list-style: none; }
/* 重置文本格式元素 */
a { text-decoration: none; color: #666;}
/* 重置表单元素 */
legend { color: #000; } /* for ie6 */
fieldset, img { border: none; }
button, input, select, textarea {
font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */
}
/* 重置表格元素 */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* 重置 hr */
hr {
border: none;
height: 1px;
}
.clearFix::after{
content:"";
display: block;
clear:both;
}
/* 让非ie浏览器默认也显示垂直滚动条,防止因滚动条引起的闪烁 */
html { overflow-y: scroll; }
a:link:hover{
color : rgb(79, 76, 212) !important;
text-decoration: underline;
}
/* 清除浮动 */
.clearfix::after {
display: block;
height: 0;
content: "";
clear: both;
visibility: hidden;
}
5、路由组件相关配置
01.安装路由
npm install --save vue-router
可查看package.json判断组件是否完成
02.创建专用文件夹放置路由组件
创建一个pages文件夹,并创建路由组件
在上面分析的时候,路由组件应该有四个: Home、 Search、Login、 Register
- -components文件夹:经常放置的非路由组件(共用全局组件)
- -pages |views文件夹:经常放置路由组件
03.配置路由
创建router文件夹,并创建index.js进行路由配置
//引入vue-router路由插件
import VueRouter from "vue-router";
//引入Vue
import Vue from "vue";
//使用插件
Vue.use(VueRouter);
import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Register from '@/pages/Register'
import Search from '@/pages/Search'
export default new VueRouter({
routes: [
{
path: "/home",
component: Home
},
{
path: "/search",
component: Search
},
{
path: "/login",
component: Login
},
{
path: "/register",
component: Register
},
]
})
在main.js中引入注册
import Vue from 'vue'
import App from './App.vue'
//01.此处
import router from '@/router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
//02.此处
router
}).$mount('#app')
04.总结
路由组件和非路由组件区别:
路由组件与非路由组件的区别?
1:路由组件一般放置在pages|views文件夹,非路由组件一般放置components文件夹中
2;路由组件一般需要在router文件夹中进行注册(使用的即为组件的名字),非路由组件在使用的时候,一般都是以标签的形式使用
3:注册完路由,不管路由路由组件、还是非路由组件身上都有$route、I $router属性
$route:一般获取路由信息【路径、query.params等等】
$router:一般进行编程式导航进行路由跳转【push/replace】
05.路由的跳转
路由的跳转有两种形式:
声明式导航router-link,可以进行路由的跳转
编程式导航push|replace,可以进行路由跳转
编程式导航:声明式导航能做的,编程式导航都能在,
但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。
6、footer组件显示与隐藏(Header fotter)
实现的基本思路
- footer在登录注册页面是不存在的,所以要隐藏,v-if 或者 v-show
- 这里使用v-show,因为v-if会频繁的操作dom元素消耗性能,v-show只是通过样式将元素显示或隐藏
实现的两种方式
-
配置路由的时候,可以给路由配置元信息meta,
<Footer v-show="$route.meta.show"></Footer>
{ path: "/register", component: Register, meta: { show: false } },
-
在路由的原信息中定义show属性,用来给v-show赋值,判断是否显示footer组件
<Footer v-show="$route.path=='/home'||$route.path=='/search'"></Footer>
7、路由传参(Header fotter)
01.路由跳转有几种方式?
比如:A->B
声明式导航:router-link(务必要有to属性),可以实现路由的跳转
编程式导航:利用的是组件实例的$router.push/replace方法,可以实现路由的跳转。[(可以书写一些自己业务)
02.路由传参的两种方式params query
01.字符串传参
研究实例
按钮点击将参数传入下个页面
params传参实现方法
第一步:搜索框数据双向绑定
第二步:路由占位
第三步:函数路由传参
gosearch() { this.$router.push('/search' + '/' + this.keyword) }
query传参
需要使用**?以及kv**的方式进行传参
gosearch() { this.$router.push('/search' + '/' + this.keyword + '?k=' + this.keyword.toUpperCase()) }
数据展示
组建内部
<h1>我是params参数{{$route.params.keyword}}</h1> <h1>我是query参数{{$route.query.k}}</h1>
02.模板字符串
就是个ES6写法
this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()}`)
03.对象写法
push后面装载的需要是一个参数
第一步
给路由添加名字
{ path: "/search/:keyword", component: Search, meta: { show: true }, name: "search" },
第二步
传入参数
gosearch() { // 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', query: { k: this.keyword.toUpperCase() }, params: { keyword: this.keyword } }) }
04.面试题
面试题1:
1.路由传递参数(对象写法)path是否可以结合params参数一起使用?
尝试一起使用
gosearch() { //路由传递参数(对象写法)path是否可以结合params参数一起使用? this.$router.push({ path: 'search', params: { keyword: this.keyword } }) }
发现路由无法跳转
答:路由跳转传参的时候,对象的写法可以是name、path形式,但是需要注意的是,path这种写法不能与params参数写法一起使用
面试提2
2.如何指定params参数可传可不传?
尝试在路由占位的情况下不传入params参数
gosearch() { //如何指定params参数可传可不传? this.$router.push({ path: 'search', query: { k: this.keyword.toUpperCase() }, }) }
如果路由要求传递params参数,但是你就不传递params参数,发现一件事情,URL会有问题的
如何指定panams参数可以传递、或者不传递,在配置路由的时候,在占位的后面加上一个问号【params可以传递或者不传递】
其实类似于正则 ?就是表示参数可传可不传
{ path: "/search/:keyword?", component: Search, meta: { show: true }, name: "search" },
面试提3
params参数可以传递也可以不传递,但是如果传递是空串,如何解决?
尝试
gosearch() { //**params参数可以传递也可以不传递,但是如果传递是空串,如何解决?** this.$router.push({ path: 'search', params: { keyword: '' } }) }
发现路径也出现了问题 seach不显示
使用undefined解决:params参数可以传递、不传递(空的字符串)
gosearch() { //**params参数可以传递也可以不传递,但是如果传递是空串,如何解决?** this.$router.push({ path: 'search', params: { keyword: ''|| undefined } }) }
面试提4
路由组件能不能传递props数据?
可以的:三种写法 但是只能传递params参数,
params 布尔值写法
首先 router路由的位置添加props布尔值写法
然后 组建props的位置添加变量定义
params 对象写法
首先 router路由的位置添加props对象写法
然后跳转的组件直接使用即可
params 函数写法
首先 router路由的位置添加props函数写法
然后跳转的组件直接使用即可
8、多次执行相同的push问题(Header fotter)
问题描述
编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误?
–路由跳转有两种形式:声明式导航、编程式导航
–声明式导航没有这类问题的,因为vue-router底层已经处理好了
多次执行相同的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,编程式导航还是会有类似错误
如何解决
首先需要明白 在当前创建的组件当中
this:当前组件实例(search)
this.
r
o
u
t
e
r
属
性
:
当
前
的
这
个
属
性
,
属
性
值
V
u
e
R
o
u
t
e
r
类
的
一
个
实
例
,
当
在
入
口
文
件
注
册
路
由
的
时
候
,
给
组
件
实
例
添
加
router属性:当前的这个属性,属性值VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加
router属性:当前的这个属性,属性值VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加router| $route属性
push是VueRouter.prototype的一个方法,在router中的index文件中重写该方法即可(看不懂也没关系,这是前端面试题)
https://www.bilibili.com/video/BV1Vf4y1T7bw?p=10&spm_id_from=pageDriver 不理解可以看下视频 第10节 第15分钟
本质上虽说是重写 实际上还是调用了原来的方法 只是加工了一下
此代码复制到router文件夹下即可
//1、先把VueRouter原型对象的push,保存一份
let originPush = VueRouter.prototype.push;//push方法
let originReplace= VueRouter.prototype.replace//方法
//2、重写push|replace
//第一个参数:告诉原来的push,跳转的目标位置和传递了哪些参数
//第二个参数:成功回调
//第三个参数:失败的回调
VueRouter.prototype.push = function (location,resolve,reject){
//如果传递了成功和失败的回调函数 则不做处理
if(resolve && reject){
call| |apply区别
//相同点,都可以调用函数一次,都可以篡改函数的上下文一次
//不同点: call与apply传递参数: call传递参数用逗号隔开,apply方法执行,传递数组
originPush.call(this,location,resolve,reject)
}else{//否则自己添加两个函数
originPush.call(this,location,() => {},() => {})
}
}
VueRouter.prototype.replace = function (location,resolve,reject){
if(resolve && reject){
call| |apply区别
//相同点,都可以调用函数一次,都可以篡改函数的上下文一次
//不同点: call与apply传递参数: call传递参数用逗号隔开,apply方法执行,传递数组
originReplace.call(this,location,resolve,reject)
}else{
originReplace.call(this,location,() => {},() => {})
}
}
9.Home组件拆分(home)
拆成7个组件 按功能划分
部分通用的组件需要配置为全局组件
10、Home三级联动全局组件完成(home)
三级联动组件完成
—由于三级联动,在Home、Search、Detail,把三级联动注册为全局组件。好处:只需要注册一次,就可以在项目任意地方使用
01.创建基本组件
创建组件和编写基本html和css
02.组件全局注册
注意 注册全局组件需要在main.ja文件中操作
第一步 导入组件
//将三级联动组件注册为全局组件 import TypeNav from '@/pages/Home/TypeNav';
第二步 注册
//第一个参数:全局组件名字,第二个参数:全局组件 Vue.component(TypeNav.name,TypeNav);
03.组件的使用
在Home组件中使用该全局组件
<template> <div> <!-- 三级联动全局组件已经注册为全局组件,因此不需要引入--> <TypeNav/> </div> </template>
全局组件可以在任一页面中直接使用,不需要导入声明
11、封装ListContainer组件
01.封装样式和css
注意改变图片的路径
12、封装TodayRecommend组件
01.封装样式和css
注意改变图片的路径
13、封装Rank组件
01.封装样式和css
注意改变图片的路径
14、封装Like组件
01.封装样式和css
注意改变图片的路径
15、封装Floor组件
01.封装样式和css
注意改变图片的路径
16、封装Brand组件
01.封装样式和css
注意改变图片的路径
17.封装axios
axios二次封装:XMLHttpRequest、fetch、JQ、axios
请求拦截器、响应拦截器:请求拦截器,可以在发请求之前可以处理一些业务、响应拦截器,当服务器数据返回以后,可以处理一些事情
安装axios
npm install --save axios
观察package.json查看是否安装成功
01.封装request文件
axios中文文档,包含详细信息。
https://www.kancloud.cn/yunye/axios/234845
在根目录下创建api文件夹,创建request.js文件。
内容如下,当前文件代码还比较少,后续有需求可以增添内容。
import axios from "axios";
//1、对axios二次封装
const requests = axios.create({
//配置对象
//基础路径,requests发出的请求在端口号后面会跟改baseURl
baseURL:'/api',
//代表请求超时的时间是5s
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;
02.封装api统一管理文件
在文件夹api中创建index.js文件,用于封装所有请求
将每个请求封装为一个函数,并暴露出去,组件只需要调用相应函数即可,这样当我们的接口比较多时,如果需要修改只需要修改该文件即可。
如下所示:
//当前模块,API进行统一管理,即对请求接口统一管理
import requests from "@/api/request";
//首页三级分类接口 /api/product/getBaseCategoryList get请求 无参数
export const reqCateGoryList = () => {
return requests({
url: '/product/getBaseCategoryList',
method: 'GET'
})
}
当组件想要使用相关请求时,只需要导入相关函数即可,以上图的reqCateGoryList 为例:
import {reqCateGoryList} from './api'
//发起请求
reqCateGoryList();
03.解决跨域问题
什么是跨域:协议、域名、端口号不同请求,称之为跨域
http: //localhost:8080/#/home ----前端项目本地服务器
http://39.98.123.211 ----后台服务器
解决跨域问题:JSONP、CROS、代理而代理实现方案
在根目录下的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',
//因为都有api 所以不再路径重写
//pathRewrite:{'^/api':''}
}
},
}
}