一. 脚手架
vue官方定义了一套标准的项目结构,vue项目都要基于这套标准结构开发
1.安装可以反复创建脚手架项目的命令行工具
a.npm默认是国外的,访问慢而且不稳定
1)淘宝镜像:npm config set registry http://registry.npm.taobao.org
2)备选方案:npm i -g cnpm --registry=http://registry.npm.taobao.org (cnpm -v 查看版本)
b.用npm或cnpm安装反复生成脚手架项目的命令行工具:
1). 用npm安装: npm i -g @vue/cli //如果说FEXIST错误,可进入出错提示中的路径,默认为: C:\Users\登录操作系统的用户名\AppData\Roaming\npm\node_modules 删除@vue文件夹
2). 如果npm出错,可换成: cnpm i -g @vue/cli
c.验证vue/cli是否安装成功: vue -V (看到版本号说明安装成功) //i安装 g全局
2.用脚手架工具,创建一个项目的标准文件夹结构的副本:
a.找到要创建的文件夹位置;
b.在当前位置,地址栏输入cmd,在当前路径打开命令行窗口
c.命令行运行:
(1)vue.create xxx;
(2)选择版本:请选择预设:(1)默认vue2 (2)默认vue3 (3)手工选择功能
(3)选择你需要的项目功能:
(4)选择一个版本 :
(5)是否启用history作为路由:
(6)你想要在哪里放配置:
(7)为将来的项目保存一个预设:
(8)安装成功:npm run serve 运行脚手架
二. 脚手架文件结构
问题:所有JS文件都堆在一个JS文件夹里,管理和使用非常混乱
解决:真实的项目,都是把不同用途的JS文件,分别放在不同的文件夹中分类保存
脚手架的项目结构:
(1) public文件夹 ---- 开发过程中,很少修改
a).单页面应用唯一完成的HTML页面
b).img文件夹
c).第三方开发压缩过的css或者js源代码
(2) src文件夹 ---- 自己编写的一切源代码
a).assets文件夹:自己开发的一些通用的工具代码
b).components文件夹:包含所有的全局组件或子组件片段
c).router文件夹:包含路由器对象和路由字典的文件夹
d).views文件夹:包含所有当作页面使用的组件
e).App.vue:保存唯一完成HTML页面的<div id="app">以及内部和页头和<router-view>
f).main.js:保存整个项目唯一的newVue({})代码
vue文件:
(1)问题: 传统的使用js文件创建组件的方式,有两个问题:
a. HTML内容是写在模板字符串中,既没有提示,又没有颜色区分!极其不便于代码编写和纠错!
b. 在js文件中,就没有编写css内容的位置!
(2). 解决: vue脚手架中首创了一种新的文件格式: .vue文件,同时包含:
a. <template></template>: 专门包含当前组件的HTML内容,既有提示,又有颜色区分!极其有利于代码的编写和纠错
b. <script></script>专门包含当前组件的js对象
c. <style></style>专门包含当前组件的CSS代码
所以,将来vue脚手架项目中,只要创建一个组件(全局组件,子组件,页面组件),一律都要创建为.vue文件。
三. ES6的模块化开发
包含一组功能或数据的一个整体
旧网页中:必须将所有的js文件和对象先统一引入到页面的内存中。然后再分配
解决:如果一个功能向使用另一个功能内容,直接引入,不需要经过HTML页面统一引入
如何在脚手架中使用模块化开发:
(1). 如果一个模块文件的内容想被别人引入并使用,必须使用:
export default { 组件内容 }
抛出一个模块
(2). 如果一个模块想使用另一个模块抛出的内容,必须使用:
import 别名 from "要引入的模块文件的相对路径"
结果: 在当前模块中使用"别名",就等效于使用引入的模块内容
特殊: 如果组件内容非常简单,没有任何js代码,只有HTML和css代码,则不用export default { ... },默认也是一个模块
四. 项目的实现
1.准备:
问题:vue脚手架天生不带axios,所以用axios必须单独安装
解决:项目文件夹 npm i -save axios 结果:node_modules文件夹多一个axios文件夹
如何:main.js中new Vue()之前
引入:import axios from "axios"
vue原型对象添加:Vue.prototype.axios=axios
统一配置基础路径属性:axios.defaults.baseURL="http://服务器端同一域名"
结果:任何位置发送axios请求,只写/相对路径即可,不用反复填写域名
(1). 将图片和第三方文件放入public文件夹下
(2). 所有页面都需要的基础的css文件内容,应该放在App.vue中的<style>里。
因为App.vue中包含<router-view>,所以,放在App.vue中的css样式,是所有页面共享的
实现首页:
(1). 删除src/views/Home.vue,新建Index.vue,其中包含三部分内容:
a. <template>: 首页主体内容的HTML代码
比如: 从旧项目public下index.html中复制首页的主体内容html代码(<main></main>及其内容),粘贴到新项目src/views/Index.vue中的<template>标签内。
b. <style>: 首页所需的css样式
比如: 从旧项目public下css/index.css文件中,全选复制所有css代码,粘贴到新项目src/views/Index.vue中<style>标签内
(2). 必须去路由器对象(src/router/index.js)中配置首页的路由字典:
import Index from "../views/Index"
const routes = [
{
path: '/',
component: Index
},
(3). 实现首页商品数据的绑定:
a. 在首页组件的mounted中使用axios请求来首页的6个商品数据:
b. 在首页组件中添加data(){ return { }},并在data中定义四个变量:
p1:{price:0}, p2:{price:0}, p3{price:0}, others:[]
问题: 因为在axios请求回来之前,vue就要使用p1,p2,p3中的price调用toFixed()函数,所以p1,p2,p3中的price不能是空的!所以,p1,p2,p3中都要有一个price:0属性,临时占位。
c. 在首页组件的mounted中将请求回来的result.data中的商品分别放到p1,p2,p3和others变量中
this.p1=result.data[0];
this.p2=result.data[1];
this.p3=result.data[2];
this.others=result.data.slice(3);
3. 实现点首页商品,跳转到详情页查看详情:
(1). 先在src/views文件夹下,删除About.vue,新建Details.vue,其中也包含三部分内容:
a. <template>: 包含详情页的HTML内容
比如: 从旧项目public/product_details.html中,复制详情页的主体部分HTML内容,粘贴到新项目src/views/Details.vue中<template>标签中
b. <style>: 包含详情页的css样式
比如: 从旧项目public/css/product_details.css中,复制详情页的所有css代码,粘贴到新项目src/views/Details.vue中<style>标签中
c. <script>: 包含详情页的组件对象内容
(2). 去新项目src/router/index.js中引入详情页.vue,并配置路由字典
import Details from "../views/Details"
const routes = [
{...},
{
path:"/details/:lid",
component: Details,
props:true
}
注释:{ path:"/about", ... }
(3). 在Details.vue中mounted()中发送axios请求,获得指定编号的商品的详细信息:
a. 获得地址栏中的参数值: props:[ 'lid' ] //复习vue第四天的SPA应用
b. 在mounted中发送axios请求,获得指定编号的商品信息
(4). 将获得的商品详细信息绑定到页面上:
a. 先创建data(){ return {} },并在data中定义页面所需的变量
b. 在mounted中将获得相应结果中的商品对象的属性,赋值给data中的变量
c. 在Details.vue中51行开始,绑定商品的title, subtitle, price, promise四个属性
(5). 回到Index.vue中,为每个商品上的"查看详情"按钮,绑定详情页地址
坑: 服务器端返回的商品对象.href属性地址,还是旧项目的地址格式:
product_details.html?lid=5
解决: 将旧地址按=切割,取第二部分商品编号,再和/details/拼接为vue需要的地址
<router-link :to="`/details/${p1.href.split('=')[1]}`";
(6). 为了防止axios请求回来之前p1.href.split()报错,应该:
data(){
return {
p1:{ price:0, href:"" },
p2:{ price:0, href:"" },
p3:{ price:0, href:"" },
others:[]
//others中不用属性占位也不会报错,是因为others中元素个数为0时,v-for一次都不执行!就不会读到页面中的p.href.split()。所以,数组中不用占位,只有单个对象绑定时,才用属性占位。
}
}
五. 懒加载
单页面应用的致命问题: 首屏加载极慢。因为单页面应用默认必须在首次请求时,把所有页面组件都下载到客户端本地
npm run serve时,vue脚手架会将所有.vue文件的代码都打包压缩到一个js文件中,再引入唯一完整的index.html文件中。所以,这个包含所有代码的js文件会很大
懒加载,2种:
(1). 异步延迟加载(脚手架默认):
a. 暂时不要把除首页之外的其它代码import进程序中!
b. 在路由字典中定义,只有当用户访问到某个组件时,才动态引入这个组件。
问题: 虽然不会在首屏下载details.js,但是却会在后台异步下载details.js,偷跑流量
彻底的懒加载:
a. 如果用户不访问其它页面,则根本一点儿都不下载其它页面的内容
b. 如何: 在vue脚手架中添加一个配置信息:
1). 在vue脚手架项目的根目录下创建vue.config.js
2). 在vue.config.js中添加配置信息,禁止提前下载其它组件
module.exports={
chainWebpack:config=>{
config.plugins.delete("prefetch")
//删除index.html开头的带有prefetch属性的link,不要异步下载暂时不需要的页面组件文件
},
}
vue.config.js
结果: 除首页之外的其它页面组件内容,一点都不提前下载。只有当用户点连接,访问到一个页面时,才临时下载这个页面组件的内容
问题: 因为当用户访问页面时,临时去下载页面的内容,加载速度稍慢。
六. http-proxy 代理跨域
问题:
(1). CORS: 优点: 仅服务器端修改即可; 缺点: 必须知道客户端的具体IP地址
(2). JSONP: 优点: 不需要知道客户端具体IP地址; 缺点: 需要客户端和服务器端都作出调整!
(3). 如果服务器端觉得CORS也不好用,JSONP也不好用,干脆就没有做跨域!单靠前端能否跨域?
解决: 利用vue脚手架中自带的http-proxy代理程序来跨域访问。详细信息可查看官方文档: https://cli.vuejs.org/config/#devserver-proxy
在vue脚手架配置文件(vue.config.js)中,添加一个新的配置项
module.export={
... ... ,
devServer: {
proxy: {
'/api': { //为所有服务器端接口起一个别名前缀,为了和vue脚手架中其它页面的路由地址区分
target: `http://服务器端接口地址统一前缀`,
changeOrigin: true, //跨域
pathRewrite: {
//因为真实的服务器端地址中是不包含/api的,所以
'^/api' : '' //应该将程序中的/api删除(替换为空字符串),再和target中的基础路径拼接起来作为发送到服务器的最终请求地址。
}
}
}
}
}
(2). 在main.js中,axios的baseURL就不能写真实的服务器地址,应该写devServer中定义的所有服务器端地址的别名前缀:
axios.defaults.baseURL="/api"
(3). 在组件内使用axios发送请求时,依然使用接口的相对地址就可以: this.axios.get("/index")。
this.axios.get("/index")
↓axios会将baseURL和"/index"拼接
this.axios.get("/api/index")
所有↓带有/api前缀的路径都会交给devServer程序处理
devServer —— 不使用ajax,也能发送http请求的程序
↓先将相对路径中的/api去掉
/index
↓再和提前配置好的这是服务器地址target拼接
http://localhost:5050/index
↓向真实的服务器接口地址发送请求
devServer接到响应结果
↓
返回给axios.then(result=>{})继续处理
整个过程因为没有用到ajax,所以不受浏览器同源策略的限制。
七. 组件间样式冲突
脚手架项目在运行时会将所有组件的js打包为一个js文件,也会将所有组件的css打包为一个css文件。不同组件中的css最终会放在一个文件中并存!同名的选择器之间一定会发生冲突!
(1).解决: <style scoped>
a. 原理: 自动给组件以及组件内所有元素添加随机自定义属性,用不同的自定义属性名来区分不同组件中的元素。
b.问题:
1). 对每个HTML元素都加随机自定义属性,效率低
2). 不一定总是有效的!
c. 示例: 使用<style scoped>避免组件间样式冲突
(2) 好的解决: 为每个组件起一个专门的class名
a. 如何: 2步
1). 在定义组件时,组件的唯一父元素上就要提供一个唯一的class名
2). 凡是这个组件下的选择器,都要以这个组件的唯一父元素上的class作为开头!
b. 优点: 不需要vue多做任何处理就能避免样式冲突,效率高
c. 示例: 使用class名避免组件间样式冲突:
八. watch + 事件修饰符 +防抖
如何实现一边输入 一边就执行操作
解决: 使用vue中的监视函数watch
何时: 只要希望一个变量一发生变化,就能立刻执行一个我们自定义的任务
如何:
组件对象中{
... ,
data(){
return {
变量:值
}
},
watch:{
变量(){
要执行的操作!
}
}
}
只要变量一发生变化,就自动调用watch中的同名函数
问题: 如何实现在文本框上点回车也能搜索?
解决: <input @keyup="search">
@keyup的问题:
(1). 当@keyup和watch同时存在时,执行的操作可能重复执行两遍: 内容变化,变量变化,watch执行;又因为触发了keyup,所以事件处理函数也执行。
(2). 除了按字母和回车键之外,按键盘上其它无关的按键也会触发搜索操作 特别没必要!
(3). 应该只有按照回车键时才能执行搜索操作
解决: 就要用vue中的事件修饰符:
(1). 事件修饰符: 对传统的DOM事件对象中常用方法和属性的简写。
(2). 想判断键盘按键的编号:
a. DOM中: if(e.keyCode==13)
b. vue中: @keyup.13 //只有按在13号键上才执行操作
(3). 其它事件修饰符常用的操作:
a. 停止冒泡:
1). DOM中: e.stopPropagation()
2). vue中: @事件名.stop="处理函数"
b. 阻止默认行为:
1). DOM中: e.preventDefault()
2). vue中: @事件名.prevent="处理函数"
10. 问题: 如果用户还没有输入完关键词,就去服务器频繁查找,既没必要,又浪费服务器端资源,降低效率!
解决: 防抖(debounce)
(1). 什么是: 必须等待用户操作停止后一段时间,才执行正式的操作!
(2). 何时: 今后只要希望阻止用户频繁操作向服务器发送请求时,都可以用防抖。
(3). 如何: 定时器