模块化开发
常见的 模块化的规范
CommonJS AMD CMD 也有 ES6的 Modules
CommonJS
需要底层支撑
commonJS的导出:
module.exports = {
flag:true,
test(a,b){
return a+b
},
demo(a,b){
return a*b
}
}
commonJS的导入:
let {test,demo,flag} = require('moduleA');
//等同于
let _mA = require('moduleA');
let test = _mA.test;
let demo = _mA.demo;
let flag = _mA.flag;
ES6的 Modules
export指令用于导出变量,比如下面的代码:
首先需要在页面引入 js 文件的时候 给一个 type
属性,值为 module
,本身script标签具备跨域请求,不受同源策略影响,但由于添加了type =module
请求方式改为了 file:// 文件请求,受到同源策略的影响
1.导出方式一:
// info.js
export let name = 'why'
export let age = 18
export let height = 1.88
上面的代码还有另一种写法:
2.导出方式二:
// info.js
let name = 'why'
let age = 18
let height = 1.88
export {name, age, height}
3.可以导出函数、类:
export function mul(num1,num2){
return num1 + num2
}
4.export default 注意!!! 只能一个导出为这个,否则import导入时候会产生错乱
var count = 88
export default count
import alger from "./main"
console.log(alger);
导入的三种方式:
// 导入 //对象 括号不能去
import {name, age, height} from '文件地址'
// 当导出为 export default
import 自己起的名字 from '文件地址'
// 以这种方式导入的时候 起的名字是alger ,因此这个模块中要想使用导入的数据需要添加 alger前缀
import * as alger from './main.js'
// 只有这个特殊一些,其他的直接可以拿到原来的变量,default 则是把导出的变量用自己起的名字代替了
// 这里导入的是'./main.js' 导出的所有变量 包括 default, 都包含着自己命名的这个对象中
webpack开始
- 认识webpack
- webpack 的安装
- webpack 的起步
- webpack 的配置
- loader的使用
- webpack中配置Vue
- plugin的使用
- 搭建本地服务器
认识webpack
从本质上来将,webpack是现代的javascript应用的静态模块打包工具
从 模块 打包来解释
前端模块化:
- 目前使用前端模块化的一些方案:AMD、CMD、CommenJS 、ES6
- 在ES6之前,我们想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发
- 并且在通过模块化开发完成了项目后,还需要处理模块之间的各种依赖,并且将其进行整合打包
- 而webpack其中一个核心就是让我们可以进行模块化开发,并且会帮助我们处理模块间的依赖关系
- 而且不仅仅是JavaScript 文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块化来使用
打包
- 理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了
- 就是讲webpack中的各种资源模块进行整合并称一个或多个包
webpack和node之间关系
webpack模块化打包工具,为了可以正常运行,必须依赖node环境,node环境为了可以正常的执行很多代码,必须其中包括各种依赖的包,npm工具(node packges manager)
安装node
安装webpack 首先需要安装Node.js,Node.js自带了软件包管理工具npm
查看自己的node 版本: node -v
全局安装webpack (这里我先指定版本3.6.0,因为vue cli2依赖该版本)
npm install webpack@3.6.0 -g
局部安装 webpack(后续才需要)
- –save-dev 是开发时的依赖,项目打包后不需要继续使用的
vscode打包报错解决方案
在通过vs code 运行webpack进行打包时,报错webpack : 无法加载文件 D:\nodejs\node_global\webpack.ps1,因为在此系统上禁止运行脚本。
解决方案:
以管理员身份运行vs code
执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的
执行:set-ExecutionPolicy RemoteSigned
这时再执行get-ExecutionPolicy,就显示RemoteSigned
此时发现再进行打包就没有问题了
webpack基本使用
webpack ./src/main.js ./dist/bundel.js
由于每次打包都要执行这样的代码 ,太过于麻烦,因此我们需要对webpack 进行一些配置
webpack 配置
可以在项目根目录创建一个webpack.config.js文件,对webpack进行一些配置
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundel.js'
},
}
1.官方推荐 output出口需要传个对象,对象中的属性 path,filename. 其中 path需要传一个绝对路径。
2.而我们并不希望这个路径被写死。 此需要用到 node中的 path模块, 当用到node模块时 可以先执行 npm init 初始化下,得到 package.json 文件。
3.然后导入node 的path模块
const path = require('path')
4.调用 path.resolve() 函数,该函数传递两个参数,会将两个参数进行拼接。 第一个参数传递 __dirname, 注意两个下划线,第二个参数传递当前文件目录 下的目标文件
5.然后直接执行 webpack 即可 按照 webpack.config.js 配置文件中的配置项 进行配置
package.js 配置
1.原本我们已经通过 webpack.config.js 配置, 只需要运行webpack 就会自动进行 src 下的 main.js打包到 dist文件下的 bundel.js
2.package.js文件中"scripts"
对象下存储的都是 运行 npm run … 的快捷方式 。 我们如果在这个对象下 定义 "build": "webpack"
则运行npm run build
会自动运行 webpack, 也就是进行打包
注意!!!! package.json中的script的脚本在执行时,会按照一定的顺序寻找命令对应的位置
- 首先,会寻找本地的node_modules/bin路径中对应的命令
- 如果没有找到,会去全局的环境变量中寻找
- 如何执行我们的build指令呢
npm run build
也就是说在cmd中直接使用webpack命令 则一定会使用全局配置的webpack,然而使用脚本命令,则会优先使用局部配置的webpack,这也是配置脚本运行的一个原因
关于全局webpack 和 本地webpack
1.由于每个项目都有自己依赖的webpack版本,所以不是所有项目都可以运行全局的webpack进行打包,因此我们需要在本地也安装相应版本的webpack.
2.安装本地webpack 执行命令npm install webpack@3.6.0 --save-dev
此时 package.js文件下的devDependencies
中就有了安装的webpack信息了。 【devDependencies 中 develop的缩写,翻译:开发,dependencies 翻译:依赖】
配置css样式文件
0.在引入css样式的入口文件中需要导入,css不需要变量接收,因此直接使用commenJS中的语法 ,require('./src/css/normal.css')
1.本身webpack没有办法对css类型的文件进行解析打包。
2.需要依赖其他的loader 来配置,官网上看了后发现需要用到 css-loader 因此需要 通过 npm来安装
3.执行命令 npm install --save-dev css-loader
安装完毕后再 package.json 文件中的开发依赖可以看到这个loader,
4.安装结束后,需要在webpack 配置文件中进行配置,打开webpack.config.js 之前我们配置了 入口,出口,现在需要配置module,如下:
module:{
rules:[{
test: /\.csss$/,
use: ['style-loader', 'css-loader']
}]
}
5.不难发现rules 是定义一些模块规则的, test是匹配这些文件,模块的,当匹配到的时候,则会依据use,执行里面相应的包。
6.注意此处的css-loader是用来解析css 文件的,你并不能将该css文件绑定到相应的DOM上,所以此时又需要使用 style-loader
7.因此我们需要安装 npm install --save-dev style-loader , 安装后依然是在use中配置,style-loader 应该是css文件解析结束后再调用,因此需要现在css-loader前面,因为 use中执行顺序是 从右到左
配置less样式文件
方法与配置css基本一致
安装: npm install --save-dev less-loader less
配置: 在 webpack.config.js 中进行配置
{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}
配置图片文件
一:配置 url-loader
-
安装依赖 npm install --save-dev url-loader
-
配置 webpack.config.js 环境
{ test: /\.(png|jpg|gif|jpeg)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }
一般 limit 限制大小 为 8k ,小于8k webpack会将其打包为 base64的图片,但是大于 8k的话 就不能这样处理了,进行打包的话会报错,提醒找不到 file-loader 整个模块,因此我们需要安装,file-loader
二:配置 file-loader
1.安装依赖 npm install --save-dev file-loader
2.不需要单独对file-loader 进行配置 ,配置的进行直接在url-loader中
3.此时再打包,默认大于 limit 的文件会直接调用 file-loader
4.默认情况下 是使用原来的扩展名,使用hash值作为文件名,直接打包在打包文件下。
5.此时我们需要在webpack.config.js的出口 配置上 publicPath:'dist/'
将输出解析文件的目录改为 dist ,url 相对于 HTML 页面
图片文件处理—修改文件名称
我们发现webpack自动帮助我们生成了一个非常长的名字
- 这是一个32位hash值,目的是防止名字重复
- 但是,真是开发中,我们可能对打包的图片名字有一定的要求
- 比如,将所有图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复 写法
img/name.hash:8.ext
所以我们可以在options中添加上如下选项:
- img:文件要打包到的文件夹
- name:获取图片原来的名字,放在该位置
- hash:8 为了防止图片名称冲突,依然使用hash,但是我们只保留8位
- ext:使用图片原来的扩展名
但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确
- 默认情况下,webpack会将生产的路径直接返回给使用者
- 但是,我们整个程序打包在dist文件下的,所以这里我们需要在路径下再添加一个dist
因此 最终我们只需要在 url-loader 配置的 limit下配置一个 name: 'img/[name].[hash:8].[ext]'
中括号说明是变量,hash:8说明截取hash前八位, .ext说明使用默认的扩展名
ES6语法处理
webpack打包的js文件,写的ES6语法并没有转化为ES5,那么就意味着可能一些ES6还不支持的浏览器没有办法很好的运行我们的代码
此时我们需要使用babel
-
webpack中,我们直接使用babel对应的loader就可以了
-
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
配置webpack.config.js文件
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
重新打包,查看bundle.js 文件,发现es6语法 改为了es5
如何模块化使用Vue
1.安装 Vue 因为Vue不仅仅是开发时候我们需要使用,运行的时候也需要使用,因此npm安装的时候 不再是-dev
而是 npm install --save vue
2.在 main.js 中通过import Vue from 'vue'
进行导入 ,因为vue内部导出的方式是 export default vue , 因此可以这样导入,注意大小写
3.打包发现报错You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
4.出现的原因,Vue构建最终发布版本的时候,构建了两类版本
- runtime-only 代码中不可以有任何template
- runtime-compiler 代码中,可以有template,因为有compiler可以用于 编译 【compiler 编译】
5.不难看出错误信息,简单翻译:你是用的是 runtime-only 进行构建Vue,template 不可以使用。
6.解决方式,进行webpack的配置
resolve:{
alias:{
// 此处是使用了 vue的 esm的一个版本,这个版本有template 语法
'vue$': 'vue/dist/vue.esm.js'
}
}
vue实例下的 el属性 和 template属性
当同时 有 el属性和 template属性时候,则会将el位置的模板用 template模板进行替换
.vue文件封装处理
但是一个组件以js对象的形式进行组织和使用的时候是非常不方便的
- 一方面编写template模块非常的麻烦
- 另一方面如果有样式的话,我们写在哪里比较合适呢
现在我们以一种全新的方式来组织一个vue组件
但是这个时候这个文件被正确加载吗?
- 必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理
- 谁来处理呢? vue-loader以及vue-template-compiler
安装 vue-loader以及vue-template-compiler
**注意 ** vue版本,13版本以上还需要多加载其他loader,因此这里我们使用 vue-loader 的 13.0.0版本即可
修改webpack.config.js的配置文件:
{
test: /\.vue$/,
use: ['vue-loader']
}
如果想要省略导入时候的后缀名的话,需要在 配置文件webpack.config.js中的 resolve 中添加 extensions: ['.js','.css','.vue']
这样导入的时候就可以不加 扩展名了。 【extensions: 拓展,扩展】,但是注意同名文件产生意想不到的错误
认识 plugin
loader 和 plugin 的区别
- loader主要用于转换某些类型的模块,他是一个转换器
- plugin是插件,它是对webpack本身的扩展,是一个扩展器
plugin的使用过程
步骤一: 通过npm安装需要使用的plugin(某些webpack已经内置的插件不需要安装)
步骤二: 在webpack.config.js 中的plugin中配置插件
版权插件 BannerPlugin
本身是webpack自带的插件,因此只需要在 webpack.config.js中配置就可以了
module.exports = {
...
plugins:[
new webpack.BannerPlugin('最终版权归coderWyj所有')
]
}
由于此处new了 webpack ,因此这个页面需要导入webpack ,因此这个文件头部需要 const webpack = require('webpack')
HtmlWebpackPlugin插件
目前我们的index.html文件是存放在项目的根目录下的
- 发布真实项目的时候,发布的是dist文件夹中的内容,但是dist 文件夹中没有index.html文件,那么打包js等文件也就没有意义
- 所以我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用 HtmlWebpackPlugin 插件了
HtmlWebpackPlugin 插件可以为我们做什么事情
- 自动生成一个index.html文件(可以指定模板来生成)
- 将打包的js 文件,自动通过script标签插入到body中
安装HtmlWebpackPlugin 插件
npm install html-webpack-plugin --save-dev
使用插件,修改webpack.config.js文件中 plugins部分 的内容如下:
- 这里的template 表示根据什么模板来生成index.html
- 另外,我们需要删除之前在 output中添加的publicPath属性
- 否则插入的script标签中的src可能会有问题
1.安装插件
2.配置插件 const HtmlWebpackPlugin = require('html-webpack-plugin')
和 new HtmlWebpackPlugin()
3.**另外,我们需要删除之前在 output中添加的publicPath属性 ** 否则插入的script标签中的src可能会有问题
这时我们还有一个问题需要解决 因为index.html文件body下 需要一个 <div id="app"></div>
因此我们在webpack.config.js 中配置这个参数时候可以传入一个 template参数
new HtmlWebpackPlugin({
template: 'index.html'
})
此时 ,打包的时候,就会自动查找 这个index.html ,以这个文件模板进行编译至 dist文件夹
js压缩的Plugin
在项目发布之前,我们必然需要对js等文件进行压缩处理
-
这里我们就对打包的js文件进行压缩
-
我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和 CLI2 保持一致
-
npm install uglifyjs-webapck-plugin@1.1.1 --save-dev
修改webpack.config.js文件进行配置,使用插件:
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
new uglifyJsPlugin()
查看打包后的 bundel.js 文件,是已经被压缩过了的
搭建本地服务器
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果
- 不过它是一个单独的模块,在webpack中使用之前需要先安装它
-
npm install --save-dev webpack-dev-server@2.9.1
**注意:**此处由于webpack使用的是 3.6.0版本 ,对应了 CLI2 因此这里的webpack-dev-server 也需要对应版本,所以使用了 2.9.1
- devserver 也是作为webpack中的一个选项,选项本身可以设置如下属性:
- contentBase :为哪一个文件提供本地服务,默认是根文件夹,我们这里要填写./dist
- port : 端口号
- inline : 页面实时刷新(true , false)
- historyApiFallback :在SPA页面中,依赖HTML5的 history模式
webpack.config.js 文件配置修改如下:
devServer: {
contentBase:'./dist'
inline: true
}
- 我们可以再配置另一script:
- –open 参数表示直接打开浏览器
"dev": "webpack-dev-server --open"
webpack 配置文件抽离
因为有些配置文件是开发阶段需要使用的,还有一些时开发结束,最终打包时候需要使用的,因此需要对配置文件 webpack.config.js 进行抽离
1.创建 build目录存放 这些webpack的配置文件,
- base.config.js
- develop.config.js
- product.config.js
2.需要安装 npm install --save-dev webpack-merge
安装之后就可以在这三个配置文件中进行使用了,先进行抽离
// develop.config.js 开发时候需要的配置
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
devServer: {
contentBase: '/dist',
port: 3000,
inline: true
}
})
// product.config.js 生产发布时候需要的配置
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
plugins: [
new UglifyjsWebpackPlugin()
]
})
注意,此处两个文件因为需要拼接base.config.js 因此都需要加载 webpackMerge的包,其次,由于都需要用到 base.config.js 因此需要先导入这个文件, 然后通过 webpackMerge(基础文件,需要拼接的内容)
3.由于webpack.config.js文件抽离了,原本package.json中的 scripts 配置需要更改,定义的 npm run 快捷中 build的快捷需要 指定 --config ./build/product.config.js
, dev 的快捷也需要添加 ` --config ./build/develop.config.js
4.base.config.js
中输出出口 为path: path.resolve(__dirname, './dist')
,此时会在当前文件夹下建dist文件存储输出的文件,我们需要更改下 path: path.resolve(__dirname, '../dist')
Vue CLI 开始
什么是Vue CLI
- 如果你只是简单写几个Vue的 demmo程序,那么你不需要Vue CLI
- 如果每个项目都要手动完成这些工作,那无疑效率比较低,所以我们通常会使用一些脚手架工具来帮助完成这些事情
CLI是什么意思
- CLI是Command-Line Interface ,翻译为命令行界面,但是俗称脚手架
- Vue CLI 是一个官方发布的vue.js项目脚手架
- 使用vue-cli 可以快速搭建 Vue 开发环境以及对应的webpack配置
cnpm安装(淘宝镜像)
npm insatll -g cnpm --registry=http://registry.npm.taobao.org
脚手架2
1.安装Vue 脚手架
- npm install -g @vue/cli 脚手架全局安装就好了
- 脚手架3安装后,可以拉脚手架2 没有必要单独安装脚手架2
2.注意:上面安装的是Vue CLI3的版本,如果想按照VueCLI2的方式初始化项目是不可以的
-
vueCLI3 和旧版使用了相同的vue命令,所以vue CLI2(
vue-cli
)被覆盖了,如果你仍然想使用旧版本,可以全局安装一个桥接工具 -
npm install -g @vue/cli-init // vue init 的影响效果将会跟 'vue-cli2'相同
3.初始化方式
CLI2 : 命令: vue init webpack my-project
CLI3 : 命令 : vue create my-project
4.初始化后进行的一些配置
- Project name myvuejsproject 项目名字(默认是文件夹名字)
- Project description (项目描述)
- Author (项目作者)
- Vue build (创建模式 runtime-only 还是runtime-compiler)
- Install vue-router (是否使用路由)
- Use ESLint to line your cord (是否 严格要求代码格式) 关闭是在 config文件夹下的 index.js文件中, true 改为 false
- Set UP unit tests (单元测试)
- Set UP e2e test with Nightwatch? (端到端测试)
- Should we run
npm install
for you after the project hasbeen created? (使用的哪种包管理工具)
runtime-only 和 runtime-compiler区别
runtime-compiler( v1 ) 整个解析渲染过程:
- template -> ast -> render -> vdom -> UI
runtime-only ( v2 ) 整个解析渲染过程:
- render -> vdom -> UI
因此可以看出 runtime-only 性能更高,代码量更少,了解区别后,开发尽量使用 runtime-only
其实 后一个版本是在前一个版本的基础上加上了 compiler ,然而在 vue2.0版本后 最终的渲染都是通过 render函数的,如果写template 属性的话,则会对template进行编译,这样其实是对性能的一种损耗。因此我们尽量去使用runtime-only
那么runtime-only 怎么使用,注意点是什么呢?
- 直接组件中来一个 render: function( creatElement ){ return creatElement (组件)}
const APP = require('./src/components/App.vue')
new Vue ({
el:'#app',
render: h => h(App)
})
//此处的参数 h 其实就是 createElement 这个函数 ,这个函数有两种用法
//1. 传递三个参数 元素名字 属性名字(可选) 内容,其中数组内容中可以继续去嵌套createElement函数
new Vue ({
el:'#app',
render: createElement => createElement('h2',
{class: 'box'},
['Hello MOTO', createElement('button',['按钮'])])
})
//2.开发中常用 就是直接传一个vue 组件
new Vue ({
el:'#app',
render: h => h(App)
})
因此我们在实例中就不要再使用 template 定义模板了 ,直接使用render 函数去渲染
原理:
此时你可能会有疑惑,我们定义组件的时候 也就是 App.vue 文件中还是有 template 便签啊, 其实在我们将App导入进来的时候,导入的这个App对象 中是没有 template属性的,为什么没有呢???? 是因为我们之前是用来一个loader, 就是 vue-template-compiler
,它已经将我们的 .vue文件解析过了 ,因此其实已经没有template 了。
脚手架3:
vue-cli3 与 2 版本区别
- vue-cli 3 是基于 webpack 4 打造的, vue-cli2还是webpack3
- vue-cli 3 的设计原则是( 0配置) 移除的配置文件根目录下的 build和 config等目录
- vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
- 移除了 static文件夹,新增了public文件夹,并且index.html 移动到 public中
创建CLI3 : 命令 : vue create my-project
修改webpack一些配置的方式
- 命令行启动 vue ui 打开vue ui界面进行修改
- 去node_modules 中隐藏的@vue中的 cliservice 中的lib 中的service.js中修改
- 根目录创建一个 vue.config.js 进行配置的一些修改 ,文件名字固定不能改
Vue router(重点)
内容概述:
- vue-router基本使用
- vue-router嵌套路由
- vue-router参数传递
- vue-router 导航守卫
- keep-alive
前端路由的两种方式
- location.hash
location.hash = 'foo' //路由后面会添加上 /foo ,执行后未对服务端发送请求
- h5 中的 history
history.back() // 后退一个路由/栈 等价于下面:
history.go(-1)
history.forward() //向前,前进一个路由/栈 等价于:
history.go(1)
- h5中压栈两种方式(两个函数都是三个参数:一个对象,一个title,一个url)
- history.pushState( {} , ‘’ , ‘home’ )
- history.replaceState( ( {} , ‘’ , ‘home’ )
两种方法都是往栈结构里压url,但是pushState 可以通过上面几种方法前进后退,浏览器有记录,replaceState 方法 不可以通过上面几种方法前进后退,,浏览器没有记录
安装和使用vue-router
因为我们已经学习了webpack,后续开发中我们主要是通过工程化的方式进行开发的
- 所以后续,我们直接使用npm来安装路由即可
- 步骤一:安装vue-router
- 步骤二:在模块化工程中使用它(因为是一个插件,所以可以通过Vue.use()来安装路由功能)
- 第一步:导入路由对象,并且调用Vue.use(VueRouter)
- 第二步:创建路由实例,并且传入路由映射配置
- 第三步:在Vue实例中挂载创建的路由实例
// 配置路由相关的信息
//1.导入路由
import Vue from 'vue'
import VueRouter from 'vue-router'
// 2.通过Vue.use()安装插件
Vue.use(VueRouter)
// 3.创建VueRouter对象
const routes = []
const router = new VueRouter({
// 配置路由和组件之间的路由关系
routes
})
// 4.将router对象传入到Vue实例中
export default router
使用vue-router的步骤
- 第一步;创建路由组件
- 第二步:配置路由映射:组件和路径映射关系
- 第三部:使用路由:通过
<router-link>
和<router-view>
简单使用:
1.创建路由组件 home.vue about.vue
2.配置路由映射:组件和路径映射关系
-
router文件夹下index.js中配置
-
const routes = [{ path: '/home', component: Home }, { path: '/about', component: About } ]
几个路由对应几个对象,几个关系
3.使用路由:通过<router-link>
和 <router-view>
渲染
- 找到 App.vue组件,标签中使用
<router-link to="/home">首页</router-link>
这个作用是当点击首页,url改变 <router-view></router-view>
这个标签是个占位符,到时候对应的路由组件就会在这个占位里渲染
路由的默认路径
我们这里还有一个不太好的实现:
- 默认情况下,进入网站的首页,我们希望
<router-view></router-view>
渲染首页的内容 - 但是我们实现中,默认没有显示首页的组件,必须让用户点击才可以
如何可以让路径默认跳到首页,并且<router-view></router-view>
渲染首页组件呢
- 非常简单,我们只需要多配制一个映射就可以了
const routes = [
{
path: '/',
redirect: '/home'
}
]
配置解析
- 我们在routes中又配置了一个映射
- path配置的根路径: /
- redirect 是重定向,也就是我们将根路径重定向到 /home的路径下,这样就得到我们想要的结果了
如何将路径改为history模式
- 我们前面说过改变路径的方式有两种:
- URL的hash
- HTML5的history
- 默认情况下,路径的改变使用的URL的hash
- 如果希望使用HTML5的history模式,非常简单,进行如下配置
const router = new VueRouter({
// 配置路由和组件之间的路由关系
routes,
mode: 'history',
})
router-link补充
在前面的 router-link
中,我们只是使用了一个属性:to, 用于指定跳转的路径
在 router-link
中还有一些其他属性:
- tag: tag可以指定
router-link
之后渲染成什么组件,比如上面的代码被渲染称一个li元素,而不是a - replace:replace不会留下hostory记录,所以指定replace的情况下,回退键不能返回到上个页面
- active-class:
router-link
对应的路由匹配成功时,会自动给当前元素设置一个 router-link-active 的 class ,设置active-class 可以修改默认的名称- 在进行高亮显示的导航菜单或者底部tabbar时 ,会使用到该类
- 但是通常不会修改类的属性,会直接使用默认的router-link-active
- 当标签较多的时候也可以在路由规则中修改,使用 linkActiveClass
路由代码跳转
前面我们使用的是router-link这个标签的 to来更改路由,如果不使用这个标签我们该怎么做呢?
- 首先,我们用任意的标签,对这个标签进行一个事件监听
- 监听事件传递的方法中对 $router进行一个操作。
- 因为我们使用了 vue-router 因此所有组件中都会有一个 r o u t e r 对 象 , 此 时 我 们 只 需 要 调 用 t h i s . router对象,此时我们只需要调用 this. router对象,此时我们只需要调用this.router.push(’/home’) 就可以对路由url进行更改,当然使this.$router.replace(’/home’) 也是可以的
问题,此处连续点同一个按钮,会报一个promise的错误,原因不明???????????
动态路由:
首先是普通的路由配置,创建路由对应的组件,将组件导入路由,进行映射,在出口用router-link,通过to属性去使用。
当使用动态路由则:
- 进行映射的时候原本path 路径后面添加 :userId 此时这个path路径就是动态的了,那么怎么确定 userId呢,在出口组件的data中定义 userId, 然后通过 v-bind 属性绑定的方式动态给router-link 动态绑定 to属性,此时更改data中的 userId,则 router-link 中 to的 路径也会改变。这样就做到了动态绑定了路由
- 那么我们怎么在组件中拿到 userId 这个值呢,可以通过父子组件传值,或者更简单的方式 $route.params.userId拿到
$router 拿到的是路由 router这个实例对象 ; $route 拿到的是 活跃状态组件的路由,也就是new VueRouter({routes}),中处于活跃状态的那个, $route下有一个属性 params ,对应的就是活跃状态路由的参数,里面有{ “userId”: “kenan” } 对象,这样就可以拿到
路由的懒加载
官方给出的解释
- 当打包构建应用时候,Javascript包会变的非常大,影响页面加载
- 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了
官方在说什么呢?
- 首先我们知道路由中通常会定义很多不同的页面
- 这个页面最后被打包在哪里呢?一般情况下是放在一个js文件夹中
- 但是页面这么多放在一个js文件夹中,必然会造成这个页面非常大
- 如果我们一次性从服务器请求下来这个页面,可能需要花费一定的时间,甚至用户的电脑还会出现短暂的空白
- 如何避免这种情况被?使用路由懒加载就可以了
- 就是每个路由对应的组件都打包到不同的js文件中
路由懒加载做了什么?
- 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块
- 只有在这个路由被访问到的时候,才加载对应的组件
路由懒加载的效果
- 不适用懒加载的方式
import Home from '../components/home'
import About from '../components/about'
import User from '../components/user'
const routes = [{
path: '/',
redirect: '/home'
}, {
path: '/home',
component: Home
},
{
path: '/about',
component: About
}, {
path: '/user/:userId',
component: User
}
]
const router = new VueRouter({
// 配置路由和组件之间的路由关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
打包出来后,这三个组件的js代码都是在一起的
- 使用懒加载的方式
const routes = [{
path: '/',
redirect: '/home'
}, {
path: '/home',
component: () => import('../components/home')
},
{
path: '/about',
component: () => import('../components/about')
}, {
path: '/user/:userId',
component: () => import('../components/user')
}
]
const router = new VueRouter({
// 配置路由和组件之间的路由关系
routes,
mode: 'history',
linkActiveClass: 'active'
})
总结 : 就是将路由routes属性中的 component 通过 () => import('../components/home')
函数的方式绑定上,此时打包的时候就会按照组件个数分别打包成js代码块 。 当然 () => import('../components/home')
可以抽离出去。
路由的嵌套(重点)
嵌套路由是一个很常见的功能
- 比如在home页面中,我们希望通过 /home/news 和 /home/message访问内容
- 一个路径映射一个组件,访问这两个路径也会分别渲染两个组件
实现嵌套路由有两个步骤:
- 创建对应的子组件,并且在路由映射中配置对应的子路由
- 在组件内部使用
<router-view></router-view>
标签
1.创建自组件
2.配置映射,因为是路由的嵌套,因此先找到嵌套在哪个路由中
3.在相应的路由中添加属性 children 并传一个数组,数组内传多个 路由对象,其中路由对象的 path 属性直接 给一个不带 / 的 相对路径即可,vue内部会自动拼接
4.通过<router-link>
在嵌套的组件中 通过to属性 绑定这个路径,在通过 <router-view>
将组件页面挂在上去
传递参数(重点)
传递参数主要有两种类型;params 和 query
params的类型:
- 配置路由格式:/router/:id
- 传递的方式: 在path后面跟上对应的值
- 传递后形成的路径:/router/123 , /router/abc
query 的类型:
- 配置路由格式: /router ,也就是普通配置
- 传递的方式; 对象中使用query 的 key 作为传递方式
- 传递后形成的路径: /router?id=123&name=jack , /router?id=abc
1.以普通的方式创建路由,以及组件,在router-link to属性传值的时候,传递一个对象
2.对象中含有path属性,给与与路由映射相应的path, 然后传递第二个属性 query属性
3.该query属性是一个对象,对象中传递的键值对最后会被以 ?key=value&key1=value1的形式拼接到path属性后面
4.组件想要拿到数据可以通过,$route.query 拿到?后拼接的数据
导航守卫
每次路由变化,标题的title也进行相应的变化
- 使用生命周期函数created 每次组件被创建的时候,就会执行这个生命周期,因此在每个路由组件中定义这个生命周期,将document.title赋值为相应的标题
- 通过导航守卫,vue-router提供了beforeEach() 和 afterEach()钩子函数,他们会在路由即将改变前和改变后触发
- 该函数有三个参数,to,from,next
- to是改变后的路由那个对象,from是改变前的那个route对象 ,next是一个函数,只有执行这个函数参会接着往下执行
- 我们在定义routes的时候,传入 meta【元数据】对象,给每个meta对象一个title属性,值为相应的值
- 此时我们通过 to中的 matched 数组中的第 0 个中的 meta对象下的title就拿到了相应的title,将其赋值给document.title 即可
导航守卫补充
- 如果是后置钩子,也就是afterEach,不需要主动调用next()函数
- 上面我们使用的导航守卫,被称为全局守卫,除了这些还有:
- 路由独享的守卫
- 组件内的守卫
keep-alive
keep-alive 是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
- 也就是组件的生命周期 created 和 destroyed 不会被频繁触发
- 他有两个非常重要的属性
- include - 字符串或正则表达,只有匹配的组件会被缓存
- exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
- 注意这两个属性传入的是组件的name属性,多个的时候用逗号隔开,但是一定不能有空格!!!!
router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配的视图组件都会被缓存
业务需求:我们想要首页每次跳转到关于,用户等界面后回到首页,能够记录当时首页选择的是新闻还是信息
1.首先要想每次切换回首页能够不重新创建首页组件进行渲染,那么首先需要将router-view 包裹在keep-alive
中,这样就能够每次切换路由,回来后不是重新创建,再渲染
2.此时这个router-view
中会渲染的组件都不会重新创建再渲染了,当我们选择信息后切换到关于界面,再返回,此时点击首页则会触发路由 /home, 由于路由中又有重定向,因此又会重定向回/home/news
。 这不是我们想要的。
3.那么我们去掉重定向呢??? 那么每次点击 首页 就会去请求/home
这个路由
4.我们一开始进入首页以及后续点击首页(多次点击) 就会反复请求/home
这个路由,当反复请求这个路由,我们还是需要让他指向 /home/news
的,因此不建议更改重定向
5.那么我们就需要在 home
组件中定义一个path去保存路径,每次离开home
这个组件的时候,要去拿到当时 this.$route.path
这个值 ,也就需要用到组件内路由守卫beforeRouteLeave
6.当我们从关于界面重新跳转到首页,那么就会触发activated()
这个函数,由于跳转回来首先是重定向到/home/news
,那么此时我们进行一个判断,如果保存的这个path
不等于 重定向的这个path
,那么我们就调用 this.$router.push(this.path)
更改路由
export default {
name: 'Home',
data() {
return {
path: '/home/news'
}
},
activated() {
if (this.$route.path !== this.path) {
this.$router.replace(this.path)
}
},
beforeRouteLeave(to, from, next) {
this.path = this.$route.path
next()
}
}
TabBar 实现思路(重要)
(视频老师思路)
1.如果在下方有一个单独的TabBar组件,你如何封装?
- 自定义TabBar组件,在APP中使用
- 让TabBar处于底部,并且设置相关的样式
2.TabBar 中显示的内容由外界决定
- 定义插槽
- flex布局平分
3.自定义TabBarItem,可以传入 图片和文字
- 定义TabBarItem,并且定义两个插槽: 图片、文字
- 给两个插槽外层包装div,用于设置样式
- 填充插槽,实现底部TabBar 的效果
4.传入高亮图片
- 定义另外一个插槽,插入 active-icon 的数据
- 定义一个变量 isActive , 通过 v-show来决定是否显示对应 icon
5.TabBarItem绑定路由数据
- 安装路由 npm install vue-router --save
- 完成router/index.js 的内容,以及创建对应的组件
- main.js中注册router
- APP中加入 router-view 组件
6.点击item跳转到对应的路由,并且动态决定 isActive
- 监听item的点击,通过this.$router.replace()替换路由路径
- 通过this.$route.path.indexOf(this.link) !== -1来判断是否是active
7.动态计算active样式
- 封装新的计算属性: this.isActive ? { ‘color’:‘red’ } : { }
(个人思路)
分析这个tabbar界面,tabbar应该抽成一个组件,然后该组件注册为App.vue 的子组件,并且挂到这个组件上
由此开始,先封装tabbar组件
- 搭建界面 tabbar下应该有四个 tabbar-item ,tabbar-item中应该有 icomoon图标 和 文字说明
- 封装好tabbar组件后,App.vue里注册这个组件,并且在App.vue组件中使用
- 调样式,通过flex布局,让tabbar-item 弹性分布
- 此时,tabbar中的内容都是写死的,然而我们希望图标和文字说明应该是 使用组件的人自己去设置的,因此需要在tabbar-item中插入插槽,让App.vue 传入 这个元素内容去填充这个插槽,这样组件的使用者就可以通过App.vue写标签的形式去自行更改内容了。
- 上一步处理后,会发现App.vue 中代码有有点冗余,我们将tabbar抽离为组件,也可以将每个 tabbar-item 也抽离会一个个组件,然后将tabbar-item 放入 tabbar中即可,那么我们有管理整个tabbar的组件,也有每个小item的组件,将item抽离为组件,跟tabbar组件一样,注册为App.vue的子组件,然后在App.vue中通过
<tab-bar-item></tab-bar-item>
添加到页面中,此时我们又发现 TabBarItem中的icomoon和文本又写死了 - 我们需要再一次添加插槽,因此TabBarItem中 可变的标签内容有两个,不能直接通过slot 单个插槽标签解决,因此我们需要使用到具名插槽,注意此处由于使用的是 CLI2 ,vue版本在2.6以下,因此可以使用 slot以前的具名插槽方式
- 抽离后,通过多次使用 TabBarItem 组件 传入不同的数据,就可以完全将数据写活了
- 此时我们基本的页面就写好了,css也需要进行一些规范,App.vue 中引入清除默认样式的css,保存在src/assets 中,这里面保存一个引入的文件,本身每个组件对应的样式写入对应的组件中,这样方便管理
- 接下来就是配置路由了,先安装路由 npm install vue-router --save
- 然后配置路由,路由本身在 src下创建router文件夹,并且创建入口文件 index.js
- 在index.js中 我们导入 Vue 导入 VueRouter ,然后通过 Vue.use( VueRouter),进行一个注册,然后new 一个路由实例对象,将这个对象导出
- 在main.js中需要导入上一步导出的 router实例,然后挂在到 vue实例上,路由的简单配置就配置好了,接下来是配置路由映射
- 在 src下创建view文件夹 存放对应的路由组件,建议先建对应路由模块的文件夹,然后创建组件,这样后期管理更清晰,然后
const routes = [{path:'/home',component:() => import('../views/home/Home)}]
- 如果不想要 hash模式的path ,只要在创建路由实例的时候传入
mode:'history'
即可 ,然后只需要在需要挂在路由的App.vue页面 加入标签 router-view即可, 但是因为没有 router-link标签,因此没有办法监听路由变化,所以我们需要给每个TabBarItem,绑定点击事件,并且传入方法 使用 this. r o u t e r . p a s h ( ) 或 者 t h i s . router.pash( ) 或者 this. router.pash()或者this.router.replace( )方法进行路由跳转,传入的参数不应写死,应该通过父传子的方式拿到数据,因此通过props 定义 link, 在父组件页面每个 TabBarItem组件上绑定 link 属性,并且传递参数为该组件的路由,然后 点击事件传递一个方法,方法通过 props拿到的字符串 ,然后执行 this. r o u t e r . p a s h ( t h i s . l i n k ) 或 者 t h i s . router.pash( this.link) 或者 this. router.pash(this.link)或者this.router.replace( this.link) 注意:此处也许你会疑问 props拿到的到底是App.vue中的哪一个link呢,其实我们在App.vue使用了4次TabBarItem组件,因此每一次都有拿到,只是点击了哪一个就会执行相应的那个组件的方法,传入相应组件的link - 接下来是点击变色,首先想到的肯定是v-bind绑定动态class属性,定义计算属性 isActive, 通过字符串方法 indexOf() 判断 this.$route.path和 this.link,返回 true或false,然后通过 :class="{active:isActive}“来决定是否显示active这个class属性样式
- 但是我们不想写死变换的颜色,因此通过动态绑定class方式就不再可取,需要动态绑定style
- 动态绑定style的话,需要再写一个计算属性 activeStyle, 通过props拿到 使用组件的人传递进行的参数 activeColor=“blue”,并且给activeColor一个默认值 red, 限定类型为 String。 activeStyle中通过一个三元表达式 ,
isActive?{color:this.activeColor}:{}
- 这样我们就将活跃的颜色写活了!!!!
vue项目中起别名
CLI2中找到 build中 webpack.base.comfig.js
找到起别名resolve 中 的alias
, 然后只要是通过import导入的 都可以使用这个别名的绝对路径,如果是html中 src等属性使用的话 就需要在路径前面加上~
符号 表示 也是用这个别名
ES6 promise 学习
什么是Promise呢?
ES6中一个非常重要和好用的特性及时Promise
- 但是初次接触Promise会一脸懵逼,这是什么东西?
Promise到底是做什么的呢?
- Promise是异步编程的一种解决方案
那什么时候我们会来处理异步事件呢?
- 一种很常见的场景应该就是网络请求了
- 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能简单的像 3+4=7一样结果返回
- 所以往往我们会传入一个另外函数,在数据请求成功时候,将数据通过传入的函数回调出去
- 如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦
但是非常复杂的时候,就会出现回调地狱
什么情况下使用Promise呢?
一般情况下是有异步操作时,使用Promise对这个异步操作进行封装
new => 构造函数(1.保存了一些状态信息 2. 执行传入的函数)
在执行传入的回调函数时,会传入两个参数,resolve,reject,本身又是函数
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('helloworld')
// reject('error Javascript')
}, 1000)
}).then((date) => {
console.log(date);
console.log(date);
console.log(date);
}).catch((error) => {
console.log(error);
console.log(error);
console.log(error);
})
// 另一种写法 then方法中传递成功和失败 两个回调
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('helloworld')
// reject('error Javascript')
}, 1000)
}).then(date => {
console.log(date);
console.log(date);
console.log(date);
},error => {
console.log(error);
console.log(error);
console.log(error);
})
Promise三种状态?
首先,当我们开发中有异步操作时,就可以给异步操作包装一个Promise
- 异步操作之后会有三种状态
我们一起来看看这三种状态
- pending:等待状态,比如正在进行网络请求,或者定时器没有到时间
- fulfill:满足状态,当我们主动回调了 resolve 时,就处于该状态,并且会回调 .then( )
- reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调 .catch( )
Promise的链式调用
就是每次成功的回调后面返回一个新的 Promise实例对象,对新的异步操作进行处理,然后接着上一个then( )函数后面继续 .then( )函数处理新的成功或者失败操作,如果有很多个,一直循环往复即可
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello111')
}, 1000)
}).then(data => {
console.log(data);
console.log(data);
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello222')
}, 1000)
})
}).then(data => {
console.log(data);
console.log(data);
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello333')
}, 1000)
})
}).then(data => {
console.log(data);
console.log(data);
console.log(data);
})
当第一次异步操作拿到数据后,后续继续想通过promise对数据进行一些操作的话可以省略返回的 new Promise的写法,直接返回一个 Promise.resolve(),在括号内对数据进行一些操作, 当然也可以直接返回 括号内对数据的操作 继续省略 Promise.resolve()。 本身promise内部还是会对返回的 数据进行一层promise的封装
当然这种方式也可用在reject错误回调上, Promise.resolve()改为 Promise.reject() 即可,当然除了通过 reject()回调拿到错误,还可以通过 throw 抛出异常的方式,catch依然会拿到错误对象
// 第一种
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
}).then(data => {
console.log(data);
return new Promise((resolve, reject) => {
resolve(data + 'ooo')
})
}).then(data => {
console.log(data);
return new Promise((resolve, reject) => {
resolve(data + 'aaa')
})
}).then(data => {
console.log(data);
})
// 第二种
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
}).then(data => {
console.log(data);
return Promise.resolve(data + 'www')
}).then(data => {
console.log(data);
return Promise.resolve(data + 'kkk')
}).then(data => {
console.log(data);
})
// 第三种
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
}).then(data => {
console.log(data);
return data + 'www'
}).then(data => {
console.log(data);
return data + 'kkk'
}).then(data => {
console.log(data);
})
Promise的all()方法使用
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: '皮卡丘',
attribute: '电'
})
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: '小火龙',
attribute: '火'
})
}, 1000)
})
]).then(result => {
console.log(result);
})
Promise.all( ) 方法可以对多个异步操作直接结束通过result拿到多次的异步resolve结果,结果以数组的形式保存在result中, 本身Promise.all( ) 传递一个数组,数组中传递Promise实例对象
Promise.race()方法
同样的它传入的也是promise对象列表 不过他们执行顺序是按照谁快 谁先输出
const pro1 = new Promise((resolve,reject) => {
setTimeout(resolve,100,'1');
});
const pro2 = new Promise((resolve,reject) => {
setTimeout(resolve,200,'2');
});
const pro3 = new Promise((resolve,reject) => {
setTimeout(resolve,300,'3');
});
const pro4 = new Promise((resolve,reject) => {
setTimeout(resolve,10,'4');
});
Promise.race([pro4,pro1,pro2,pro3]).then(data => {
console.log(data); // 1 输出最快的那个
}).catch(err => {
console.log(err);
})