webpack和脚手架
- 前端工程化
- webpack
- 问题
- 基本介绍
- 安装和配置
- 打包入口和出口文件
- 自动打包
- 生成预览页面
- 总结
- 加载器
- webpack用于vue
- 文件上线
- VUE CLI
前端工程化
什么是"前端工程化"?
目前来说,web业务日益复杂化和多元化,前端开发从WebPage模式为主转变为WebApp模式为主了。前端的开发工作在一些场景下被认为只是日常的一项简单工作,或只是某个项目的"附属品",并没有被当做一个"软件"而认真对待(无论是产品负责人还是开发者)。
在模式的转变下,前端都已经不是过去的拼几个页面和搞几个jq插件就能完成。当工程复杂就会产生许多问题,比如:
- 如何进行高效的多人协作?
- 如何保证项目的可维护性?
- 如何提高项目的开发质量?
- 如何降低项目生产的风险?
- …
前端工程化是使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化,其主要目的*为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间*,而前端工程本质上是软件工程的一种,因此我们应该从软件工程的角度来研究前端工程。
"前端工程化"里面的工程指软件工程,和我们一般说的工程是两个完全不同的概念。
- 工程是个很泛泛的概念,甚至可以认为建了一个git仓库就相对于新建了一个工程;
- 软件工程的定义是: “应用计算机科学理论和技术以及工程管理原则和方法,按预算和进度,实现满足用户要求的软件产品的定义、开发、和维护的工程或进行研究的学科”(GB/T11457-2006《信息技术 软件工程术语》)。
什么是模块化
传统开发模式的主要问题
-
命名冲突
jquery.js
和ext.js
都有 $,就会产生命名冲突
<script src="bootstrap.js"></script> <script src="ext.js"></script> <script> $('#') </script>
-
文件依赖
bootstrap.js
对jquery.js
有依赖性,所以和jquery.js
文件要放在bootstrap.js
文件之前,如果jquery.js
文件在后,会导致bs不起作用
<script src="jquery.js"></script> <script src="bootstrap.js"></script>
这两个问题在前端开发中长期存在
模块化主要就是解决上面的两个问题
模块化:就是将单独的功能封装到一个模块(文件)中,模块之间相互隔离,可以通过特定的接口公开内部成员,模块也可以依赖于其他模块
模块化的好处:方便代码重用,提升开发效率,并且方便后期维护
现阶段的模块化方案
- CommonJS:NodeJS模块系统具体实现的基石。
- AMD:异步模块规范,是RequireJS在推广过程中对模块定义的规范化产出的,推崇依赖前置,异步加载;
- CMD:是SeaJS 在推广过程中对模块定义的规范化产出的,推崇依赖就近,同步加载;
- UMD:兼容AMD和CommonJS规范的同时,还兼容全局引用的方式;
- ES6:ES6模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量;
其中AMD和CMD的语法类似,都是使用define和require来进行模块的定义和加载;
CommonJS 则使用的是moduel.exports、exports和require来紧密模块的定义和加载。
然而从本质上来讲,它们都没有解决根本问题:
- 性能低:浏览器最怕从服务器加载又小又多的文件;
- 模块之间的管理之类的问题;
- 都是JavaScript函数模拟实现的,需要第三方库的支持;
而ES6(ECMAScript2015)可以说是一个“跨时代”的更新,使得的JavaScript在原生语言上实现了模块化:export和import。
ES6的原生模块功能,它兼顾了规范、语法简约型和异步加载功能,还支持循环依赖。是不是很强大。
模块化规范
浏览器端模块化规范
- AMD:require.js
- CMD:sea.js
服务器端模块化规范
CommonJS
- 模块分为单文件模块与包
- 模块成员导出:module.exports和exports
- 模块成员导入:require (‘模块标识符’)
ES6 模块化
在 ES6 模块化规范诞生之前,javascript社区已经尝试提出并实践了 AMD、CMD、CommonJS 等模块化规范
但是,社区提供的这些模块化标准,还是存在一定的差异性与局限性,也并不是浏览器端与服务器端通用的模块化标准
- AMD和CMD 适用于浏览器端的js模块化
- CommonJS适用于服务器端的js模块化
因此,ES6在语言层面,提出了新的模块化规范,打通了浏览器端与服务器端的壁垒,实行统一的标准
正所谓,分久必合
ES6模块化规范主要包含如下几点
- 每个js文件都是一个独立的模块
- 暴漏模块成员使用 export 关键字
- 导入模块成员使用 import 关键字
ES6的入门文件
服务端使用ES6模块化
主要就是 node 中使用 ES6 模块化
node之前的模块化规范主要是 CommonJS,从 13.2.0 之后,开始支持 ES6 模块化
所以,在 node 中使用 ES6 模块化,有两种方式
使用babel
此种方法,在所有的 node 版本中都可以使用,但略显繁琐
- 安装 babel
- 项目根目录创建文件 babel.config.js
- 通过 npx babel-node index.js 执行运行程序
修改type属性
此种方法,在 13.2.0 之后可以使用,使用简单
创建 a.js
let a=10
let b=20
let c=30
let d=40
let f1=()=>{
console.log('f1');
}
export default{
a,
b,
c,
f1
}
创建 index.js
import a from './a.js'
console.log(a);
注意:
- 引入开发者自定义模块,要加路径
- 模块要带 .js 后缀名
- a.js 中使用 export default 导出的话,import 的名称可以任意
按需导出与按需导入
在ES6中每一个模块就是一个独立的文件,并且是自动采用严格模式,所以在编写ES6模块的时候要严格遵守严格模式的一些限制。
在文件中定义的变量、函数、对象外部是无法读取的。ES6模块的语法主要由两个命令构成:export和import。
- export用于对外暴露模块内部的接口和变量;
- 通常import用于引入其他模块暴露在外的接口。
使用 export 关键字直接放到要导出的变量或者函数前面
export let d=40
导入时
import {d} from './a.js'
也可使为导入的成员起名一个别名
import {d as age} from './a.js'
console.log(age);
- 模块中可以按需导出多个成员
- 模块中 export default 与 按需导出可以并存
- 注意:
export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
直接执行模块代码
模块中没有保留任何成员,导入此模块时直接执行代码即可
b.js
for(let i=0;i<10;i++){
console.log(i);
}
index.js
import './b.js'
export 导出 和 export default 导出的区别
-
使用 export 导出的导出什么,引入的时候使用的变量名就是什么
-
使用 export default 引入文件的时候变量名名字可以随便起名
ES6 模块化在浏览器中使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import obj from './a.js'
console.log(obj);
</script>
</body>
</html>
浏览器要使用ES6模块化需在 <script>
中添加上 type="module"
webpack
问题
- 文件依赖关系错综复杂
- 静态文件请求效率低
- 模块化支持不友好
- 浏览器对高级JS特性兼容性不好
webpack是近期最火的一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX)、coffee、样式(含less/sass)、图片等都作为模块来使用和处理。
webpack可以解决当前web开发中所面临的各种困境(我们主要开发的就是web程序,而浏览器对高级语法的支持不是很好,所以需要工具进行语法转换)
webpack 提供了友好的模块化 支持,以及代码压缩混淆、处理 js 兼容问题、性能优化等强大功能,从而让程序员将工作中心放到具体的功能实现上,提升开发效率和项目的可维护性
我们可以直接使用 require(XXX) 的形式来引入各模块,即使它们可能需要经过编译(比如JSX和sass),但我们无须在上面花费太多心思,因为 webpack 有着各种健全的加载器(loader)在默默处理这些事情
webpack的官网是 https://www.webpackjs.com/ ,文档地址是https://webpack.docschina.org/concepts/
基本介绍
在webpack中,一个css甚至一个字体都成为模块,彼此存在依赖关系,webpack就是处理模块间依赖关系的,并将它们进行打包。
各种文件格式通过特定的加载器loader编译后,最终统一生成为.js,.css,.png,.jpg等静态资源。
但是归根到底他就是有个.js的配置文件,一个构架好或者差都表现在这个配置文件中。
除了 webpack,尤大还开发了 Vite,未来可期,毕竟是官方工具
webpack的四个核心概念:
入口(entry)、输出(output)、加载器(loader)、插件(plugins)
-
entry 指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始,可以是单入口也可以是多入口
-
output 用来配置编译后的文件存储位置和文件名,只能有一个出口
- output中有path选项用来存放打包后文件的输出目录,必填
- filename用于指定输出文件的名称
- publicePath指定文件引用的目录
-
loader 对模块源代码进行转换 (webpack 本身只能处理Js模块),通常写在
rules
里module:{ rules:[ {test:/\.css/,use:['style-loader','css-loader'] } //css的配置 ] }
在module对象的rules属性中可以指定一系列的loader,每一个loader都必须包括test,use两个选项。
上面代码的意思是:webpack编译过程中遇到require()或import语句导入一个后缀为.css的文件时,它将先通过css-loader转换,再通过style-loader转换,然后打包。
- test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
- use 属性,表示进行转换时,应该使用哪个 loader。可以是字符串或者数组,数组从后面开始编译。
-
Plugins:可以用来解决loader无法实现的其他事。外置插件要先npm安装,然后导入插件用require()
extract-text-webpack-plugin插件可以把散落在各地的css提取出来,并合成一个文件
//导入插件 var ExtractTextPlugin=require('extract-text-webpack-plugin'); var config={ plugins:[ //重命名提取后的css new ExtractTextPlugin('main.css') ] };
webpack的优点如下:
-
webpack 遵循commonJS 的形式,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。
-
能被模块化的不仅仅是 JS ,所有的静态资源,例如css,图片等都能模块化,即以require的方式引入。
-
开发便捷,能替代部分 grunt/gulp 的工作,比如打包、压缩混淆、图片转base64等。
安装和配置
通过一个隔行变色案例讲解
步骤
- 初始化项目 npm init -y
- 跟目录下新建 src 目录,作为代码文件目录
- src 下新建 index.html和index.js
- index.html 中编写 html 代码
<ul>
<li>10</li>
<li>20</li>
<li>30</li>
<li>40</li>
<li>50</li>
<li>60</li>
<li>70</li>
<li>80</li>
<li>90</li>
<li>100</li>
</ul>
安装 jquery 插件
npm i jquery
编写 index.js
import $ from 'jquery'
$(function(){
$('li:odd').css('background-color','red')
$('li:even').css('background-color','blue')
})
上面使用了 ES6 模块化的语法,如果在 index.html 中直接引入 index.js,并运行,会提示浏览器无法识别 import 等 ES6 的新特性,所以需要安装 webpack
安装 webpack
https://webpack.docschina.org/guides/getting-started/
npm install webpack webpack-cli --save-dev
– save-dev 也可以写作 -D,表示是开发依赖,也就是开发阶段需要用到这个包,但项目上线运行后不需要
webpack 配置
根目录下新建 webpack.config.js
module.exports={
mode:'development' // 开发模式
}
或者
module.exports = {
mode: 'production' //上线
};
或者从 CLI 参数中传递:
webpack --mode=production
development 和 production的区别:
选项 | 描述 | 区别 |
---|---|---|
development | 会将 process.env.NODE_ENV 的值设为 development 。启用 NamedChunksPlugin 和 NamedModulesPlugin 。 | 开发者模式,容量大,但是代码清晰,加载速度慢 |
production | 会将 process.env.NODE_ENV 的值设为 production 。启用 FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin 和 UglifyJsPlugin . | 上线模式,容量小,代码压缩在一起,加载速度快 |
记住,只设置
NODE_ENV
,则不会自动设置mode
。
CLI
你也可以通过 CLI 使用 loader:
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
这会对 .jade
文件使用 jade-loader
,对 .css
文件使用 style-loader
和 css-loader
。
配置启动命令
打开 package.json ,在 scripts 属性中加入如下代码
"dev":"webpack"
打包
在终端运行如下命令
npm run dev
成功后会在跟目录创建 dist 目录,并生成一个 main.js 文件
运行
在 index.html 中,引入 main.js,再次运行 index.html 即可成功
打包入口和出口文件
入口文件
就是项目或程序被请求的时候,第一个被访问到的文件,此文件再找相对应的模块进行处理
在当前项目中,index.html 是入口文件,因为我们请求的就是 index.html
但是对于 webpack 来说,打包的入口文件是 index.js,因为 index.js 中引入了其他程序需要的模块,并编写了相应的逻辑代码
出口文件
打包之后的文件目录以及名称
webpack 有一些默认配置
- 入口文件:src/index.js
- 出口文件:/dist/bundle.js
重新配置入口文件和出口文件
下面的代码想更改入口文件和出口文件
const path = require('path')
module.exports = {
mode:'development', //配置打包模式为 开发模式
entry:path.join(__dirname,'src','index.js'), //入口文件,从哪个文件开始打包
output:{
path:path.join(__dirname,'dist'), //出口文件路径
filename:"bundle.js" // 出口文件名称,也就是打包之后的文件名称
},
}
const path = require('path')
module.exports = {
mode:'development',
entry:path.resolve(__dirname,'src','index.js'), //入口文件
output:{
path:path.resolve(__dirname,'dist'),
filename:'bundle.js'
}
}
join
和 resolve
都是路径拼接的方法
运行 npm run dev
命令查看打包结果
说明
- 建议输出文件名称使用 bundle.js,而且注意修改 index.html 中的引入文件为 bundle.js
自动打包
由于我们每次修改都需要重新运行一下 npm run dev,如果修改了很多枚举的麻烦,但是如果只是改革颜色,就要重新运行一下这是一个很麻烦的事情,所以这个时候我们就需要自动打包工具
不需要修改代码后重新运行 npm run dev 命令
https://webpack.docschina.org/guides/development/#using-webpack-dev-server
webpack-dev-server github 地址
https://github.com/webpack/webpack-dev-server
安装
npm install --save-dev webpack-dev-server
修改 pacjkage.json 中的启动命令
"dev":"webpack serve --open"
说明:在 v5 中使用上面的 webpacl serve 命令
但是在v5之前版本中使用 webpack-dev-server 命令
运行
npm run dev
会使用 webpack-dev-server 进行打包,成功后,会自动打开浏览器,地址为 localhost:8080
打包后,webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中
同时会将网站根目录中的文件 serve 到 localhost:8080
下 (译注: serve,将资源作为server的可访问文件)
可以通过如下地址访问
可见其在项目跟目录,所以要修改 src/index.html 的引入路径为
<script src="/bundle.js"></script>
或者
把 index 拷贝一份在 根目录下,修改引入路径为
<script src="/bundle.js"></script>
直接输入 http://localhost:8080/
就可以访问
但是有一个小问题,如果修改了src 的index.html 那还要再去复制份到根目录,所以为了不必要的麻烦,我们还需再下载插件来完成皆变的效果
生成预览页面
当前存在的问题:
localhost:8080 对应的是网站跟目录,index.html 在 src 目录下,访问起来比较麻烦
解决方案:
可以将 src 目录下的 index.html 拷贝一份到根目录下,这样访问 localhost:8080 时,就会默认渲染 index.html
但是如果修改完 index.html 后还需要手动拷贝比较麻烦,可以使用插件:HtmlWebpackPlugin
https://webpack.docschina.org/plugins/html-webpack-plugin/
安装 html-webpack-plugin
npm install --save-dev html-webpack-plugin
在 webpack.config.js 中编写代码
重新运行 npm run dev后
再次访问:npm run dev,就会运行index.html
但是跟目录下并没有 index.html ,因为其仍然创建在内存中
问题
问题1
安装完成后,运行报错
这是一个小细节,因为没有安装 loader-utils
解决方案
我们需要进行安装
npm install loader-utils -D
再运行 npm run dev
,就成功了
问题2
打包之后html 文件中 引入两个 bundle.js 文件的问题
webpack 使用 html-webpack-plugin 简化 html 的创建,此插件会为我们按照指定路径和名称生成一个 html 文件,作为网站的入口文件
但是在新版本中,其会自动在 html 文件中添加 bundle.js(出口文件)的引用
例如下面,我们已经手动在 body 中引入了 bundle.js
当我们打包运行后发现会出现两个 bundle.js 文件
手动引入的 dist 目录下的 bundle.js 和 插件自动引入的跟目录下的 bundle.js有时会发生很奇怪的事情(比如样式产生冲突),所以我们只需要(必须)引入一个 bundle.js 就可以了
解决方案
- 不要手动引入,就让插件自动引入即可
- 手动引入,不允许插件自动引入,只需要在 webpack.config.js 中增加如下配置,然后重新运行
nom run dev
总结
两个插件综合使用
- webpack-dev-server :
- 会开启一个服务,默认地址是
localhost:8080
,默认情况下,会对应项目的跟目录 - 当修改代码后,会自动的重新打包,同时在内存中生成一个
bundle.js
,而不会覆盖dist目录下的bundle.js
- 访问
bundle.js
的路径为localhost:8080/bundle.js
- 会开启一个服务,默认地址是
- html-webpacl-plugin:
- 根据配置,将某个路径下的指定的 html 文件自动拷贝到项目的某个目录下面 (比如根目录)
- 只要每次运行打包命令,就会进行拷贝
- 拷贝的文件也是在内存中,而没有写入磁盘
- 自动根据指定的页面生成一个在内存中的页面
- 自动在页面中引入打包好的bundle.js
加载器
webpack 默认只能打包 .js 模文件,其他静态文件,如 .css,图片等默认不能处理,如果不加载对应的加载器,则会报错
https://webpack.docschina.org/loaders/
webpack 支持使用 loader 对文件进行预处理。你可以构建包括 JavaScript 在内的任何静态资源。并且可以使用 Node.js 轻松编写自己的 loade
下面的图说明了webpak如何处理各种文件类型
处理css文件
src 目录下新建 css 目录,新建 index.css
编写样式
* {
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
}
li {
line-height: 45px;
}
index.js 中导入 index.css
打包时报错
可见,没有对应的 loader,webpack 打包时无法处理 css 文件
loader
loader 用于对模块的源代码进行转换。loader 可以使你在 import
或 “load(加载)” 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import
CSS文件!
安装 loader
npm install --save-dev css-loader style-loader
使用 loader
在你的应用程序中,有三种使用 loader 的方式:
配置 webpack.config.js
{test:/\.css/,use:['style-loader','css-loader'] } //loader 的调用是从后往前掉,所以他们顺序不可变,是固定的
处理less
安装
npm install less-loader less -D
rules 中增加一条规则
{test:/\.less/,use:['style-loader','css-loader','less-loader'] }
css 目录中新建 a.less
@bgColor:#ccc;
body{
background-color: @bgColor;
}
index.js 中引入 a.less
重新打包程序
处理图片和字体
在 v5 之前使用 url-loader
在 v5 之后, url-loader 被废弃(仍然可以使用),推荐使用 asset module
url-loader
url-loader
功能类似于 file-loader
, 但是在文件大小(单位为字节)低于指定的限制时,可以返回一个 DataURL。
简单来说,就是当图片尺寸<指定的尺寸时,返回的是 base64 格式,否则返回的还是一个图片路劲
安装
npm i file-loader url-loader -D
配置 webpack.config.js
{
test: /\.jpg|png|gif|bmp|jfif|ttf|eot|svg|woff|woff2$/,
use: [{
loader: 'url-loader', //使用 url-loader 处理打包 jpg等 图片文件
options: {
limit: 50000 // 当图片尺寸 <50000 字节时,会将图片转换为 base64 格式,并打包到 bundle.js中,否则就不会打包到bundle.js中,会作为资源存在
}
}]
},
index.css 中编写代码,为 ul 设置背景图片
* {
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
background: url(../images/1.jpg) no-repeat;
}
li {
line-height: 45px;
}
重新运行
npm run dev
因为当前图片为 19474 byte,我们在加载器中设置的是 50000 byte,所以返回的是 base64 格式
当图片尺寸 < 50000 字节时,会将图片转换为 base64 格式,并打包到 bundle.js中,否则就不会打包到bundle.js中,会作为资源存在
我们通过访问如下地址,查看生成的bundle.js,也可以确定这一点
http://localhost:8080/bundle.js
关于图片的 base64 编码
图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址。
这样做有什么意义呢?我们知道,我们所看到的网页上的每一个图片,都是需要消耗一个 http 请求下载而来的(所有才有了 csssprites 技术的应运而生,但是 csssprites 有自身的局限性,下文会提到)。
没错,不管如何,图片的下载始终都要向服务器发出请求,要是图片的下载不用向服务器发出请求,而可以随着 HTML 的下载同时下载到本地那就太好了,而 base64 正好能解决这个问题
处理高级js语法
babel 用于将js的新语法和特性转换为浏览器支持的语法
安装babel转换器相关包
npm install -D babel-loader @babel/core @babel/preset-env
webpack.config.js 中添加如下规则
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
在 index.js 中编写如下代码
class Person {
static age = 20
}
console.log(Person.age);
打包
npm run dev
错误原因在于没有安装babel 语法的相关插件
安装
npm i -D @babel/plugin-proposal-class-properties
然后在上面规则的 option 属性中添加配置
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties'] // 新加的配置
}
再次打包就可以了
为了提升效率,减小包的体积,我们可以安装这的两个插件
npm i -D @babel/runtime @babel/plugin-transform-runtime
规则中的 plugins 中再加入上面插件的配置
将配置信息配置到单独文件中
也可以将 babel-loader 的配置信息单独放到一个配置文件中,这样可以让 webpack.config.js 变得尽量简单清楚
根目录下新建 babel.config.js
module.exports = {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime', '@babel/plugin-proposal-class-properties']
}
webpack.config.js
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: 'babel-loader'
}
webpack用于vue
通过前面的学习,我们了解
vue 为我们提供了一种新的开发方式,让我们专注于数据、专注于业务逻辑,不需要频繁的操作Dom,当然其带了的不仅仅是 dom 操作的省略,还有更多的特性,如路由等,需要我们慢慢探索和理解
webpack解决了浏览器端模块引用和依赖混乱、js高级语法不兼容、项目压缩打包等问题
所以二者的结合就很有必要了
结合方法并不是简单的在 indx.html 中使用 script 标签引入 vue,然后像我们以前一样编写代码
现在的前端开发流行的是组件化开发,所以先讲讲 vue 中的单文件组件,然后再说基于单文件组件如何使用webpack
单文件组件
在很多 Vue 项目中,我们使用 Vue.component
来定义全局组件,紧接着用 new Vue({ el: '#container '})
在每个页面内指定一个容器元素。
这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的项目中,或者你的前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显:
- 全局定义 (Global definitions) 强制要求每个 component 中的命名不得重复
- 字符串模板 (String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的
\
- 不支持 CSS (No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
- 没有构建步骤 (No build step) 限制只能使用 HTML 和 ES5 JavaScript,而不能使用预处理器,如 Pug (formerly Jade) 和 Babel
文件扩展名为 .vue
的 single-file components (单文件组件) 为以上所有问题提供了解决方法,并且还可以使用 webpack 或 Browserify 等构建工具。
- vue 单文件组件以 .vue 作为后缀名
- vue 单文件组件包含三个部分,分别用于编写结构、样式和业务逻辑,其实每一个组件就相当于一个 html 页面,只不过后缀名是 .vue 而不是 .html
- vue 单文件组件利用了 ES6模块化的语法,从下面的 export default 可以看出来,也就是说每个组件都是一个模块,这样就可以避免命名冲突的问题
这是一个文件名为 Home.vue
的简单实例:
<template>
<div class="home">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data:function(){
return{
msg: 'Home'
}
},
methods:{
},
computed: {
},
watch: {
}
}
</script>
<style scoped>
h1{
color: red;
}
</style>
现在我们获得:
总结:
- 文件名称以.vue 作为后缀名
- 文件名称一般采用大驼峰写法,例如(Home.vue、About.vue、Detall.vue、NarBar.vue)
- 文件中包含三部分,分别是
- 模板,使用
tempate
包含起来,用来编写组件结果 - 样式,使用
style
包含起来,用来编写样式,编写样式时,可以使用css
预处理器 (less、scss、stylud) - 业务逻辑:使用
script
包含起来,内部使用 ES6 的模板化
- 模板,使用
这些特定的语言只是例子,你可以只是简单地使用 Babel,TypeScript,SCSS,PostCSS - 或者其他任何能够帮助你提高生产力的预处理器。如果搭配 vue-loader
使用 webpack,它也能为 CSS Modules 提供头等支持。
webpack 打包 vue
初始化
npm init -y
创建 src 文件,将我们所需要的页面文件放在里面
- index.html 页面
- main.js 入口文件
- Home.vue 单文件
index.html 中编写代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
下载vue插件
npm i vue
下载 webpack
npm install webpack webpack-cli --save-dev
创建 配置(webpack.config.js) 文件,并配置
const path = require('path')
module.exports={
mode:'development',
entry:path.join(__dirname,'src','main.js'), // 打包的入口文件
output:{
path:path.join(__dirname,'dist'),
filename:"bundle.js"
}
}
编写入口文件(main.js)
import Vue from 'vue'
import Home from './Home.vue'
let vm = new Vue({
el:'#app',// 指定当前vue实例的挂载点,也就是将index.html 中的id=app 的dom作为挂载点
render: h => h(Home)
})
render
函数
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。
render
主要作用:就是把一个组件的模板、css样式给渲染到某一个html页面上
上面那个案例就是使用 render
函数 进行渲染
将 Home 组件中的模板和样式渲染到 index.html 页面中,具体来说,就是用模板中的内容替换 index.html 中的 id=app 的元素,也会将 css 放在页面中
做好准备之后就可以下载vue loader 对 vue 单文件进行一系列的打包了
Vue Loader
Vue Loader 是什么?
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件:
<template>
<div class="home">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data:function(){
return{
msg: 'Home'
}
},
methods:{
},
computed: {
},
watch: {
}
}
</script>
<style scoped>
h1{
color: red;
}
</style>
Vue Loader 还提供了很多酷炫的特性:
- 允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在
<style>
的部分使用 Sass 和在<template>
的部分使用 Pug; - 允许在一个
.vue
文件中使用自定义块,并对其运用自定义的 loader 链; - 使用 webpack loader 将
<style>
和<template>
中引用的资源当作模块依赖来处理; - 为每个组件模拟出 scoped CSS;
- 在开发过程中使用热重载来保持状态。
简而言之,webpack 和 Vue Loader 的结合为你提供了一个现代、灵活且极其强大的前端工作流,来帮助撰写 Vue.js 应用。
Vue CLI
如果你不想手动设置 webpack,我们推荐使用 Vue CLI 直接创建一个项目的脚手架。通过 Vue CLI 创建的项目会针对多数常见的开发需求进行预先配置,做到开箱即用。
具体会在下面细讲
安装
npm install -D vue-loader vue-template-compiler
webpack 配置
Vue Loader 的配置和其它的 loader 不太一样。除了通过一条规则将 vue-loader
应用到所有扩展名为 .vue
的文件上之外,请确保在你的 webpack 配置中添加 Vue Loader 的插件:
const { VueLoaderPlugin } = require('vue-loader')
在规则中写入
module.exports = {
module: {
rules: [
// ... 其它规则
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
// 请确保引入这个插件!
new VueLoaderPlugin()
]
}
这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue
文件里相应语言的块。
安装相关加载器
因为我们所写的项目中又涉及到 css
样式,所以我们要下载 css 加载器
安装 css loader
npm install --save-dev css-loader style-loader
配置规则
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
给 package.json 文件中添加配置
打包
npm run dev
然后在html页面引入 bundle.js
文件
在进行打包,运行 html.js就可以将单文件的内容展示在html页面上了
警告
如果你在开发一个库或多项目仓库 (monorepo),请注意导入 CSS 是具有副作用的。请确保在 package.json
中移除 "sideEffects": false
,否则 CSS 代码块会在生产环境构建时被 webpack 丢掉。
创建其他VUE进行联动
创建 一个 About.vue 文件
<template>
<div class="about">
<h2>关于我们</h2>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
h2{
color: orange;
}
</style>
引入About.vue
再在入口文件中引入About.vue
import About from './About.vue'
但此时有一个麻烦,如果我们想展示About文件中的内容,就要改变 render:h=>h()
的括号中的内容
显示About.vue
比如我想展示About.vue中的内容,就要这么写
render:h => h(About)
然后打包,index.html 就会显示About.vue的内容
所以为了解决这个问题,我们就需要创建一个根组件 App.vue
,使用路由进行组件之间切换
配置路由
创建父组件 App.vue
App.vue
就相当于是父组件,Home.vue
和 About.vue
就相当于是子组件
<template>
<div>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
安装 vue-router
npm install vue-router
引入 vue-router
import VueRouter from 'vue-router'
// 安装路由功能:
Vue.use(VueRouter)
创建路由规则
- 当用户请求不同的 hash 值时,显示不同的组件(将不同的组件内容显示在
<router-view></router-view>
)
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
创建 router 实例,然后传 routes
配置
你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes
})
通过 router 配置参数注入路由
在 vue实例中 调用 router 实例,并将h()里的内容改成App
为了提高开发效率,减少不必要的麻烦,我们需要安装几个插件,辅助我们进行效率性开发
自动打包
安装插件
npm install --save-dev webpack-dev-server
配置package.json 文件
生成预览页面
安装 html-webpack-plugin
npm install --save-dev html-webpack-plugin
安装 loader-utils
npm install loader-utils -D
配置webpack.config.js
问题
当我们打包运行后会发现一个错误
原因是自动创建的 bundle.js位置和 我们所创建的位置不同
解决方案
-
自己生成,禁止自动生成bundle.js
-
删除自己引入的bundle.js,让其自动引入,并为其规定他引入的位置(body的后面)
实现公共导航栏
为了更有效率的实现子组件间内容切换,这个时候我们可以做一个导航栏,通过切换导航栏实现更高效的内容切换,所以我们要做个公共导航栏
创建公共导航栏文件 NavBar.vue
<template>
<nav>
<ul>
<li><router-link to="/home">首页</router-link></li>
<li><router-link to="/about">关于我们</router-link></li>
</ul>
</nav>
</template>
<script>
export default {
}
</script>
<style scoped>
ul{
list-style-type: none;
overflow: hidden;
}
li{
float: left;
padding: 10px;
}
a{
text-decoration: none;
}
</style>>
</style>
公共组件引入导航栏
封装组件和路由文件
创建各个文件夹 - 组件
- components 文件夹 - 局部组件
- NavBar.vue
- views 文件夹 - 页面子组件
- Home.vue
- About.vue
改变路径
记得改变文件路径路径
创建各个文件夹 - 路由
创建路由文件夹
为了减少入口文件的繁重性,我们按照他们的功能把他们分别分配给各个子文件,然后进行调用子文件,来实现效果,所以我们可以创建路由文件夹,来存放路由的代码
- 创建路由文件 router
- 创建子文件 index.js
将有关路由的那内容存放在index.js文件中,修改文件路径
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
// 安装路由功能:
Vue.use(VueRouter)
/**
* 创建路由规则
* 当用户请求不同的 hash 值时,显示不同的组件(将不同的组件内容显示在 <router-view></router-view>)
*/
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
// 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
routes
})
export default router
main.js
引入index.js 文件
import Vue from 'vue'
import router from './router/index.js'
import App from './App.vue'
let vm = new Vue({
el:'#app',
render:h => h(App),
router
})
由此来看我们的 入口文件,瞬间清爽很多
一个小细节
为什么我们给路由文件取名为index.js呢?
因为我们这里有个好处,就是如果我们叫index.html那我们引入时则可以省略文件名,因为它会自动寻找我们目录下面名叫 index.js 的文件
所呈现的效果
文件上线
不管我们的index.html 还是 bundle.js 都是在我们的内存当中,所以我们要上线就必须要解决这个问题
我们要在package.json文件中,加入 "build":"webpack"
删除原来的 dist 文件夹,运行命令
npm run build
打包之后我们可以发现它所占内存有点大,所以我们可以再次修改,把打包的模式改成上线模式,然后再次运行
这个时候我们可以发现,现在的bundle.js的内存变小了
原来的:
现在的:
VUE CLI
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
-
通过
@vue/cli
实现的交互式的项目脚手架。 -
通过
@vue/cli
+@vue/cli-service-global
实现的零配置原型开发。 -
一个运行时依赖 (
@vue/cli-service
),该依赖:
- 可升级;
- 基于 webpack 构建,并带有合理的默认配置;
- 可以通过项目内的配置文件进行配置;
- 可以通过插件进行扩展。
-
一个丰富的官方插件集合,集成了前端生态中最好的工具。
-
一套完全图形化的创建和管理 Vue.js 项目的用户界面。
Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。
该系统的组件
Vue CLI 有几个独立的部分——如果你看到了我们的源代码,你会发现这个仓库里同时管理了多个单独发布的包。
CLI
CLI (@vue/cli
) 是一个全局安装的 npm 包,提供了终端里的 vue
命令。它可以通过 vue create
快速搭建一个新项目,或者直接通过 vue serve
构建新想法的原型。你也可以通过 vue ui
通过一套图形化界面管理你的所有项目。我们会在接下来的指南中逐章节深入介绍。
CLI 服务
CLI 服务 (@vue/cli-service
) 是一个开发环境依赖。它是一个 npm 包,局部安装在每个 @vue/cli
创建的项目中。
CLI 服务是构建于 webpack 和 webpack-dev-server 之上的。它包含了:
- 加载其它 CLI 插件的核心服务;
- 一个针对绝大部分应用优化过的内部的 webpack 配置;
- 项目内部的
vue-cli-service
命令,提供serve
、build
和inspect
命令。
如果你熟悉 create-react-app 的话,@vue/cli-service
实际上大致等价于 react-scripts
,尽管功能集合不一样。
CLI 服务章节涵盖了它的具体用法。
CLI 插件
CLI 插件是向你的 Vue 项目提供可选功能的 npm 包,例如 Babel/TypeScript 转译、ESLint 集成、单元测试和 end-to-end 测试等。Vue CLI 插件的名字以 @vue/cli-plugin-
(内建插件) 或 vue-cli-plugin-
(社区插件) 开头,非常容易使用。
当你在项目内部运行 vue-cli-service
命令时,它会自动解析并加载 package.json
中列出的所有 CLI 插件。
插件可以作为项目创建过程的一部分,或在后期加入到项目中。它们也可以被归成一组可复用的 preset。我们会在插件和 preset 章节进行深入讨论。
安装
若电脑之前已经安装过vue-cli了,但是版本过低,比方说当前vue-cli的版本为2.9.6,然后我想升级到vue-cli的最新版本4.0.5,则需要将旧版本卸载,然后再重新安装@vue/cli。
# 3.0之前版本使用此名称
vue-cli:vue
# 3.0之后版本包括3.0版本使用此名称
@vue/cli:vue
可以使用下列任一命令安装这个新的包:
npm install -g @vue/cli
# OR
yarn global add @vue/cli
安装指定版本
# 安装2.9.6版本
npm install -g vue-cli@2.9.6
yarn global add vue-cli@2.9.6
# 安装4.0.5版本
npm install -g @vue/cli@4.0.5
yarn global add @vue/cli@4.0.5
查看版本
安装之后,你就可以在命令行中访问 vue
命令。你可以通过简单运行 vue
,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。
你还可以用这个命令来检查其版本是否正确:
vue --version
升级
如需升级全局的 Vue CLI 包,请运行:
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli
卸载
# 卸载3.0之前的版本
npm uninstall -g vue-cli
# 或者
yarn global remove vue-cli
# 卸载3.0之后的版本(可以统一使用此指令卸载)
npm uninstall -g @vue/cli
# 或者
yarn global remove @vue/cli
创建一个vue项目
使用命令创建项目
vue create 项目名
案例:
vue create hello-world
警告
如果你在 Windows 上通过 minTTY 使用 Git Bash,交互提示符并不工作。你必须通过 winpty vue.cmd create hello-world
启动这个命令。不过,如果你仍想使用 vue create hello-world
,则可以通过在 ~/.bashrc
文件中添加以下行来为命令添加别名。 alias vue='winpty vue.cmd'
你需要重新启动 Git Bash 终端会话以使更新后的 bashrc 文件生效。
在 Windows PowerShell 中创建
按照我图上的去选择,然后回车创建项目
测试是否创建成功
Windows PowerShell 中验证
使用两个命令
$ cd hello-world
$ npm run serve
等加载完毕后会出现一个链接:
在浏览器的搜索栏复制此路径,就会打开这个页面,则安装成功
vscode 中验证
或者我们可以直接在vscode中打开项目,在集成终端中输入上面的命令
cd hello-world
npm run serve
注意,在修改项目内容时,他会自动更近更新
vue create
命令有一些可选项,你可以通过运行以下命令进行探索:
vue create --help
用法:create [options] <app-name>
创建一个由 `vue-cli-service` 提供支持的新项目
选项:
-p, --preset <presetName> 忽略提示符并使用已保存的或远程的预设选项
-d, --default 忽略提示符并使用默认预设选项
-i, --inlinePreset <json> 忽略提示符并使用内联的 JSON 字符串预设选项
-m, --packageManager <command> 在安装依赖时使用指定的 npm 客户端
-r, --registry <url> 在安装依赖时使用指定的 npm registry
-g, --git [message] 强制 / 跳过 git 初始化,并可选的指定初始化提交信息
-n, --no-git 跳过 git 初始化
-f, --force 覆写目标目录可能存在的配置
-c, --clone 使用 git clone 获取远程预设选项
-x, --proxy 使用指定的代理创建项目
-b, --bare 创建项目时省略默认组件中的新手指导信息
-h, --help 输出使用帮助信息
使用图形化界面
你也可以通过 vue ui
命令以图形化界面创建和管理项目:
vue ui
上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。
跟我们上面选的东西一样,只不过变成了选项选择
打开文件
当我们用vscode打开我们创建的vue项目,就会发现它会自动给你分配好各个文件
做一个表格案例
我们做项目准备
views 中创建所需的文件
- 表格文件 List.vue
- 详情页 Detail.vue
- 添加页 Add.vue
- 更新页 Edite.vue
在路由文件中引入
在index.js 文件中引入
import List from '../views/List.vue'
import Detail from '../views/Detail.vue'
import Add from '../views/Add.vue'
import Edite from '../views/Edite.Vue'
匹配规则(只显示页面跳转)
{
path: '/',
name: 'List',
component: List
},
{
path: '/detail',
name: 'Detail',
component: Detail
},
{
path: '/add',
name: 'Add',
component: Detail
},
{
path: '/edite',
name: 'Edite',
component: Edite
}
清除App.vue多余的数据
将不需要的代码全部清除,只剩下路由出口代码
<template>
<div id="app">
<!-- 路由出口 -->
<router-view />
</div>
</template>
<style>
</style>
正式编写页面—无接口版
列表展示页面
List.vue 页面
<template>
<div class="list">
<router-link to=“/add”>
添加
</router-link>
<table>
<thead>
<tr>
<th>编号</th>
<th>标题</th>
<th>发表时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in posts" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.title }}</td>
<td>{{ item.create_time }}</td>
<td>
<router-link>
详情
</router-link>
<router-link>
编辑
</router-link>
<a href="javascript:;">
删除
</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data:function(){
return{
posts:[{
id:1,title:'今天据说要下雨',content:"今天据说要下雨今天据说要下雨今天据说要下雨今天据说要下雨",create_time:Date.now()
},
{
id:2,title:'天气越来越暖和了',content:"天气越来越暖和了天气越来越暖和了天气越来越暖和了天气越来越暖和了",create_time:Date.now()
},
{
id:3,title:'万物复苏,冰雪消融',content:"万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融",create_time:Date.now()
}
]
}
}
}
</script>
匹配列表页面的规则
{
path: '/',
name: 'List',
component: List
}
列表详情页
Detail.vue 页面
- 路由的页面之间不能共用数据,所以我们要在detail 页面也创建数据,其他几个页面也是
- 在正式开发项目的时候,我们经常时做一个后台接口,让前台去数据库中查询数据并操作
- 并且跳转的时候我们要做到每一个id 对应每一条数据
-
路由组件传参:
-
在组件中使用
$route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性 -
取代与
$route
的耦合:<div class="detail">详情id 为 {{$route.params.id}} </div>
-
因为他传递的id和我所展示的详情页id是一样的,所以我们需要使用v-bind为 to元素绑定属性—:to=“跳转页面”, 我们下面所涉及到的编辑页面也需如此 ,这就是我们的命名路由
-
命名路由
-
有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在
routes
配置中给某个路由设置名称。const router = new VueRouter({ routes: [ { path: '/user/:userId', name: 'user', component: User } ] })
-
要链接到一个命名路由,可以给
router-link
的to
属性传一个对象:<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
-
跟代码调用
router.push()
是一回事:router.push({ name: 'user', params: { userId: 123 }})
这两种方式都会把路由导航到
/user/123
路径。
-
-
我们需要页面一加载就展示我们详情页的组件,那么就触发内置的一个事件
- 我们所需要的就是created事件 (只要页面一加载就会触发这个事件)
- created: function () {}
-
使用 filter 或者 find 查询数据
- filter 返回时数组,不管查询多少条数据他返回都是一跳数组
- find 返回是对象,只找到第一条符合的就返回了
完整代码:
<template>
<div class="detail">
<router-link to="/">文章列表</router-link>
<h2>{{ post.title }}</h2>
<div>{{ post.content }}</div>
</div>
</template>
<script>
export default {
data: function () {
return {
posts: [
{
id: 1,
title: "今天据说要下雨",
content: "今天据说要下雨今天据说要下雨今天据说要下雨今天据说要下雨",
create_time: Date.now(),
},
{
id: 2,
title: "天气越来越暖和了",
content:
"天气越来越暖和了天气越来越暖和了天气越来越暖和了天气越来越暖和了",
create_time: Date.now(),
},
{
id: 3,
title: "万物复苏,冰雪消融",
content:
"万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融",
create_time: Date.now(),
},
],
post: {}, // 查找到符合条件的文章对象
}
},
created: function () {
// 获取传递过来的 id
let post_id = this.$route.params.id;
// 从 posts 变量中查询 id = post_id 的元素
/**
* 使用 filter 或者 find
* filter 返回时数组,不管查询多少条数据他返回都是一跳数组
* find 返回是对象,只找到第一条符合的就返回了
*/
let res_post = this.posts.find(item => {
return item.id == post_id;
});
this.post = res_post;
}
};
</script>
设置详情页的规则为:
{
path: '/detail/:id',
name: 'Detail',
component: Detail
}
列表添加页面
Add.vue 页面
我们使用表单提交的方式,进行页面数据的添加,由于我们要让他是因为添加v-on
属性的单击事件而添加数据效果的,所以我们要设置提交按钮的类型为 button
,并且我们还要使用双向数据绑定的方式进行数据填充
由于我们是单页面数据,并且没有链接后台,所以无法把数据共享,只能在本页面使用
在data里面自定义相关变量,通过v-model给输入框进行绑定(下面的编辑页面也要如此)
Add.vue
<template>
<div class="box">
<router-link to="/">文章列表</router-link>
<form action="">
<div>
<label for="title">标题:</label>
<input type="text" id="title" v-model="title">
</div>
<div>
<label for="title">内容:</label>
<textarea id="content" style="width: 500px; height:250px; resize: none" v-model="content">
</textarea>
</div>
<input type="button" value="提交" @click="saveDate">
</form>
</div>
</template>
<script>
export default {
data: function () {
return {
posts: [
{ id: 1, title: '今天据说要下雨', content: '今天据说要下雨今天据说要下雨今天据说要下雨今天据说要下雨', create_time: Date.now() },
{ id: 2, title: '天气越来越暖和了', content: '天气越来越暖和了天气越来越暖和了天气越来越暖和了天气越来越暖和了', create_time: Date.now() },
{ id: 3, title: '万物复苏,冰雪消融', content: '万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融', create_time: Date.now() },
],
title: '',
content: ''
}
},
methods:{
saveDate :function(){
this.posts.push({id:4 ,title:this.title,content:this.content})
console.log(this.posts)
}
}
}
</script>
设置添加页规则:
{
path: '/add',
name: 'Add',
component: Add
},
列表更新页
Edite.vue 页面
更新页面结合添加页和详情页,更新页面上有添加页面的表单提交方式,还有详情页获取要更改id的数据,再结合自身所构成
首先还要把数据引进去,再把created的事件写进去,来获取你要更改的数据
在 created 事件中添加上,我们的表单输入框的内容,等于我们获取到的,所需要更改的内容
his.title = post_res.title
this.content = post_res.content
完整代码:
由于没有接口,就不费事去写编辑代码了,等下面使用接口操作的时候再补充完整
<template>
<div class="edite">
<router-link to="/">文章列表</router-link>
<form action="">
<div>
<label for="title">标题:</label>
<input type="text" id="title" v-model="title">
</div>
<div>
<label for="title">内容:</label>
<textarea id="content" style="width: 500px; height:250px; resize: none" v-model="content">
</textarea>
</div>
<input type="button" value="提交" @click="updateData">
</form>
</div>
</template>
<script>
export default {
data: function () {
return {
posts: [
{ id: 1, title: '今天据说要下雨', content: '今天据说要下雨今天据说要下雨今天据说要下雨今天据说要下雨', create_time: Date.now() },
{ id: 2, title: '天气越来越暖和了', content: '天气越来越暖和了天气越来越暖和了天气越来越暖和了天气越来越暖和了', create_time: Date.now() },
{ id: 3, title: '万物复苏,冰雪消融', content: '万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融', create_time: Date.now() },
],
id: '',
title: '',
content: '',
post: {} // 查找到的符合条件的文章对象
}
},
created: function () {
let post_id = this.$route.params.id
// 下面代码要替换成请求 api 接口
let post_res = this.posts.find(item => {
return item.id == post_id
})
this.title = post_res.title
this.content = post_res.content
},
methods :{
updateData:function (){
/**
* 提交并修改数据
* 将 id、标题和内容提交到后台 api
*/
}
}
}
</script>
删除列表
删除列表的数据我们不需要跳转页面,所以我们只需要一个在列表页面添加个a
标签的超链接就可以个,给超链接添加单击事件,使数据删除
<template>
<div class="list">
<router-link to="/add">新增</router-link>
<table>
<thead>
<tr>
<th>编号</th>
<th>标题</th>
<th>发表时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in posts" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.title }}</td>
<td>{{ item.create_time }}</td>
<td>
<router-link :to="{ name: 'Detail', params: { id: item.id } }">
详情
</router-link>
<router-link :to="{ name: 'Edite', params: { id: item.id } }">
编辑
</router-link>
<a href="javascript:;" @click="deleteData(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data:function(){
return{
posts:[{
id:1,title:'今天据说要下雨',content:"今天据说要下雨今天据说要下雨今天据说要下雨今天据说要下雨",create_time:Date.now()
},
{
id:2,title:'天气越来越暖和了',content:"天气越来越暖和了天气越来越暖和了天气越来越暖和了天气越来越暖和了",create_time:Date.now()
},
{
id:3,title:'万物复苏,冰雪消融',content:"万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融万物复苏,冰雪消融",create_time:Date.now()
}
]
}
},
methods:{
deleteData: function (post_id) {
this.posts.forEach((item, index) => {
if (item.id == post_id) {
this.posts.splice(index, 1)
}
});
}
}
}
</script>
正式编写页面—有接口版
使用axios
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
- 主要特点包括:在浏览器中发送XMLHttpRequests 请求、在node.js 中发送http请求、支持Promise API、拦截请求和
响应、转换请求和响应数据等等;
安装
使用 npm:
$ npm install axios
使用 bower:
$ bower install axios
使用 cdn:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
请求配置
这些是创建请求时可以用的配置选项。只有 url
是必需的。如果没有指定 method
,请求将默认使用 get
方法。
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `headers` 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: {
ID: 12345
},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: {
firstName: 'Fred'
},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 1000,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,以使测试更轻松
// 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
adapter: function (config) {
/* ... */
},
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
// 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0,将不会 follow 任何重定向
maxRedirects: 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
socketPath: null, // default
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
// `keepAlive` 默认没有启用
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 'proxy' 定义代理服务器的主机名称和端口
// `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
// 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken` 指定用于取消请求的 cancel token
// (查看后面的 Cancellation 这节了解更多)
cancelToken: new CancelToken(function (cancel) {
})
}
响应结构
某个请求的响应包含以下信息
{
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 服务器响应的头
headers: {},
// `config` 是为请求提供的配置信息
config: {},
// 'request'
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance the browser
request: {}
}
使用 then
时,你将接收下面这样的响应 :
axios.get('/user/12345')
.then(function(response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
在使用 catch
时,或传递 rejection callback 作为 then
的第二个参数时,响应可以通过 error
对象可被使用,正如在错误处理这一节所讲。
配置的优先顺序
配置会以一个优先顺序进行合并。这个顺序是:在 lib/defaults.js
找到的库的默认值,然后是实例的 defaults
属性,最后是请求的 config
参数。后者将优先于前者。这里是一个例子:
// 使用由库提供的配置的默认值来创建实例
// 此时超时配置的默认值是 `0`
var instance = axios.create();
// 覆写库的超时默认值
// 现在,在超时前,所有请求都会等待 2.5 秒
instance.defaults.timeout = 2500;
// 为已知需要花费很长时间的请求覆写超时设置
instance.get('/longRequest', {
timeout: 5000
});
错误处理
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
也可以使用 validateStatus
配置选项定义一个自定义 HTTP 状态码的错误范围。
axios.get('/user/12345', {
validateStatus: function (status) {
return status < 500; // Reject only if the status code is greater than or equal to 500
}
})
引入
给所有使用页面都引入 axios
import axios from ‘axios’
请求配置
运行后台文件
在后台 api 文件中按住 shift 键右击打开 Windows PowerShell ,在内输入
node app.js
或者
nodemon app.js
运行 api文件 (app.js)
接口基准地址:http://127.0.0.1:3000/api/v1/
也就是所有的请求前面都要加上上面的基准地址
博客列表
url: posts
请求方式: get
参数:无
使用 created 函数刷新页面
respones.data.data获取服务器中列表的数据
删除博客
url: posts/id
请求方式: delete
参数:无
它是通过点击而完成的动作,所以写在methods 的点击里
由于我们的删除是由 api 自行删除,所以我们只需要传递所要删除的数据就可
List.vue页面改造
结合列表展示和删除,list.vue的代码改造完成:
<template>
<div class="list">
<router-link to="/add">新增</router-link>
<table>
<thead>
<tr>
<th>编号</th>
<th>标题</th>
<th>发表时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in posts" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.title }}</td>
<td>{{ item.create_time }}</td>
<td>
<router-link :to="{ name: 'Detail', params: { id: item.id } }">
详情
</router-link>
<router-link :to="{ name: 'Edite', params: { id: item.id } }">
编辑
</router-link>
<a href="javascript:;" @click="deleteData(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import axios from 'axios'
export default {
data:function(){
return{
posts:[
]
}
},
created:function(){
// 使用 get请求 列表的所有内容
axios.get('http://127.0.0.1:3000/api/v1/posts')
.then (response=> {
this.posts = response.data.data;
})
.catch (error => {
console.log(error);
});
},
methods:{
deleteData: function (post_id) {
axios.delete('http://127.0.0.1:3000/api/v1/posts/'+post_id)
.then (response=> {
// console.log(response)
location.reload()
})
.catch (error => {
console.log(error);
});
}
}
}
</script>
添加博客
url: posts
请求方式: post 参数:
参数:
{
"title": "标题",
"content": "内容"
}
添加也是通过点击事件完成的,所以也写在 methods 的点击事件中
Add.vue 页面改造
<template>
<div class="box">
<router-link to="/">文章列表</router-link>
<form action="">
<div>
<label for="title">标题:</label>
<input type="text" id="title" v-model="title">
</div>
<div>
<label for="title">内容:</label>
<textarea id="content" style="width: 500px; height:250px; resize: none" v-model="content">
</textarea>
</div>
<input type="button" value="提交" @click="saveDate">
</form>
</div>
</template>
<script>
import axios from 'axios'
export default {
data: function () {
return {
posts: [
],
title: '',
content: ''
}
},
methods:{
saveDate :function(){
axios.post("http://127.0.0.1:3000/api/v1/posts", {
title: this.title,
content: this.content,
})
.then((res) => {
console.log(res);
if(res.status==200){
this.$router.push({path:'/'})
}
})
.catch( error=> {
console.log(error);
});
}
}
}
</script>
补充:vue 页面跳转
vue 页面跳转的两种方式
1,标签跳转
<router-link to='/index'>点我到index页面</router-link>
2,点击事件跳转
html :
<button @click="hreftwo" class="test-one">点我到第二个页面</button>
js :
methods:{ //跳转页面
hreftwo(){
this.$router.push({ path:'/two.html' })
}
}
跳转返回上一页
-
vue 如何点击按钮返回上一页呢?
this.$router.go(-1)
小案例:
这是vue挂载的范围html代码
<div @click="goOff()">返回</div>
下面是点击返回的方法
-
第一种只返回上一页
goOff(){ this.$router.go(-1); },
-
第二种 返回上一页,如果没有上一页返回首页
methods: {
back(){
if (window.history.length <= 1) {
this.$router.push({path:'/'})
return false
} else {
this.$router.go(-1)
}
}
}
vue调取接口小知识
actAllProfit(){ //调取接口的函数
var params = {}; //这是调取接口时的请求参数 明白点就是我们要传过去的参数
var reqUrl = this.diviBaseUrl + '/bonuses/grossProfit'; //这是后台接口地址
this.$http.get(reqUrl,{ //get方式发送请求
params: params //把外面声明的参数传过去
}).then((res)=>{ //巧妙运用箭头函数
if(!res){return;}
this.allProfit = res.Profit; //res就是后台返回的所有数据 前端需要接收并保存
})
}
详情页面
url: posts/id
请求方式: get
参数:无
详情页面是通过页面刷新完成的,所以写在created里
Detail.vue页面改造
<template>
<div class="detail">
<router-link to="/">文章列表</router-link>
<h2>{{ posts.title }}</h2>
<div>{{ posts.content }}</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
data: function () {
return {
posts: [
]
}
},
created: function () {
// 获取传递过来的 id
let post_id = this.$route.params.id;
// 为给定 ID 的 user 创建请求
axios.get('http://127.0.0.1:3000/api/v1/posts/'+post_id)
.then (response=> {
this.posts = response.data.data;
})
.catch (error => {
console.log(error);
});
}
};
</script>
编辑博客
url: posts/id
请求方式: put 、get
参数:
{
"title": "标题",
"content": "内容"
}
编辑分为两部分:
-
获取要编辑的数据
-
编辑的数据是一刷新页面就要获取到的,所以写在 created 里
-
-
更新数据
-
更新数据,是通过点击事件进行数据的更新,所以写在methods的点击事件里
-
Edite.vue页面改造
<template>
<div class="edite">
<router-link to="/">文章列表</router-link>
<form action="">
<div>
<label for="title">标题:</label>
<input type="text" id="title" v-model="title">
</div>
<div>
<label for="title">内容:</label>
<textarea id="content" style="width: 500px; height:250px; resize: none" v-model="content">
</textarea>
</div>
<input type="button" value="提交" @click="updateData">
</form>
</div>
</template>
<script>
import axios from 'axios'
export default {
data: function () {
return {
posts: [
],
id: '',
title: '',
content: ''
}
},
created: function () {
let post_id = this.$route.params.id
axios.get('http://127.0.0.1:3000/api/v1/posts/'+post_id)
.then (response=> {
this.title = response.data.data.title
this.content = response.data.data.content
})
.catch (error => {
console.log(error);
});
},
methods :{
updateData:function (){
/**
* 提交并修改数据
* 将 id、标题和内容提交到后台 api
*/
let post_id = this.$route.params.id
axios.put("http://127.0.0.1:3000/api/v1/posts/"+post_id,{
title: this.title,
content: this.content,
})
.then((res) => {
this.posts = res.data.data;
console.log(res);
if(res.status==200){
this.$router.push({path:'/'})
}
})
.catch( error=> {
console.log(error);
});
}
}
}
</script>