vue基础学习
Vue基础学习第一章 NPM包管理工具1.1 认识一下什么是NPMnpm(Node Package Manager node包管理器) 是Node.js默认的以JavaScript编写的软件包管理系统npm来分享和使用代码已经成了前端的标配 官网:http://www.npmjs.comnpm是Node.js默认的软件包管理系统包系统 安装完毕node后会默认安装好npm npm本身也是基于Nod
Vue基础学习
第一章 NPM包管理工具
1.1 认识一下什么是NPM
npm(Node Package Manager node包管理器) 是Node.js默认的以JavaScript编写的软件包管理系统
npm来分享和使用代码已经成了前端的标配 官网:http://www.npmjs.com
npm是Node.js默认的软件包管理系统包系统 安装完毕node后会默认安装好npm npm本身也是基于Node.js开发的软件
下载Node:http://nodejs.cn
1.2 安装NPM工具 npm的常用命令
VScode调用控制台ctrl + Tab键上面的
Win+r 调用电脑系统的控制台 再输入cmd
node -v / npm -v 查看版本号
npm install bootstrap -g 全局安装
npm config get registry 查看镜像的配置结果
npm config set registry https://registry.npm.taobao.org --global 设置成淘宝镜像(国内更快)
npm config set registry https://npmjs.org/ 设置当前地址(设置为默认地址)
npx nrm use taobao 使用nrm工具切换淘宝源
npx nrm use npm 如果之后需要切换回官方源可使用
$npm install -g cnpm --registry = https://registry.npm .taobao.org 使用淘宝定制的cnpm命令行工具代替默认的npm
npm list -g 查看所有全局安装模块
npm list jquery 查看某个模块的版本号
npm uninstall jquery 删除
npm update jquery 更新
npm install -save moduleName 在package.json文件的dependencies节点写入依赖,发布后还用
npm install -save-dev moduleName 开发时依赖
ctrl + c 退出
Alt + Shift + a VScode中的全选注释
1.3 Package.json属性说明
1.3.1 Package.json的生成
- 在命令行输入npm init --yes 会自动生成package.json文件 默认加载的属性都是空的
{
"name": "eduwork", 包名
"version": "1.0.0", 包版本号
"description": "", 对项目(包)的描述
"main": "index.js", 主入口文件 require('moduleName')就会加载这个文件 默认是模块根目录下的index.js
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1", //npm run test 相当于在运行里面的命令脚本
"hello": "node -v" //npm run hello
},
"keywords": [], 关键字
"author": "", 包的作者姓名
"license": "ISC" 七种规范
}
-
npm init 回车后自己指定 一个一个的初始化
test comand: //测试命令 git repository: //代码存放地方的类型
1.3.2 Package-lock.json的作用
当在命令行安装npm install bootstrap -save-dev && npm install jquery -save 后会出现一个package-lock.json文件 然后package.json里面会出现 :
{
"name": "eduwork",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": { 依赖包列表 如果依赖包没有安装 npm会自动将依赖包安装在node_module目录下
"jquery": "^3.6.0"
},
"devDependencies": {
"bootstrap": "^4.6.0"
}
}
作用:当我把项目拷给别人时不拷扩展包 在package.json中有记录 直接使用npm install 后会找配置文件 把所有的文件所记录的都下载下来 package-lock.json文件是依据package.json生成的 为了锁定固定版本
先查找package-lock.json如果里面有变化 package.json会根据变化来写入
“^3.X.X” 会按照后面的X的最大版本更新
“~3.5.X”
“3.0.0” 固定的不变
1.4 NPM安装的包的使用
没有webpack之前搜寻整个node_modules目录来定位每个包的路径再手动添加到我们HTML文件中(实在太不方便了)
CommonJS中很大的一部分便是对模块系统的规范
使用require语句导入包
新的ES6可以使用import导入包
1.4.1 ES6兼容性解决
比较通用的工具方案有babel jsx等
-
在线转换 (这种编译会加大页面渲染的时间) 客户端渲染
<script src = "https://lib.baomituj.com/babel-core/5.8.38/browser.js"></script> <script type="text/babel"> const name = "学习天地"; consloe.log(name); </script>
-
提前编译 (强烈建议这种方式 不影响浏览器渲染时间) 服务器端渲
-
使用NPM全局安装babel-cli包 (babel -V)
-
在项目目录下新建一个.babelrc文件(这是babel的配置文件)
{ "presets":["es2015", "stage-2"], //设置转码规则 "plugins":["transform-runtime"] // 设置插件 }
-
npm install babel-core babel-preset-es2015 babel-plugin-transform-runtime bable-preset-stage-2 -save-dev //把需要用到的工具再重新安装
-
在项目下(项目的根目录下)新建源代码目录src 再新建一个lib目录 在src目录下新建一个index.js文件
const name = "学习天地"; consloe.log(name);
将src目录下所有写的ES6语法的js文件在开发阶段渲染到lib目录下 在package.json文件下写入:
"scripts":{"build": "babel src -w -d lib"},
后在命令行npm run build 然后lib目录下出现index.js文件
"use strict";var name = "学习天地"; consloe.log(name);
这样各种浏览器就兼容了
-
1.5 NPM的竞品yarn的安装和使用
1.5.1 yarn是什么?
Yarn是由Facebook,Google,Exponent和Tilde联合推出了一个新的JS包管理工具 为了弥补NPM5的一些缺陷而出现的
npm install的时候巨慢
同一个项目 多人开发时 由于安装的版本不一致出现bug
官网:www.yarnpkg.com
第二章 webpack实战教程
2.1 认识webpack5
webpack是一个模块打包器(构建工具).它的主要目标是将JavaScript文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换(transform),打包(bundle)或包裹(package)任何资源(resource or asset)。
官网: https://webpack.js.org/ 中文文档: http://webpack.docschina.org/
2.2 了解webpack原理和概念
- 树结构: 在一个入口文件中引入所有资源,形成所有依赖关系树状图
- 模块: 模块就是可以是ES6模块也可以是commonJS或者AMD模块,对于webpack来说,所有的资源(css,img…)
- chunk: 打包过程中被操作的模块文件叫做chunk,例如异步加载一个模块就是一个chunk
- bundel: bundel是最后打包后的文件,最终文件可以和chunk长得一模一样,但是大部分情况下他是多个chunk的集合
为了优化(翻译 优化 压缩等)最后生产出的bundle数量可能不等于chunk的数量,因为有可能多个chunk被组合到了一个bundle中
2.3 webpack安装和体验
-
创建项目目录: webpack5
-
进入目录初始化NPM操作:npm init -y (生成package.json文件)
-
安装webpack及webpack-cli:npm install webpack webpack-cli -D (-D = --save-dev)
-
创建src目录并且根据需要创建下面的子目录
one.js
let n = 100;function add(x, y) { return x + y;}function demo(string) { return string;}module.exports = { n: n, add: add, demo: demo}
two.js
const { n, add, demo } = require('./one');let b = 20;let sum = add(n, b);module.exports = { sum }
-
在src下创建一下js文件,和一个主入口文件index.js
index.js
const { sum } = require('./two');console.log(sum);
-
控制台运行命令:webpack --mode=development (开发环境)
控制台运行命令:webpack --mode=production (生产环境)
-
可以使用node运行打包后的资源,也可以使用HTML引入打包后的资源
2.4 webpack的5个核心概念
webpack配置文件webpack.config.js(默认是这个名称 可以自己设置)
作用:指示webpack干哪些活,当运行webpack指令时会加载其中配置
构建工具(webpack.config.js)基于nodejs平台运行的,模块化默认采用common.js,而项目文件(src内文件)采用的是ES6语法
用控制台输入命令配置文件时太过麻烦 文件很多的时候每次都需要输入:
webpack ./src/index.js -o ./build/build.js --mode development 开发环境webpack ./src/index.js -o ./build/build.js --mode production 生产环境
用webpack.config.js配置文件(在项目文件下创建):
const { resolve } = require('path'); //resolve用来拼接绝对路径的方法,NODE的方法module.exports = { // 1.entry 入口 指示webpack以哪个文件作为入口起点开始打包 分析构建内部依赖图 entry: './src/index.js', //默认是./src/index.js // 2.output 输出 指示webpack打包后的资源bundles输出到哪里 以及如何命名 output: { //默认是dist/main.js filename: 'build.js', //文件名 path: resolve(__dirname, 'bulid') //输出的目录 }, // 3.loader 让webpack能够去处理那些非JavaScript资源css、img等,让他们处理成webpack能够识别的资源,可以理解成一个翻译过程(webpack自身只能理解js和json) module: { rules: [ ] }, // 4.plugins 插件 可用于执行范围更广的任务,插件的范围包括从打包优化到压缩,一直到重新定义环境中的变量等 plugins: [ ], // 5.mode 模式 指示webpack使用相应模式的配置 // 开发模式(development):配置比较简单,能让代码本地调试运行环境 // 生产模式(production):代码需要不断优化达到性能最好,能让代码优化上线运行的环境 // 都会自动启用一些插件,生产模式使用插件更多 mode: 'production'}
2.4.1 多个入口和多个出口的情况
const {resolve} = require(‘path’);
module.exports = {
单入口:如果只有一个入口,使用字符串,指定一个入口文件,只打包一个chunk,输出一个bundle,chunk的名称是默认的
entry: ‘./src/index.js’
多入口 数组格式 可以写多个入口的文件 所有的入口文件会形成一个chunk,名称是默认的(output里面的filename),输出也是只有一个bundle
entry: [
“./src/index.js”,
“./src/main.js”
]Object 写法 多入口 有几个入口文件就会生成几个chunk,并输出几个bundle,chunk的名称是key =>one.js two.js
entry: { one: './src/index.js', two: './src/main.js'},output: { filename: '[name].js', path: resolve(__dirname, 'bulid') } > 特殊用法 多入口 有几个入口文件就会生成几个chunk,并输出几个bundle chunk的名称是key > > entry: { > > //Array 多入口 可以写多个入口的文件 所有的入口文件会形成一个chunk,名称是默认的,输出也是只有一个bundle > > onea: ['./src/main.js', './src/index.js'], > > //混合式写法 > > twob: './src/index.js' > > }
}
2.5 打包和压缩HTML资源
使用插件(plugins)对HTML文件进行处理(html-webpack-plugin) 打包html文件用插件,打包css等用的是loader,loader直接下载使用就好了,而plugins需要下载引入使用。
-
下载安装 npm install html-webpack-plugin -D
-
在webpack.config.js中引入插件:const HtmlWebpackPlugin = require(‘html-webpack-plugin’)
-
使用插件:
plugins: [ // 默认会创建一个空的html文件,目的是自动引入打包的资源(js/css) // new HtmlWebpackPlugin() //通过参数可以输出有结构的HTML资源 new HtmlWebpackPlugin({ //复制'./src/index.html'文件,并自动引入打包输出的所有资源(JS/CSS) template: "./src/index.html", //默认是index.js名称,通过filename设置输出文件名称 filename: "demo.html", //压缩html代码 minify: { //移除空格 collapseWhitespace: true, //移除注释 removeComments: true } }) ]
打包完之后的文件自动会引入我们需要的js文件和css文件,不用我们手动的引入了而且也都会给我们打包到build目录下,我们直接把这个文件夹的内容拷走上线就可以了。直接全打包在一起
2.5.1 压缩JS和HTML代码
JS代码只需设置成生产模式(production),会自动压缩
压缩HTML方法:
new HtmlWebpackPlugin({ //复制'./src/index.html'文件,并自动引入打包输出的所有资源(JS/CSS) template: "./src/index.html", //默认是index.js名称,通过filename设置输出文件名称 filename: "demo.html", //压缩html代码 minify: { //移除空格 collapseWhitespace: true, //移除注释 removeComments: true } })
2.5.2 webpack打包多html开发案例
详情见webpack1文件夹
2.6 使用webpack打包CSS资源
需要使用npm下载安装两个loader帮我们完成打包 npm install css-loader style-loader -D
- css-loader的作用是将css文件捆绑到js文件中。[js文件中需要引入=>require(’./style.css’)]
- style-loader的作用是把捆绑到js文件中的css代码在我们的html文件的head中创建一个style标签(内联式),把css代码插入到这个标签中。(在html文件中看不到,是js文件中插入的,需要F12看源代码可见)
style.css
#box { width: 100px; height: 100px; color: red; background-color: green;}
index.js
require('./style.css');console.log('这是入口文件');
webpack.config.js
const { resolve } = require('path');// 引用插件html-webpack-pluginconst HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry: './src/index.js', output: { filename: '[name].js', path: resolve(__dirname, 'build') }, module: { rules: [{ test: /\.css$/, //以.css结尾的文件 use: ['style-loader', 'css-loader'] //从右到左读取 先css-loader后style-loader 如果是一个直接loader: 'css-loader' }] }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", filename: "index.html", }) ], mode: 'development'}
详情看webpack2
2.6.1 webpack打包less或sass资源
因为css只是单纯的属性描述,它并不具有变量、条件语句等,css的特性导致了它难组织和维护。Sass和Less都属于CSS预处理器,定义了一种新的语言,其基本思想是用一组种专门的编程语言,为CSS增加一些编程的特性,将CSS作为目标生成文件,然后开发者使用这种语言进行CSS编码工作。
Less需要使用npm下载less包和less-loader [less-loader => 帮我们把less文件的内容转换成css]
Sass需要使用npm下载node-sass和sass-loader
lessstyle.less
@width: 200px;@height: 200px;@color: red;#box2 { width: @width; height: @height; color: @color; background-color: pink;}
sass.scss
$height: 200px;$color: yellow;#box3 { width: 100px * 3; height: $height * 2; color: $color; background-color: blue;}
src/index.js
require('./style.css');require('./lessstyle.less')require('./sass.scss')console.log('这是入口文件');
src/index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> 打包css文件<br> <div id="box"> css </div> <div id="box2"> less </div> <div id="box3"> sass </div></body></html>
webpack.config.js
const { resolve } = require('path');// 引用插件html-webpack-pluginconst HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry: './src/index.js', output: { filename: '[name].js', path: resolve(__dirname, 'build') }, module: { rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader'] //从右到左读取 先css-loader后style-loader 如果是一个直接loader: 'css-loader' }, { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", filename: "index.html", }) ], mode: 'development'}
详情见webpack2
2.6.2 提取CSS为单独文件
css内容是打包在js文件中的,可以使用"mini-css-extract-plugin"插件提取成单独的css文件。
- 在webpack.config.js中引入插件
const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’);
- 在plugins模块中使用插件
plugins: [new MiniCssExtractPlugin()],
或通过参数filename重新命名提取的css文件名
new MiniCssExtractPlugin({filename: ‘demo.css’})
- 在css的rules中,使用 MiniCssExtractPlugin.loader取代style-loader,提取js中css内容为单文件
{test: /\.css$/, use:[MiniCssExtractPlugin.loader, 'css-loader']}如果sass和less也提取成单独的css文件,也一样将style-loader换成MiniCssExtractPlugin.loader{test: /\.css$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']}
最终html文件中的head标签中会出现 且所有的css文件打包成了一个文件
详情见webpack2
2.6.3 处理css的浏览器兼容性
开发的时候浏览器有很多厂商,不同的厂商出产的浏览器包括浏览器不同的版本,对css、html、javascript的支持是不同的
src中的css样式 转成浏览器支持后仍不变
display: flex; backface-visibility: hidden;
display: -webkit-flex; display: flex; -webkit-backface-visibility: hidden; backface-visibility: hidden;
需要使用postcss处理,下载两个包postcss-loader和postcss-preset-env
npm install postcss-loader postcss-preset-env -D
postcss会找到package.json中的browserslist里面的配置,通过配置加载css的兼容性
- 下载两个包
- 在webpack.config.js文件中写入’postcss-loader’写在最右边,从右往左翻译,先转成浏览器支持的兼容性的问题再打包再加入到页面
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] }, { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader', 'postcss-loader'] }, { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'] }
- 通过’postcss-loader’配置文件会自动查找postcss.config.js文件中加载插件
postcss.config.js
module.exports = { plugins: [ require('postcss-preset-env')() ]}
- 通过插件会自动查找package.json文件中设置的支持什么浏览器的列表,然后帮我们翻译
"browserslist": [ "> 0.2%", "last 2 versions", "not dead" ]
2.6.4 压缩css
使用optimize-css-assets-webpack-plugin插件压缩css内容
- 下载插件
npm i optimize-css-assets-webpack-plugin -D
- 引入插件 (在webpack.config.js文件中)
const OptimizeCssAssetsWebpackPlugin = require(‘optimize-css-assets-webpack-plugin’);
- 使用插件 (在webpack.config.js文件中)
plugins: [new OptimizeCssAssetsWebpackPlugin()]
去除注释和空格
2.7 webpack打包图片资源
在css文件中使用图片打包
- 需下载url-loader和file-loader两个包,依赖关系
npm install url-loader file-loader -D
- 在src/css中引入图片
#img1 { width: 200px; height: 200px; background-image: url(./bottom-icon.png); background-repeat: no-repeat; background-size: 100% 100%;}#img2 { width: 200px; height: 200px; background-image: url(./xiaozhan1.jpeg); background-repeat: no-repeat; background-size: 100% 100%;}#img3 { width: 200px; height: 200px; background-image: url(./xiaozhan2.png); background-repeat: no-repeat; background-size: 100% 100%;}
src/html
使用图片<br> <div id="img1"></div> <div id="img2"></div> <div id="img3"></div>
- webpack.config.js
{ test: /\.(png|jpg|jpeg|gif)$/, // use: ['url-loader', {loader: 'file-loader', options: {}}] loader: 'url-loader', options: { publicPath: './images/', //访问路径 公共的路径 outputPath: 'images/', //输出的路径 limit: 1024 * 8, //限制 1024个字节是1K 这里是8k 8k以下的转bate64的格式 以上的形成独立的文件在images中 name: '[name][hash:10].[ext]', //自定义图片名称 [hash:10]取前十位 } },
在html中使用图片需要下载html-loader
npm i html-loader -D
然后在webpack.config.js文件中使用
{ test: /\.(png|jpg|jpeg|gif)$/, // use: ['url-loader', {loader: 'file-loader', options: {}}] loader: 'url-loader', options: { publicPath: './images/', outputPath: 'images/', limit: 1024 * 8, name: '[name][hash:10].[ext]', esModele: false } }, { test: /\.html$/, loader: 'html-loader', }
html-loader不是打包html文件的,而是告诉编译html里面写图片的时候用url-loader来处理
详情见webpack2
2.7.1 打包其他资源字体图标
不需要优化和压缩处理,直接输出的资源称为其他资源(字体图标)
iconfont.html
<i class="iconfont icon-react"></i><i class="iconfont icon-vue"></i><i class="iconfont icon-webpack"></i>
webpack.config.js
{ exclude: /\.(js|json|html|css|less|scss|png|jpg|gif|jpeg)$/, //排除css/js/html和图片 loader: 'file-loader', options: { outputPath: 'font/', publicPath: './font/', name: '[name].[ext]' }}
2.7.2 对js语法配置eslint进行检查
webpack默认打包js资源和json资源,不用处理,但是在打包的时候需要优化一下,检查一下js代码,规范一致的语法风格。(空格 空行 分号等)
eslint是一个开源的js代码检查工具,初衷是为了让程序员可以创建自己的检测规则。实际生产中,团队内往往会制定一套统一的标准,让整个团队的编码风格达到一致。eslint其实与webpack没有任何关系,两者并不下相互依赖,甚至一般情况下我们并不会在webpack中进行eslint的配置,可以单独使用。
语法检查使用eslint-loader,并基于eslint包,只用来检查js语法。注意只检查自己写的js源代码,第三方库是不用检查的,可以在npmjs.com中查看规则。
需要使用js来的规则库来检查代码’airbnb’,需要下载eslint-config-airbnb-base和eslint-plugin-import两个包
eslint-loader依赖eslint-config-airbnb-base这个包 会自动去package.json文件中找
npm i eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import -D
//package.json中加入"eslint": { "extends": "airbnb-base"}
//webpack.config.js中加入{ test:/\.js&/, //只检查js语法 exclude: /node_modules/, //只加检查自己写的代码 不检查第三方库的代码 loader: 'eslint-loader'. options: { fix: true //自动修复 }}
//在js文件中加入//下一行eslint所有规则失效//eslint-disable-next-line eslint会解析这个注释console.log('这是入口文件', 11111)
2.7.3 开发服务器devServer配置
devServer给我们提供了开发过程中的服务器,它的作用主要是为了监听资源文件的改变,只有资源文件发生改变,webpack-dev-server就会实时进行编译。只会在内存中编译(不会生成build目录文件),速度快,提高开发效率,大大降低了等待的时间。
下载webpack-server包。webpack-dev-server并不能读取你的webpack.config.js的配置output。启动devServer指令为:npx webpack serve 本目录执行
npm install webpack-dev-server -D
webpack4 => 在命令行运行 npx webpack-dev-server 可自动打开浏览器自动刷新 (webpack5不支持)
webpack5 => webpack serve
监听文件的变化 在html文件或js文件中改变并保存后 打开相应的端口(http://localhost:8080/)即可查看
改变webpack.config.js文件中的内容需要重启一下(ctrl+c) 命令行输入 webpack serve
webpack5无法刷新,解决:添加配置:target: ‘web’
在webpack.config.js文件中target: 'web' //webpack5需要加上这个配置才可以自动刷新浏览器
可以通过命令行参数进行配置
在命令行输入 webpack serve --port 3000 => 改变端口 访问(http://localhost:3000/)
这样配置会导致命令行很长,每次配置都很麻烦,可以将命令行写到package.json文件中有一个专门执行脚本的地方
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack serve --mode development --port 3000", "build": "webpack --mode production" //编译打包 生成build目录 },
在命令行中输入 npm run build (编译打包 生成build目录) 开发模式 npm run dev (监听文件的变化)
这样写还是比较复杂,一般有命令行可以指定后面选项参数的在配置文件中都可以指定
//在webpack.config.js文件中//添加配置devServer: { port: 3000, //设置服务器端口号 compress: true, //自动gzip压缩 open: true //自动打开浏览器}
详情可看webpack2文件
2.7.4 开发环境的优化HMR模块热替换(开发环境的优化)
环境的优化分为开发环境的优化(打包构建速度、优化代码调试)和生产环境的优化(代码运行的性能)
模块热替换(Hot Module Replacement)是webpack提供的最有用的功能之一,它允许在运行时更新各种模块,而无需进行完全刷新。
启用这个功能只需要修改一下webpack.config.js的配置,使用webpack内置的HMR插件就可以了,在devServer中使用hot参数:
devServer: { port: 3000, //设置服务器端口号 compress: true, //自动gzip压缩 open: true, //自动打开浏览器 hot: true }
- 样式HMR功能,在开发环境中使用style-loader(上线的时候才把样式提取成单独的文件)
//webpack.config.js文件中{ test: /\.css$/, // use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'], use: ['style-loader', 'css-loader', 'postcss-loader'] },
- HTML的HMR功能默认也没有HMR功能(不用做HMR功能),需要在entry入口中引入html文件。
//webpack.config.js文件中 entry: ['./src/index.js', './src/index.html'],
- js的HMR功能,默认没有HMR功能,只能处理非入口文件的js文件(入口的文件必须加载然后编译)
启用webpack内置的HMR插件后,module.hot接口就会暴露在index.js中,接下来需要在index.js中配置告诉webpack接受HMR的模块:
//index.js文件中if (module.hot) { module.hot.accept('./print.js', function() { console.log('print.js这个文件内容有改变') })}
详情看文件webpack2
2.7.5 去除项目里的死代码(无用的js和css代码(生产环境的优化)
- Webpack(自带树摇功能)通过tree-shaking去掉了实际上并没有使用的js代码来减少包的大小。
- 必须使用es6模块化
- 开启production环境
- 去除没有用到的css
webpack使用purgecss-webpack-plugin去除无用的css(在打包前先去掉css中无用的代码)
npm i purgecss-webpack-plugin -D
//在webapck.config.js文件中const {resolve, join} = require('path');const purgecssPlugin = require('purgecss-webpack-plugin');const glob = require('glob');const PATHS = {src:join(__dirname, 'src')}plugins: [ new purgecssPlugin({ paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true}), })]
通过这个插件读取html文件,看看html里面用到了什么样式,然后再找到css文件把里面的样式提出了,提出了后打包提取成单独的文件
第三章 ES6语法和应用
3.1 es6新增let关键字的语法应用
3.2 es6新增const关键字的使用
3.3 箭头函数和this指向问题
ES6允许使用"箭头"(=>)定义。不需要参数或需要多个参数,就用圆括号。代码块部分多余一条语句(一条语句就不需要,并且自带return),就用大括号括起来,并且用return返回。
箭头函数返回对象时,必须在对象外面加上()
const fun = id => ({id: id, name: 'zhangsan'});console.log(fun(10).id); //10
如果不用括号的话,会认为{}不是json对象的{},而是函数的{}。函数不认识这个语法,所以需要使用(),防止{}冲突
关于箭头函数中this的指向:
普通函数的this:指向它的调用者,如果没有调用者则默认指向window
箭头函数的this:指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this
综上:箭头函数没有自己的this,它的this是继承而来,默认指向在定义它时所处的对象(宿主对象)
箭头函数里面没有构造方法,做不了构造器(也没有继承的方法)去new对象。所有new普通对象还是使用传统方法,不使用箭头函数
3.4 数组中新增加的高级函数
filter forEach some every map reduce等
3.5 ES6新增的数据结构Map和Set
Map数据结构类似于对象,是键值对的集合,传统的键只能用字符串,Map的键不限于字符串,各种类型的值(包括对象)都可以当作键。
属性和操作方法:
- size属性,返回Map结构的成员总数
- set(key, value)方法,设置set方法设置键名key对应的键值为value,然后返回整个Map结构。如果key已经有值,则键值会被更新。
- get(key)方法,读取key对应的键值,如果找不到key,返回undefine
- has(key)方法,返回一个布尔值,表示某个键是否在当前Map对象之中
- delete(key)方法,删除某个键,返回ture,如果删除失败,返回false
- clear方法,清除所有成员,没有返回值
Map遍历
- keys()返回键名的遍历器
- values()返回键值的遍历器
- entries()返回所有成员的遍历器
- forEach()遍历Map的所有成员
Set往里存值,没有键:
const obj = new Set();//添加数据addobj.add(1);console.log(obj); // {1}obj.add(2);console.log(obj); // {1, 2}obj.add(3);console.log(obj); // {1, 2, 3}obj.add(['aa', 'bb']);console.log(obj); //{1, 2, 3, ['aa', 'bb']}//如果添加的是重复的数据则添加不进去obj.add(2);console.log(obj); //{1, 2, 3, ['aa', 'bb']}//判断是否有这个数据obj.has(4); //false//删除obj.delete(2); //trueobj.delete(7); //falseconsole.log(obj); //{1, 3, ['aa', 'bb']}//遍历obj.forEach(n => console.log(n));//1//3//['aa', 'bb']obj.size //3obj.clear //{}
3.6 字符串新增的方法和模板字符串
3.7 解构赋值和三点扩展运算符号
三点扩展运算符号:
let arr1 = [1, 2, 3];let arr2 = [4, 5, 6];let arr = [...arr1, 7, 8, 9, ...arr2];console.log(arr); //[1,2,3,4,5,6,7,8,9]function show(a,b,c) { console.log(a); console.log(b); console.log(c);}let arr = [1,2,3];show(...arr);function demo(...args) { console.log(args);}demo(1,2,3); //[1, 2, 3] function demo(a, b, ...args) { console.log(args);}demo(1,2,3,4,5,6);//[3,4,5,6]
解构赋值左右两边结构必须一样,右边必须有值,声明和赋值不能分开
3.8 ES6新增class用法和JSON的新应用
JSON对象的新应用:
- JSON.stringify() 串行化(将对象转换为字符串)
let name = "张三";let age1 = 22;//const obj = {name: name, age: age1, sex: "男", say: function() {}};const obj = {name, age: age1, sex: "男", say() { console.log(this.name);}};console.log(obj); //{name: "张三", age: 10, sex: "男"}let str = JSON.stringify(obj);console.log(str); //'{name: "张三", age: 10, sex: "男"}'
- JSON.parse() 反串行化(将字符串转换为对象)
let o = JSON.parse(str);console.log(o.name); //张三
3.9 Module模块化编程export和import的使用
模块化优点:减少命名冲突,避免引入时的层层依赖,可以提升执行效率
export命令:用于规定模块的对外接口,一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量
import命令:用于输出其他模块提供的功能。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。
one.js
let name = "zhangsan";export let age = 20;//可以一个一个的导出export function add(x, y) { return x + y;}//可以一起声明导出//export {name: name, age: age, add: add};//export {name, age, add};export class Person { constructor() { } say() { console.log('this is a test') }}console.log(add(100, 200)); //300export {name as myname, age, Person, add};
two.js
let name = "李四";export function add(x, y) { return x * y;}//一个模块中只能有一个export default export default function(args) { console.log(args);}export default function() { }
index.js
let a = 100;import {add, Person} from './one.js';import {add as mul} from './two.js' //不能重名 使用时可以另起名字console.log(add(10, 20)); //30console.log(mul(10, 20)); //200//import {Person} from './one.js';const p = new Person();p.say(); //this is a testimport print from './two.js'; //可以任意起名字print('hello'); //helloimport * as one from './one.js';console.log(one.add(20, 60)); //80console.log(one.myname); //zhangsan
第四章 axios异步网络请求技术
4.1 认识axios
Axios简单的理解就是ajax的封装(和ajax功能一样,比ajax更强大,使用更简单。只用到了jquery里面ajax的功能),是一个基于promise的HTTP库,支持node端和浏览器端,使用Promise管理异步,告别传统callback方式,丰富的配置项,支持拦截器等高级配置,转换请求数据和相应数据
4.2 为axios应用准备RestFul标准API
项目的两种编程方式:模板式编程(前后端不完全分离)和接口式编程(前后的分离)
我们配置接口时,建议按照RestFul API规范来使用(URL,HTTP,版本,状态码,返回值,请求条件等规范)
- GET(SELECT):从服务器取出资源(一项或多项)
- POST(CREATE):在服务器新建一个资源
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
- DELETE(DELETE):从服务器删除资源
4.3 Postman的安装和基本使用
Postman一款非常流行的API调试工具 http://www.getpostman.com/apps
4.4 ES6中Promise的原理和应用
主要用于异步计算,可以将异步操作队列化,安装期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作promise,帮助我们处理队列。
异步回调的问题:之前处理异步是通过纯粹的回调函数的形式进行处理,很容易进入到回调地狱中,剥夺了函数return的能力,虽然问题可以解决但是难以读懂、维护困难,稍有不慎就会踏入回调地狱-嵌套层次深,不好维护
Promise是一个对象,对象和函数的区别就是对象可以保持状态,函数不可以(闭包除外),并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据。代码风格容易理解,便于维护,多个异步等待合并便于解决。
详细可看学习中 vue day04文件夹
4.5 axios入门应用
打开浏览器https://www.bootcdn.cn/axios/复制
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
详细可看学习中 vue day04文件夹
4.5.1 axios在生产环境的应用
新建一个项目叫axiosagain,然后打开控制台,cd到相应文件夹,命令行输入npm init -y,初始化package.json,然后在命令行下载axios、webpack、webpack-cli
npm i axios -Snpm i webpack webpack-cli -D
新建webpack.config.js文件,然后在项目下新建一个文件夹src,在src下新建入口文件index.js
命令行下载html-webpack-plugin插件。
npm i html-webpack-plugin -D
在webpack.config.js中引入并使用,在src下面新建一个入口文件index.html
//webpack.config.jsconst HtmlWbpackPlugin = require('html-webpack-plugin');module.exports = { entry: './src/index.js', plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ], mode: 'development',}
//index.js//get的传承方式import axios from 'axios';axios.get({ url: "http://api.eduwork.cn/admin/link?id=1" }).then(res => { console.log(res)});/*axios.get({ url: "http://api.eduwork.cn/admin/link" , config: { params: { id: 1 } }}).then(res => { console.log(res)});*///post传承方式axios.post( url: "http://api.eduwork.cn/admin/link", data: "name=测试&url=48&do_submit=yes" ).then(res => { console.log(res)});
4.5.2 axios的并发请求处理
ajax请求过多对页面性能可能会有影响,以及代码不美观,代码过于臃肿,所有我们可以通过axios的并发请求axios.all()。
axios.all()和promise.all()方法是一个道理,axios.all()这个方法在axios的构造函数时没有的,没在实例对象上。
//index.jsaxios.all(values: [ //一次触发多个异步任务 所有任务都执行完成才能得到结果 axios.get(url: "http://api.eduwork.cn/admin/link>id=1"), axios.get(url: "http://api.eduwork.cn/admin/link>id=7"), axios.get(url: "http://api.eduwork.cn/admin/link>id=9") ]).then(res => { console.log(res); //结果都在一个数组里面 [1, 7, 9]}).catch(err => { console.log("-----------error----------"); console.log(err);})axios.all(values: [ //一次触发多个异步任务 所有任务都执行完成才能得到结果 axios.get(url: "http://api.eduwork.cn/admin/link>id=1"), axios.get(url: "http://api.eduwork.cn/admin/link>id=7"), axios.get(url: "http://api.eduwork.cn/admin/link>id=9") ]).then( axios.spread((res1, res2, res3) => { //分别接收打印 console.log(res1); console.log(res2); console.log(res3); }) )}).catch(err => { console.log("-----------error----------"); console.log(err);})
4.5.2 axios的全局配置
做完全局配置之后再发送axios请求时就简单了
axios.defaults.baseURL = "http://api.eduwork.cn/admin"; //配置请求的基准URL地址axios.defaults.timeout = 5000; //超时时间 请求数据超出时间报错 //配置请求头信息axios.defaults.headers.post['content-type'] = 'application/x-www-from-urlencoded';//整理数据 返回的是json数据形式axios.defaults.transformRequest = function(data) { data = JSON.stringify(data); return data;}axios.get(url: 'link?id=1').then(res => { console.log(res);}).catch(err => { console.log(err);})axios.post(url: 'link/add', data: 'name=hello&url=lmonkey&ord=5&do_submit=yes').then(res => { console.log(res);}).catch(err => { console.log(err);})
4.5.3 axios的实例封装
有时候后台接口地址有很多个并且超时时长不一样,我们不可能在axios中把每个后台请求的域名地址都拼接在URI中,并且在axios中的config写不同的超时时长很繁琐,这个时候可以用到axios实例,在实例中可以配置这两种参数。
假如新建了一个axios实例但是没有参数,取得的就是全局的配置值,实例中如果有则优先取实例中的
axios实例的相关配置(config参数)
baseURL:请求的域名基本地址
timeout:后端定义的超时时长
url:请求的路径
methods:请求的方法(get、post…)
params:请求的参数拼接在url中
data:请求的参数放在request body中
axios.defaults.baseURL = "http://api.eduwork.cn/admin"; axios.defaults.timeout = 5000; //creat创建一个新的实例对象let eduwork = axios.create({ baseURL: "http://api.eduwork.cn/admin", timeout: 5000});let local1 = axios.create({ baseURL: "http://localhost/axiosdemo", timeout: 3000});eduwork.get(url: 'link?id=1').then(res => { console.log(res);}).catch(err => { console.log(err);})local1.get(url: 'getapi.php?id=1000').then(res => { console.log(res);}).catch(err => { console.log(err);})axios.post(url: 'link/add', data: 'name=hello&url=lmonkey&ord=5&do_submit=yes').then(res => { console.log(res);}).catch(err => { console.log(err);})
4.5.4 axios的拦截器的应用
为每个请求都带上的参数,比如token,时间戳等。
对返回的状态进行判断,比如token是否过期。
eduwork.interceptors.request.use(config => { console.log("请求的拦截成功,处理一下,放行!"); return config;}, err => { });eduwork.interceptors.response.use(config => { console.log("从服务器将数据带回来了,处理,放行!"); return config.data;}, err => { });
第五章 2021 新版 Vue3 全家桶
5.1 Vue3的CDN方式安装和基本开发功能体验
<!-- <script src="https://unpkg.com/vue@next"></script> --> //通过静态CDN资源安装Vue最新版本 <script src="./vue.global.js"></script> <div id="app"> {{ message}} </div> <div id="app1"> </div> <script> // const app = new Vue({ // el: "#app", // data: { // message: "this is a test" // } // }) const app = Vue.createApp({ data() { return { message: 'this is a test' } } }).mount("#app"); // const vm = app.mount("#app"); const app1 = Vue.createApp({ data() { return { message: 'this is a test' } } }).mount("#app1"); app.message = 'hello';
5.2 vue-cli脚手架安装与项目创建
Vue-Cli(Command Line Interface),Vue-Cli是Vue官方提供的脚手架。默认已经帮我们搭建好了一套利用Webpack管理Vue的项目结构。
命令安装:npm install -g @vue/cli
检查版本:vue --version
创建项目:vue creat 项目名称
配置文件详情
5.2.1 通过vue-cli体验vue开发并扩展配置
@vue/cli 版本从3以后就给我们提供0配置了,自动给我们搭建环境。一些东西是不用配置的,方便很多。但是又一些功能我们想加或者修改,vue有一个单独的配置文件(vue.config.js)。
webpack更改目录名 (官方文档 webpackjs.com)
//webpack.config.jsconst path = require('path');module.exports = { output: { path: path.resolve(__dirname, 'build'); }}
vue.config.js更改目录名 (官方文档 vuejs.org)
//vue.config.jsconst webpack = require('webpack');module.exports = { outputDir: 'build', //相当于webpack的配置文件内嵌到vue-cli的文件里了 configureWebpack: { plugins: [ new webpack.BannerPlugin({ banner: '学习园地冲冲冲啊' }) ] }}
由于webpack在打包的时候把public里面的内容原封不动的拷贝到build中(解析不了{{message}}),在vue3中使用组件化开发思想:
- 在src下面新建一个组件(App.vue) 默认有三个结构。这个组件就相当于配置,把组件整体的内容暴露出去
<template></template><script> export default { name: 'App' }</script><style scoped></style>
- 在main.js中引用
import {createApp} from 'vue';import App from './App';//把组件(相当于配置)放到createApp()里面使用,通过这个导入进来把它挂载到#app里面,#app又在首页index.html里面,就相当于把组件放到了 <div id="app">这个中间</div>,所有这个中间就不用再写任何东西了createApp( App).mount('#app');
把组件(相当于配置)放到createApp()里面使用,通过这个导入进来把它挂载到#app里面,#app又在首页index.html里面,就相当于把组件放到了
<div id="app"> </div>
这个中间,所有这个中间就不用再写任何东西了
App.vue组件
<template> <div> {{msg}} {{msg}} {{msg}} {{msg}} {{msg}} {{msg}} </div></template><script> export default { name: 'App', data() { return { msg: 'this is a test hello vue.js' } } }</script><style scoped></style>
5.2.2 options基础定义和MVVM模式
main.js
import { createApp } from 'vue';import App from './App.vue';//相当于通过组件创建了一个App的vue的对象,让创建完的对象挂到#app中(mian.js会打包到index.html中)//createApp(options).mount()createApp(App).mount('#app');//https://v3.vuejs.org/api/options-api.html 里面的配置方法都是规定好的
options是vue实例参数,意思是构造选项
options的五类属性
- 数据:data、props、propsData、computed、methods、watch
- DOM:el、template、render、rederError
- 生命周期钩子:beforeCreate、cteated、beforeMount、mounted、beforeDestroy、destroyed、errorCaptured
- 资源:directives、filters、components
- 组合:parent、mixins、extends、provide、inject
- 其他:先不看
index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <div id="app"></div></body></html>
app.vue
<template> <div> {{msg}} {{num}} <br> <!-- <button @click="num--">-</button> --> <button @click="sub">-</button> <input type="text" size="3" v-model="num"> <!-- <button @click="num++">+</button> --> <button @click="add">+</button> </div></template><script> export default { name: 'App', data() { return { msg: 'hello vue', num: 0, max: 10, min: 1 } }, methods: { add() { if(this.num >= this.max ) { this.num = this.max; } else { this.num++; } }, sub() { if(this.num <= this.min) { this.num = this.min } else { this.num--; } } } }</script><style scoped></style>
5.3 模板基础语法
5.3.1 插值和指令
插值:{{}}
指令:v-
<p v-once> {{msg}} </p> <!--数据只第一次时显示,不响应式--><p v-pre> {{msg}} </p> <!--内容原封不动的展示--><p v-text='msg'></p> <!--就相当于插值表达式功能--><p v-html='title'></p> <!--可以解析标签-->data: { msg: 'test message', title: `<h1 style="color: red"> Title</h1>`}
5.3.2 v-bind绑定属性
插值{{}}只能用在模板内容中,用于动态内容绑定。如果希望元素的属性也可以动态绑定,需要通过v-bind指令(缩写"😊
绑定有意义元素中的属性,绑定class属性,四种用法(字符串,数组,对象,方法),绑定style属性
<template> <div> <h2 v-bind:title='msg'> {{msg}} </h2> <!--在没有绑定之前,里面的'msg'是不能解析的 --> <img :src="imgsrc" :width="size" :height="size"> <a :href:'http://www.lmonkey.com'></a> <br/> <div style="width:100px;height:100px;background:blue;color:pink"> </div> <div :style="[fontSize, bgcolor]"> </div> <div class='one two' :class='[one, two]'> </div> <!--class='one'和:class="'one'"是没有区别的--> <!-- class='one two' 和 :class="['one', 'two']"是一样的 --> <div :class='getStyleObj()'> </div> <!--对象里面是布尔值的形式,真就加上,假就不加--> <!-- :class='{isone: isone, istwo: istwo}' => :class='{isone, istwo}' --> </div></template><script> export default { name: 'App', data() { return { msg: 'this is test', imgsrc: 'https://v3.vuejs.org/logo.png', size: 200px, fontSize: 'font-size:10px', bgcolor: 'background-color: red', one: 'one', two: 'two', isone: true, istwo: false } }, methods: { //如果里面的属性太长,可以把操作放到方法里面 getStyleArr() { return [this.one, this.two] }, getStyleObj() { return {one: this.isone, two: this.istwo} } } }</script><style scoped> .one { background: red; } .two { font-size: 30px; }</style>
5.3.4 计算属性computed
计算属性和方法的区别:计算属性调用不是调用方法,不需要加(),而方法需要。计算属性有缓存的作用,所以只会打印一次’111’,会打印三次’222’。只要计算属性里面的值变化了才会再次调用,计算属性用于处理一些复杂逻辑。
<template> <div> <h2>{{name}} _ {{slogen}}</h2> <h2>{{name + '_' + slogen}}</h2> <h2>{{getTitle()}}</h2> <h2>{{getTitle()}}</h2> <h2>{{getTitle()}}</h2> <h2>{{title}}</h2> <h2>{{title}}</h2> <h2>{{title}}</h2> 总价:<Small>$</Small>{{totalPrice}} </div></template><script> export default { name: 'App', data() { return { name: '金香香', slogen: '是大傻子', books: [ {name: '简爱',price: 45}, {name: '日落', price:90}, {name: '承诺', price: 89} ] } }, methods: { getTitle() { console.log('111'); return this.name + '_' + this.slogen; } }, computed: { title: { get() { console.log('222') return this.name + '_' + this.slogen; } }, totalPrice: { get() { return this.books.reduce((s, n) => s + n.price, 0) } } } }</script><style scoped></style>
5.3.5 事件监听v-on
在前端开发中,需要经常和用户交互。
绑定事件监听器指令:v-on
缩写: @
参数:$event
v-on事件修饰符号:.stop阻止事件冒泡 .self当事件在该元素本身触发时才触发事件 .capture添加事件侦听器时,使用事件捕获模式(相当于改变了冒泡的顺序) .prevent阻止默认事件 .once事件只触发一次
<template> <div> <button v-on:click='num--'>-</button> <input type="text" size='2' v-model='num'> <button @click='num++'>+</button> <button v-on:click='sub()'>-</button> <input type="text" size='2' v-model='num'> **<button @click='add'>+</button> //MouseEvent** <button v-on:click='sub("hello")'>-</button> <input type="text" size='2' v-model='num'> **<button @click='add()'>+</button> //undefined ** <button @click='add1(100)'>+</button> //100 undefined <button @click='add1(100, $event)'>+</button> //强制手动传事件对象 100 MouseEvent //事件修饰符 <div @click="one()" id='box1'> <div @click="two()" id='box2'> //222 111 <button @click.stop="three()">按钮</button> //333 </div> </div> </div></template><script> export default { name: 'App', data() { return { num: 0 } }, methods: { sub(p) { this.num--; console.log(p); //hello }, add(e) { this.num++; console.log(e); //如果不传参数加,定义的参数就是 MouseEvent 事件对象 }, add1(n, e) { console.log(n, e); //如果传了参数 则会将事件对象覆盖 e是undefined }, one() { console.log(111) }, two() { console.log(222) }, three() { console.log(333); } } }</script><style scoped> #box1 { width: 200px; height: 200px; background-color: red; } #box2 { width: 100px; height: 100px; background-color: pink; }</style>
5.3.6 条件分支v-if和v-show
v-if是"真正"的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当的被销毁和重建。
v-show就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS进行切换
v-if v-else
v-if v-else-if v-else
<template> <div> <button @click='isshow = !isshow'>显示|隐藏</button> <div v-if="isshow"> //v-if所在的整个div都删掉了 {{msg}} </div> <div v-show="isshow"> //只是display: none的方式隐藏了v-show所在的div {{msg}} </div> <div v-if="isshow"> <button> {{msg}}</button> </div> <div v-else> 2222222 </div> <div v-if="card==1"> <button> {{msg}}</button> </div> <div v-else-if="card==2"> 2222222 </div> <div v-else> 3333 </div> </div></template><script> export default { name: 'App', data() { return { msg:'hello', isshow: false, card: 1 } }, methods: { } }</script><style scoped> </style>
5.3.7 循环遍历v-for
遍历指令: v-for
遍历数组 v–for="(item [index]) in 数组"
遍历对象 v-for="(value, [key], [index]) in 对象"
<template> <div> <!-- <ul> <li v-for="(item, index) in list" :key='item'>{{index + 1}} - {{item}}</li> </ul> --> <!-- <ul> <li v-for="(value, key, index) in obj" :key='index'>{{index}}-{{key}}{{value}}</li> </ul> --> <ul> <li :class="{active: item.active}" v-for="(item, index) in books" :key="item.id" @mouseenter='one(index)'>{{index+1}}--{{item.name}}--{{item.price}}</li> </ul> </div></template><script> export default { name: 'App', data() { return { list: ['Java', 'Python', 'PHP', 'Vue', 'React'], obj: { name: '金香香', url: 'https://www.baidu.com', slogen: '是大傻子' }, books: [ {id: 1, name:'Jxx', price: 88, active: false}, {id: 2, name: 'Lm', price:66, active: false}, {id: 3, name:'Tly', price:88, active: false}, {id: 4, name: 'Dx', price: 33, active: false} ] } }, methods: { one(index) { for(let i=0; i < this.books.length; i++) { if(index == i) { this.books[index].active = true; } else { this.books[i].active = false; } } } } }</script><style scoped> ul { margin: 0px; padding: 0px; list-style: none; } .active { background-color: pink; color: green; }</style>
vue中列表循环需要加:key=“唯一标识” 唯一标识可以是item里面的id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件key的作用主要是为了高效的更新虚拟DOM,使用diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层的对比。
5.3.8 v-model详解
v-model指令的本质是:它负责监听用户的输入事件,从而更新数据,并对一些极端场景进行一些特殊处理。同时,v-model会忽略所有表单元素的value、checked、selected特性的初始值,它总是将vue实例中的数据作为数据来源。然后当输入事件发生时,实时更新vue实例中的数据。
实现原理:<input v-bind:value='message' v-on:input="message = $event.target.value"/>
v-model的修饰符号:.lazy懒加载修饰符(数据不实时变化,而是等回车后变化)
.number修饰符让其转换为number类型
.trim修饰符可以自动过滤掉输入框的首尾空格
表单中v-model的用法:
- 跟表单双向绑定
- 可以代表同组,不用单独起name了
- 默认值想要的时候可以直接带上
<template> <div> <!-- 单选框 --> <label for="one"> <!-- name: 'sex' 绑定了相同的变量具有排斥的关系 同组的概念 加了v-model后具有name的效果--> <input type="radio" id="one" value="男" v-model="sex"> 男 </label> <label for="two"> <!-- name: 'sex' --> <input type="radio" id="two" value="女" v-model="sex"> 女 </label> <br> <!-- sex的值是根据value:''里面的值来的 --> {{sex}} <br> <label for="a"> <!-- 通过布尔值来判断是否同意 --> <input type="checkbox" v-model="isAgree">同意协议{{isAgree}} </label> <br> <!-- 复选框 --> <label for="b"> <input type="checkbox" value="Java" id="b" v-model="tags">Java </label> <label for="c"> <input type="checkbox" value="PHP" id="c" v-model="tags">PHP </label> <label for="d"> <input type="checkbox" value="Vue" id="d" v-model="tags">Vue </label> <label for="e"> <input type="checkbox" value="Python" id="e" v-model="tags">Python </label> <br> {{tags}} <br> <!-- 下拉列表 --> <select v-model="language"> <option value="Java">Java</option> <option value="PHP">PHP</option> <option value="Vue">Vue</option> <option value="CSS">CSS</option> </select> <br> {{language}} <br> <!-- 加上multiple可以多选 按ctrl/shift再点击 --> <select v-model="languages" multiple> <option value="Java">Java</option> <option value="PHP">PHP</option> <option value="Vue">Vue</option> <option value="CSS">CSS</option> </select> <br> {{languages}} </div></template><script> export default { name: 'App', data() { return { msg: 0, sex: '女', isAgree: true, tags: [], language: 'Vue', languages: [] } }, methods: { inchange(event) { this.msg=event.target.value; } } }</script><style scoped> </style>
5.4 Vue的组件化开发
组件化是Vue的精髓,Vue开发就是由一个一个的组件构成的
组件的分类:页面级组件 业务上可复用的基础组件 与业务无关的独立功能组件
组件开发三要素(prop、自定义事件、slot):prop用于定义组件的属性 自定义事件用于触发组件的事件 slot用于组件功能的扩展
组件设计需要考虑的问题:可扩展性强 组件中方法函数的抽离、便于复用、适用程度高 文档清楚详细 颗粒度合适,适度抽象 功能尽可能单一,代码行数适中
5.4.1 创建Vue组件和组件语法结构
App.vue
<template><!-- 3.使用组件 --> <HelloWord></HelloWord> </template><script>import HelloWord from './components/HelloWord'; //1.引入组件export default { name: 'App', components: { HelloWord //2.注册组件 Helloword: Helloword }}</script><style scoped></style>
HelloWord.vue
<template> <div> <h1 class="box1">{{message}}</h1> </div></template><script>export default { name: "HelloWord", data() { return { message: "hello world!!!!!" } }}</script><style scoped> .box1 { width: 100px; height: 100px; background-color: pink; }</style>
OneComp.vue
<template> <div> <h1 class="box1">{{message}}</h1> <!--这两个是一样的 --> <OneComp></OneComp> <one-comp></one-comp> </div></template><script>import OneComp from './OneComp';export default { name: "HelloWord", data() { return { message: "hello world!!!!!" } }, components: { OneComp }}</script><style scoped lang="scss"> /* 加上lang="scss"就可以解析scss语法 */ $w: 200px; $color: red; .box1 { width: $w; height: 100px; background-color: pink; color: $color; }</style>
App.vue中里面的内容是可以全局使用的,它的儿子、孙子…都可以使用,而scoped的作用就是限制局部使用,只能在自己里面有效,子组件用不了
5.4.2 父传子props(组件之间通信)
父子组件之间不能相互访问data内容,父组件向子组件传值通过props,子组件向父组件传值通过$emit Events
在父组件中的子组件标签里面传送值msg=‘hello’,然后在子组件中通过props: [‘msg’],来接收就可以使用,使用方法相当于子组件中data里面的值,父组件中的msg不加冒号就是传后面的值,加了冒号后面的值就是变量。从父组件传过来的数据还可以向下一个子组件中传递,方法一样。
父组件
<template> <HelloWord msg='hello' :title='msg' :article='article' :MyTitle='msg'></HelloWord> <!-- 这里面传值的:my-title是不可以的 只能用驼峰式命名 --> <hello-word msg='hello' :title='msg' :article='article' :my-title='msg'></hello-word></template><script>import HelloWord from './components/HelloWord';export default { name: 'App', components: { HelloWord }, data() { return { msg: "this is app data msg", article: [ '11111', '2222', '3333' ], MyTitle: 'jxx' } }}</script>
子组件
<template> {{msg}}</template><Script>export default { name: "HelloWord", data() { return { } }, // props: ['msg', 'title', 'article', 'MyTitle'], props: { msg:{ type: String, default: '##########' //如果父组件不传这个值过来,就默认使用defalut定义的值 }, title:{ type: String, required: true //父组件必须传这个值过来,否则会报错 }, article: { type: Array } },methods: { add() { return this.msg + this.msg; } }}</Script>
5.4.3 子传父$emit(组件之间通信)
先在子组件中触发事件 @click=‘changenum(2)’,然后在子组件的方法changenum中自定义事件this.$emit(‘mycountevent’, num),mycountevent是自定义事件的名称,num是子组件传给父组件的值,然后父组件中的子组件标签触发这个自定义事件@mycountevent=‘mydemo’,在父组件的mydemo方法中处理子组件传过来的值
父组件
<template> <div>{{count}} </div> <my-conn @mycountevent='mydemo'></my-conn></template><Script> import MyConn from './components/MyConn';export default { name: "MyMain", data() { return { msg1: 'world', count: 0 } }, conponents: { MyConn },methods: { mydemo(data) { this.count += data; } }}</Script>
子组件
<template> <div> <button @click='changenum(2)'>+</button> </div></template><Script>export default { name: "MyConn", data() { return { } },methods: { changenum(num) { this.$emit('mycountevent', num) } }}</Script>
5.4.4 父子组件之间的相互访问方式
子组件调用父组件的方法: p a r e n t 或 parent或 parent或root
父组件调用子组件的方法: c h i l d r e n 或 children或 children或refs($children已经淘汰了)
父组件
<template> <div>{{count}} </div> <my-conn ref='one'></my-conn> <!-- 通过ref起名字,因为父组件只可能有很多个子组件需要区分使用 --> <my-conn ref='three'></my-conn> <button @click='two'> 让子组件数加1 </button></template><Script> import MyConn from './components/MyConn';export default { name: "MyMain", data() { return { count: 0, } }, conponents: { MyConn },methods: { change() { this.count++; }, two() { // this.$children[0] vue3中已经去掉了 this.refs.one.changeone(); this.refs.three.changeone(); console.log(this.refs.one.num); console.log(this.refs.three.num); } }}</Script>
子组件
<template> <div> <button @click='one'>+</button> {{num}} </div></template><Script>export default { name: "MyConn", data() { return { num: 0 } },methods: { one() { console.log(this.$parent.count); //0 this.$parent.change(); //使用$parent可以访问父组件中的属性和方法,this.$parent.$parent父亲的父亲... console.log(this.$root.count) //this.$root可以直接访问到根组件=> App.vue }, changeone() { this.num++; } }}</Script>
5.4.5 Vue插槽slot的应用
Vue实现了一套内容分发的API,将元素作为承载分发内容的出口。插槽可以实现组件的扩展性,抽取共性,保留不同。
父组件
<template><!-- 没使用插槽的时候中间放任何内容都会被抹去 --><!-- 插槽起名字后(具名插槽)可以单独更改内容 和别的模块就有区别了 v-slot: => # --> <HelloWord> <template v-slot:two='hello'> <a href="">{{hello.user.name}}</a> </template> </HelloWord> <HelloWord> <template v-slot:one> <a href="">提交</a> </template> <template v-slot:default='subdata'> <!--代表插槽里面所有的属性 如果要在父组件中使用子组件中插槽的数据--> <a href="">{{subdata.user.name}}</a> </template> </HelloWord> <HelloWord> </HelloWord> </template><script>import HelloWord from './components/HelloWord';export default { name: 'App', components: { HelloWord }}</script><style scoped> </style>
子组件
<template> <div class='mybar'> <h5>金香香是大傻子</h5> <!-- <slot></slot>标签放在这里是什么都不显示的 里面有内容的话是default的意思 如果<HelloWord></HelloWord>父组件里面没有任何内容会默认继承这里面的值 如果<HelloWord></HelloWord>里面有内容就使用里面的内容--> <slot name='one'><button>提交</button></slot> <slot name='two' :user='user'>111</slot> <slot :user='user'></slot> </div></template><script>export default { name: "HelloWord", data() { return { user: {name: 'eduwork'} } },}</script>
5.4.6 Vue3中组件的生命周期函数
在一个对象或者一个实例,从开始创建到最终销毁整个生命中会根据不同的情况自动调用一些方法。生命周期一共十个方法,在任何组件中都可以写,跟顺序没关系
beforeCreated() { console.log('实例刚刚被创建');},created() { console.log('实例已经创建完成');},beforeMount() { console.log('模板编译之前');},mounted() { console.log('模板编译完成'); //这个经常使用 重点},beforeUpdate() { console.log('数据更新之前');},updated() { console.log('数据更新完成');},activated() { console.log('keep-alive缓存的组件停用时调用'); },deactivated() { console.log('keep-alive缓存的组件激活时调用');},beforeUnmount() { console.log('实例销毁之间');},Unmount() { console.log('实例销毁完成');}
当里面的num数值++后再销毁掉,再加回来后里面的num就是初始值。里面的数据都清空了,没有缓存。将要销毁的实例放到keep-alive中,再销毁加回来数据是num++后的,相当于从缓存里面拿过来的。MyConn组件会自动调用activated方法和deactivated方法,有缓存就相当于组件没有销毁,就不会自动调用beforeUnmount()和Unmount() 。
<!-- <my-conn v-if='isShow'>{{num}}</my-conn> --><keep-alive> <my-conn v-if='isShow'>{{num}}</my-conn></keep-alive><button @click='isShow = !isShow'> 销毁conn</button>
v-if是实例销毁,v-show是数据更新
this.$nextTick(() => { })//this.$nextTick()将回调延迟到下次DOM更新循环之后执行,在修改数据之后立即使用它,然后等待DOM更新
<template> <input ref='username' type='text'> <!-- ref可以当我们在程序里面操作的DOM元素 --></template><script> export default { name: 'MyConn', data() { return { mess: 'this is main test' } }, activated() { console.log('keep-alive缓存的组件停用时调用'); //延迟更新处理 this.$nextTick(() => { this.$refs.username.focus(); }) } }</script>
5.4.7 Vue网络请求axios
项目的命令行中下载 npm i axios -S (生产环境)
用get方法从后台取值
<template> <div class="footer"> <ul> <li v-for="item in links"><a href="item.url">{{item.name}}</a></li> </ul> </div></template><script>import axios from 'axios';export default { name: 'MyFooter', data() { return { links: [] } }, mounted() { axios.get(url:'http://api.eduword.cn/admin/link').then(res => { console.log(res.data); this.links = res.data; }).catch(err => { console.log(err); }) }}</script><style scoped> </style>
用post方法往后台传输添加值
<template> <div> <form action="#"> 网站名称:<input type="text" v-model="link.name"> {{link.name}} 网站位置:<input type='text' v-model="link.url"> {{link.url}} 位置排序:<input type="text" v-model="link.ord"> {{link.ord}} <input type="hidden" v-model="link.do_submit"> {{link.do_submit}} <!-- 阻止表单的提交 --> <button @click.prevent="dosubmit">添加数据</button> </form> </div></template><script>import axios from 'axios';export default { name: 'MyConn', data() { return { link: { name: '', url: '', ord: 0, do_submit: true } } }, methods: { dosubmit() { console.log(this.link); //{name: '', url: '', ord: '', do_submit: true} axios.post(url: 'http://api.eduword.cn/admin/link/add', data: this.link, { transformRequest: [ function(data) { //会将this.link传到data中 let str = ''; // for...in语句用于对数组或对象的属性进行循环操作,每执行一次,就会对数组的元素或对象的属性进行一次操作 // encodeURIComponent() 键和值有一些编码中文的问题,传输的时候最好去掉 for(let key in data) { str += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + '&' } return str; } ], header: { "Content-Type": 'application/x-www-form-urlencoded' //json格式 } }).then(res => { console.log(res) }).catch(err => { console.log(err); }); } }}</script><style scoped> </style>
5.4.8Vue 封装网络请求
在src目录下新建一个network目录,在里面新建一个request.js文件
request.js
import axios from 'axios';const instance = axios.create({ baseURL: 'http://api.eduwork.cn/admin', timeout: 5000})// 封装get方法export function get(url, params) { return instance.get(url, { params })}// get().then().catch();// 封装post方法export function post(url, params) { return instance.post(url, params, { transformRequest: [ function(data) { let str = ''; for (let key in data) { str += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + '&' } return str; } ], header: { "Content-Type": 'application/x-www-form-urlencoded' } })}// 封装删除的方法export function del(url) { return instance.delete(url);}// 拦截器instance.interceptors.resquest.use( config => { // config.headers.token = '123456' 配置信息 return config; //放行 }, error => { return Promise.reject(error); } )instance.interceptors.response.use( response => { return response; }, error => { return Promise.reject(error); })
用get方法从后台取值
<template> <div class="footer"> <ul> <li v-for="item in links"><a href="item.url">{{item.name}}</a></li> </ul> </div></template><script>// import axios from 'axios';import {get} from '../network/request'export default { name: 'MyFooter', data() { return { links: [] } }, mounted() { /* axios.get(url:'http://api.eduword.cn/admin/link').then(res => { console.log(res.data); this.links = res.data; }).catch(err => { console.log(err); }) */ get('/link', {id: 1}).then(res => { this.links = res.data; }).catch(err => { console.log(err); }); }}</script><style scoped> </style>
用post方法向后台传递值
<template> <div> <form action="#"> 网站名称:<input type="text" v-model="link.name"> {{link.name}} 网站位置:<input type='text' v-model="link.url"> {{link.url}} 位置排序:<input type="text" v-model="link.ord"> {{link.ord}} <input type="hidden" v-model="link.do_submit"> {{link.do_submit}} <!-- 阻止表单的提交 --> <button @click.prevent="dosubmit">添加数据</button> </form> </div></template><script>// import axios from 'axios';import {post} from '../network/request';export default { name: 'MyConn', data() { return { link: { name: '', url: '', ord: 0, do_submit: true } } }, methods: { dosubmit() { /* console.log(this.link); //{name: '', url: '', ord: '', do_submit: true} axios.post('http://api.eduword.cn/admin/link/add', this.link, { transformRequest: [ function(data) { let str = ''; for(let key in data) { str += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + '&' } return str; } ], header: { "Content-Type": 'application/x-www-form-urlencoded' } }).then(res => { console.log(res) }).catch(err => { console.log(err); }); */ post('/link/add', this.link).then(res => { console.log(res) }).catch(err => { console.log(err); }); } }}</script><style scoped> </style>
5.5 认识Vue的路由
src/router 里面有一个index.js文件配置相关路由
// 1.导入路由里面的方法import { createRouter, createWebHistory } from 'vue-router';// 3.导入需要引用的组件import Home from '../views/Home.vue';// import About from '../views/About.vue';import User from '../views/User.vue';const routes = [{ path: '/', name: 'Home', component: Home //加载的组件 }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User } ] // 2.创建路由const router = createRouter({ history: createWebHistory(process.env.BASE_URL), //设置模式 请求过的可以后退回去 == window.history.go()/.back() // 路由的规则 /* routes: [ { path: '/home' }, { path: '/about' } ] */ routes})// 4.暴露routerexport default router
src/views里面放的是大组件 src/components里面放的是小组件
views/About.vue
<template> <div>这是关于我们的页面...</div></template><script>export default { name: "About",}</script><style scoped></style>
views/User.vue
<template> <div>这是个人中心的页面...</div></template><script>export default { name: "User",}</script><style scoped></style>
views/Home.vue
<template> <div>这是首页面...</div></template><script>export default { name: "Home",}</script><style scoped></style>
src/app.vue
<template> <div id="nav"> <!-- 在路由里面自带标签 router-link 指定链接地址 自动渲染成a标签 点击后会把路径里面对应的页面请求过来放到router-view里面 --> <router-link to="/">首页</router-link> | <router-link to="/about">关于我们</router-link> | <router-link to="/user">个人中心</router-link> </div> <!-- 自带标签 用于显示 --> <router-view/></template><style>#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50;}#nav { padding: 30px;}#nav a { font-weight: bold; color: #2c3e50;}#nav a.router-link-exact-active { color: pink;}</style>
src/main.js
import { createApp } from 'vue'import App from './App.vue'//相当于 import router from './router/index.js' 会默认找到router目录下的index.jsimport router from './router'// 相当于把router加到了vue实例里面了 app.$router全局属性createApp(App).use(router).mount('#app')
详情可看vue3/demo4
5.5.1 Router模式切换和懒加载
路由是由多个URL组成的,使用不同的URL可以相应的导航到不同的位置,Vue-Router在切换页面时是没有重新进行请求的,使用起来就好像页面是有状态的一样。借助了浏览器的History API来实现的,这样可以使得页面跳转而不刷新,页面的状态就被维持在浏览器中,vue-router中默认使用的使hash模式,也就是会出现如URL: localhost:8080/#/ URL中带有#号
有三种模式
Hash: 使用URL的hash值来作为路由,用来指导浏览器动作的,对服务器端完全无用,支持所有浏览器
History:以来HTML5 History API和服务器配置
Abstract: 支持所有javascript运行模式。如果发现没有浏览器的API,路由会自动强制进入这个模式
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';// 这样引入组件会导致打包到一个js文件中(如果有100个路由,跳转到100个页面,会把用到的100个js都封装到一起,速度慢)// import Home from '../views/Home.vue';// import About from '../views/About.vue';// import User from '../views/User.vue'; // 懒加载 访问哪个路由就加载哪个路由 分开打包形成独立的js文件const Home = () => import('../views/Home.vue');const User = () => import('../views/User.vue');const routes = [{ path: '/', name: 'Home', component: Home //加载的组件 }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User } ]const router = createRouter({ // history: createWebHashHistory(process.env.BASE_URL), history: createWebHistory(process.env.BASE_URL), //设置模式 请求过的可以后退回去 == window.history.go()/.back() routes})export default router
5.5.2 自定义router-link和使用命名视图
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const routes = [{ path: '/', name: 'Home', // component: Home //加载的组件 components: { default: Home, About, User } }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User } ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})export default router
src/App.vue
<template> <div id="nav"> <!-- 在路由里面自带标签 router-link --> <!-- active-class是vue-router模块的router-link组件中的属性,用来做选中样式的切换 --> <router-link to="/" active-class="active" class="bg">首页</router-link> | <router-link to="/about" class="bg">关于我们</router-link> | <router-link to="/user" class="bg">个人中心</router-link> | <!-- 使用具名插槽的方式把默认的a标签改成自己需要的标签 --> <router-link to="/about" custom v-slot="{navigate}"> <button @click="navigate" @keypress.enter="navigate" role="link">About US</button> </router-link> | <!-- 不建议做 $router $全局属性 --> <!--$router代表整个路由对象 $route代表正在访问的路由 --> <button @click="$router.push('/user')" :class="{active: $route.path == '/user'}">个人中心</button> <button @click="$router.go(-1)">返回</button> | {{$route.path}} </div> <!-- 自带标签 用于显示 通常页面中放一个router-view也不用起名字 加多个画中画效果 了解一下就行--> <!-- <ruoter-view></ruoter-view> --> <router-view class="one" name="User"></router-view> <router-view class="two"></router-view> <router-view class="three" name="About"></router-view></template><style>#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50;}#nav { padding: 30px;}#nav a { font-weight: bold; color: #2c3e50;}#nav a.router-link-exact-active { color: pink;}.active { color: green !important;}.bg { background: #ccc;}.one{ float: left; width: 25%; height: 300px; background-color: pink;}.two{ float: left; width: 50%; height: 300px; background-color: green;}.three{ float: left; width: 25%; height: 300px; background-color: grey;}</style>
5.5.3 嵌套路由(子路由)
在views文件夹中新建MySetting.vue和MyOrder.vue文件
views/MyOrder.vue
<template> <div> <ul> <li>11111111</li> <li>11111111</li> <li>11111111</li> <li>11111111</li> </ul> </div></template><script>export default { name: "MyOrder",}</script><style scoped></style>
views/MySetting.vue
<template> <div> <ul> <li>22222</li> <li>22222</li> <li>22222</li> <li>22222</li> </ul> </div></template><script>export default { name: "MySetting",}</script><style scoped></style>
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const MyOrder = () => import ('../views/MyOrder.vue');const MySetting = () => import ('../views/MySetting.vue');const routes = [{ path: '/', name: 'Home', component: Home //加载的组件 }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User, children: [{ path: '', // /user 当处于这个路由时指定默认的组件 component: MySetting }, { path: 'order', // /user/order 如果用/order就是到根目录下的 component: MyOrder, }, { path: 'setting', component: MySetting } ] } ]const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})export default router
views/User.vue
<template> <div>这是个人中心的页面...</div> <br> <div class="menu"> <ul> <li><router-link to="/user/order">我的订单</router-link></li> <li> <router-link to="/user/setting">个人设置</router-link></li> </ul> </div> <div class="content"> <router-view></router-view> </div></template><script>export default { name: "User",}</script><style scoped> .menu { width: 30%; height: 300px; background-color: pink; float: left; } .content { width: 70%; height: 300px; background-color: green; float: right; } a { font-weight: bold; color: #2c3e50;} a.router-link-exact-active { color: purple;}</style>
5.5.4 动态路由和参数传递
传递参数主要有两种类型:params(传递一个值)和query(传递多个值)
params的类型:
配置路由的格式:/user/page/:id(动态路由)
传递的方式:在path后面对应的值:to=" ‘/user/page/’ + item.id "
传递后形成的路径:/user/page/9, /user/page/zs
接收参数:$route.params.id
在views文件夹中新建MyPage.vue文件
<template> <div> <h2>这是文章的模板</h2> 文章ID: {{$route.params.id}} <br> <h1>{{pageid}}</h1> </div></template><script>export default { name: "MyOrder", computed: { pageid() { return this.$route.params.id; } /* pageid: { get() { } } */ }}</script><style scoped></style>
views/User.vue
<template> <div>这是个人中心的页面...</div> <br> <div class="menu"> <ul> <li><router-link to="/user/order">我的订单</router-link></li> <li> <router-link to="/user/setting">个人设置</router-link></li> <li><router-link to="/user/page/1">单页1</router-link></li> </ul> <ul> <li v-for="item in articles"><router-link :to="'/user/page/' + item.id">{{item.title}}</router-link></li> </ul> </div> <div class="content"> <router-view></router-view> </div></template><script>export default { name: "User", data() { return { articles: [ {id: 11, title: '这是文章一'}, {id: 12, title: '这是文章一'}, {id: 13, title: '这是文章一'}, {id: 14, title: '这是文章一'}, {id: 15, title: '这是文章一'}, ] } }}</script><style scoped> .menu { width: 30%; height: 300px; background-color: pink; float: left; } .content { width: 70%; height: 300px; background-color: green; float: right; } a { font-weight: bold; color: #2c3e50;} a.router-link-exact-active { color: purple;}</style>
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const MyOrder = () => import ('../views/MyOrder.vue');const MySetting = () => import ('../views/MySetting.vue');const MyPage = () => import ('../views/MyPage.vue');const routes = [{ path: '/', name: 'Home', component: Home //加载的组件 }, { path: '/about', name: 'About', component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User, children: [{ path: '', // /user 当处于这个路由时指定默认的组件 component: MySetting }, { path: 'order', // /user/order 如果用/order就是到根目录下的 component: MyOrder, }, { path: 'setting', component: MySetting }, { path: 'page/:id', component: MyPage } ] } ]const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})export default router
query的类型:
配置路由格式:/user/article, 正常配置
传递的方式:对象中使用query的key作为传递方式:to="{path: ‘/user/article’, query: {name: ‘hello’, age: 100}}" / to="/user/article?name=111&age=10"
传递后形成的路径:/user/article?name=hello&age=100
接收参数:$route.query.name
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const MyOrder = () => import ('../views/MyOrder.vue');const MySetting = () => import ('../views/MySetting.vue');const MyPage = () => import ('../views/MyPage.vue');const MyArticle = () => import ('../views/MyArticle.vue');const routes = [{ path: '/', name: 'Home', component: Home //加载的组件 }, { path: '/about', name: 'About', component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User, children: [{ path: '', // /user 当处于这个路由时指定默认的组件 component: MySetting }, { path: 'order', // /user/order 如果用/order就是到根目录下的 component: MyOrder, }, { path: 'setting', component: MySetting }, { path: 'page/:id', component: MyPage }, { path: 'article', component: MyArticle } ] } ]const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})export default router
views/User.vue
<template> <div>这是个人中心的页面...</div> <br> <div class="menu"> <ul> <li><router-link to="/user/order">我的订单</router-link></li> <li> <router-link to="/user/setting">个人设置</router-link></li> <li><router-link to="/user/page/1">单页1</router-link></li> </ul> <ul> <li v-for="item in articles"><router-link :to="'/user/page/' + item.id">{{item.title}}</router-link></li> </ul> <li> <router-link to="/user/article?name=111&age=10">文章一</router-link></li> <li><router-link :to="{path: '/user/article', query: {name: 'hello', age: 100}}">文章二</router-link></li> <button @click="$router.push({path:'/user/article', query: {name:'world', age:30}})">文章三</button> </div> <div class="content"> <router-view></router-view> </div></template><script>export default { name: "User", data() { return { articles: [ {id: 11, title: '这是文章一'}, {id: 12, title: '这是文章一'}, {id: 13, title: '这是文章一'}, {id: 14, title: '这是文章一'}, {id: 15, title: '这是文章一'}, ] } }}</script><style scoped> .menu { width: 30%; height: 300px; background-color: pink; float: left; } .content { width: 70%; height: 300px; background-color: green; float: right; } a { font-weight: bold; color: #2c3e50;} a.router-link-exact-active { color: purple;}</style>
在views文件夹中新建MyArticle.vue文件
<template> <div> 这是文章的页面<br> {{$route.query.name}} <br> {{$route.query.age}} </div></template><script>export default { name: "MyArticle",}</script><style scoped></style>
5.5.5 重定向和别名
重定向:重定向也在routes配置中完成,要从重定向/a到/b
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const MyOrder = () => import ('../views/MyOrder.vue');const MySetting = () => import ('../views/MySetting.vue');const MyPage = () => import ('../views/MyPage.vue');const MyArticle = () => import ('../views/MyArticle.vue');const routes = [{ path: '/', name: 'Home', // redirect: { name: 'About' }, redirect: '/about', //打开的路由是/about component: Home, //加载的组件 }, { path: '/about', name: 'About', component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User, children: [{ path: '', // /user 当处于这个路由时指定默认的组件 component: MySetting }, { path: 'order', // /user/order 如果用/order就是到根目录下的 component: MyOrder, }, { path: 'setting', component: MySetting }, { path: 'page/:id', // 相当于把当前的路由传给to $route = to redirect: to => { return { path: 'article', query: { name: 'zhangsan', age: to.params.id } } }, component: MyPage }, { path: 'article', component: MyArticle } ] } ]const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})export default router
别名:表示用户访问时,URL保持不变,但将被匹配,就像用户正在访问时一样
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const MyOrder = () => import ('../views/MyOrder.vue');const MySetting = () => import ('../views/MySetting.vue');const MyPage = () => import ('../views/MyPage.vue');const MyArticle = () => import ('../views/MyArticle.vue');const routes = [{ path: '/', name: 'Home', // redirect: '/about', //打开的路由是/about redirect: { name: 'About' }, component: Home, //加载的组件 }, { path: '/about', name: 'About', alias: ['/a', '/b', '/c'], //访问这些路径都可以到/about 相当于有很多小名但指的是同一个 component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User, children: [{ path: '', // /user 当处于这个路由时指定默认的组件 component: MySetting }, { path: 'order', // /user/order 如果用/order就是到根目录下的 component: MyOrder, }, { path: 'setting', component: MySetting }, { path: 'page/:id', alias: ['p/:id', 'x/:id'], //带参数的路径别名也要带参数 component: MyPage }, { path: 'article', component: MyArticle } ] } ] // 2.创建路由const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})// 4.暴露routerexport default router
5.5.6 导航守卫
导航守卫主要是用来通过跳转或取消的方式守卫导航。分为前置守卫和后置钩子,有多种机会植入路由导航过程中:全局的、单个路由独享的、或者组件级的(全局导航守卫、路由独享的守卫、组件内的守卫)
全局前置守卫 router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';const Home = () => import ('../views/Home.vue');const User = () => import ('../views/User.vue');const MyOrder = () => import ('../views/MyOrder.vue');const MySetting = () => import ('../views/MySetting.vue');const MyPage = () => import ('../views/MyPage.vue');const MyArticle = () => import ('../views/MyArticle.vue');const routes = [{ path: '/', name: 'Home', // redirect: '/about', //打开的路由是/about // redirect: { name: 'About' }, component: Home, //加载的组件 meta: { title: '111111' } }, { // 在这里面定义的是路由独享的守卫 path: '/about', name: 'About', alias: '/a', meta: { title: '222222' }, component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', component: User, name: 'User', meta: { title: '333333' }, children: [{ path: '', // /user 当处于这个路由时指定默认的组件 component: MySetting }, { path: 'order', // /user/order 如果用/order就是到根目录下的 component: MyOrder, }, { path: 'setting', component: MySetting }, { path: 'page/:id', component: MyPage, meta: { title: '444444' } }, { path: 'article', component: MyArticle } ] } ]const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes});// 全局守卫 前置守卫跳转之前执行的 to是跳转到哪里 from是从哪跳转 next是可选的,如果不调用这个方法,路由就不会往下跳转,加了就相当于放行router.beforeEach((to, from) => { // 不写next() === return false /* console.log(to.fullPath, 'to'); console.log(from.fullPath, 'from') */ /* console.log(to, 'to'); console.log(from, 'from'); */ document.title = to.meta.title;});//后置钩子// 已经从form到了to 所以限制不了 没有nextrouter.afterEach((to, from) => { console.log(to.fullPath); console.log(form.fullPath)})export default router
5.5.7 keep-alive和vue-router结合使用
keep-alive使Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。(include: 正则表达式 exclude: 正则表达式)
router-view使vue-router内置组件,如果直接包含在keep-alive里面,所有的路径匹配到的组件都会被缓存。
src/App.vue
<template> <div id="nav"> <router-link to="/" active-class="active" class="bg">首页</router-link> | <router-link to="/about" class="bg">关于我们</router-link> | <router-link to="/user" class="bg">个人中心</router-link> | <router-link to="/about" custom v-slot="{navigate}"> <button @click="navigate" @keypress.enter="navigate" role="link">About US</button> </router-link> | <button @click="$router.push('/user')" :class="{active: $route.path == '/user'}">个人中心</button> <button @click="$router.go(-1)">返回</button> | {{$route.path}} </div> <!-- <router-view></router-view> --><!-- 组件被创建没有销毁 缓存了 会触发activated和deactivated方法 --> <router-view v-slot="{Component}"> <transition> <!--排除About、User组件 即没有这个功能 相反的include --> <keep-alive exclude="About, User"> <component :is="Component" /> </keep-alive> </transition> </router-view></template><style>#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50;}#nav { padding: 30px;}#nav a { font-weight: bold; color: #2c3e50;}#nav a.router-link-exact-active { color: pink;}.active { color: green !important;}.bg { background: #ccc;}.one{ float: left; width: 25%; height: 300px; background-color: pink;}.two{ float: left; width: 50%; height: 300px; background-color: green;}.three{ float: left; width: 25%; height: 300px; background-color: grey;}</style>
views/About.vue
<template> <div>这是关于我们的页面...</div></template><script>export default { name: "About", /* created() { document.title = '关于我们' } */ created() { console.log('About组件被创建了'); }, unmounted() { console.log('About组件被销毁了'); }, activated() { console.log('About缓存的组件激活时调用'); }, deactivated() { console.log('About缓存的组件停用时调用'); }}</script><style scoped></style>
views.User.vue
<template> <div>这是个人中心的页面...</div> <br> <div class="menu"> <ul> <li><router-link to="/user/order">我的订单</router-link></li> <li> <router-link to="/user/setting">个人设置</router-link></li> <li><router-link to="/user/page/1">单页1</router-link></li> </ul> <ul> <li v-for="item in articles"><router-link :to="'/user/page/' + item.id">{{item.title}}</router-link></li> </ul> <li> <router-link to="/user/article?name=111&age=10">文章一</router-link></li> <li><router-link :to="{path: '/user/article', query: {name: 'hello', age: 100}}">文章二</router-link></li> <br> <button @click="$router.push({path:'/user/article', query: {name:'world', age:30}})">文章三</button> </div> <div class="content"> <router-view v-slot="{Component}"> <transition> <keep-alive> <component :is="Component" /> </keep-alive> </transition> </router-view> </div></template><script>export default { name: "User", data() { return { articles: [ {id: 11, title: '这是文章一'}, {id: 12, title: '这是文章一'}, {id: 13, title: '这是文章一'}, {id: 14, title: '这是文章一'}, {id: 15, title: '这是文章一'}, ], path: '/user' } }, activated() { this.$router.push(this.path); }, // 组件内的守卫 当User/这个路由离开的时候触发 当组件离开时form保存的是离开时的路由/user/page/id,由于router-view搭配了keep-alive所以不会销毁 再次点击这个路由时会触发activated,然后把里面的路由赋值给activated里面的当前的路由,这样可以使/user/page/id保持原来的状态 beforeRouteLeave(to, from) { console.log(from.fullPath, '111'); this.path = from.fullPath; }}</script><style scoped> .menu { width: 30%; height: 300px; background-color: pink; float: left; } .content { width: 70%; height: 300px; background-color: green; float: right; } a { font-weight: bold; color: #2c3e50;} a.router-link-exact-active { color: purple;}</style>
以上详情可看vue3/demo4
5.6 Vuex状态管理应用概述
Vuex不属于vue,只是vue生态的插件,和路由、脚手架(vue-cli)一样。
Vuex是一个专门为Vue.js应用程序开发的状态管理模式。就是一个加强版的data!在单页应用中会有一个data函数,管理当前应用的状态。处理大量的需要在组件间传递的数据,直接定义一个全局的data属性保持就行了。
如果我们的页面比较简单,切记千万不要没事找事引入Vuex,我们使用Vuex是因为项目变得复杂之后,有很多数据需要在父组件、子组件和孙组件之间传递,处理起来很繁琐,于是需要Vuex这样一个可以对这一部分数据进行统一管理的东西。(超全局变量 响应式)
什么情况需要使用Vuex管理状态在多个组件间共享?大型项目中组件很多,多个组件中共用的数据。(用户的登录状态、用户名称、头像、地理位置信息等) (商品的收藏、购物车中的物品)
5.6.1 安装和体验Vuex状态管理
vue create demo5创建新项目的时候选择Vuex,创建出来的项目src下有一个store文件夹里面有index.js(直接用vue-cli创建好了和自己写的没区别,安装脚手架的规范完成的)。如果用npm i Vuex -S的话需要自己创建文件夹写配置文件在vue中引用就可以了(更麻烦)。
store/index.js
import { createStore } from 'vuex'// $store代表整个下面的 $store.state.num 在任何组件里面通过它都可以使用export default createStore({ state: { num: 0 }, mutations: {}, actions: {}, modules: {}})
src/main.js主入口文件
import { createApp } from 'vue'import App from './App.vue'// $router $routeimport router from './router'// 从store文件中找到index.js导入使用 全局属性$storeimport store from './store'createApp(App).use(store).use(router).mount('#app')
5.6.2 使用devtools工具查看状态管理
这是“单向数据流”概念的简单表示:
但是,当我们有多个共享相同状态的组件时,简单性很快就会崩溃:
- 多个视图可能取决于同一状态。
- 来自不同观点的动作可能需要改变同一状态。
通过mutations(变化)来管理状态,可以通过devtools工具(devtools属于浏览器的插件)查看修改的信息(什么时候点击修改的等)
不推荐直接修改状态(state=>vue components=>state),通过state=>vue components= >actions=>mutations=>state
需要异步请求服务器中的数据的时候用到actions,如果用不到就跳过它直接commit到mutations里面,通过mutations来修改状态
<template> <div class="home"> <h2>这是在单页模板中应用</h2> <h2>{{count}}</h2> <button @click="count--">-</button> <button @click="count++">+</button> <hr> <h2>使用全局的状态</h2> <!-- 直接操作值改变不是状态管理--> <h2>{{$store.state.num}}</h2> <button @click="$store.state.num--">-</button> <button @click="$store.state.num++">+</button> <h2>使用Mutations来修改管理状态</h2> <h2>{{$store.state.dnum}}</h2> <!-- 通过调用组件里面的方法来调用store/index.js里面的mutations里面的方法 --> <button @click="sub1">-</button> <button @click="add1">+</button> <hello-world :count="count"></hello-world> </div></template><script>// @ is an alias to /srcimport HelloWorld from '@/components/HelloWorld.vue';export default { name: 'Home', components: { HelloWorld }, data() { return { count: 0 } }, methods: { add1() { this.$store.commit('add'); }, sub1() { this.$store.commit('sub'); } }}</script>
store.index.js
import { createStore } from 'vuex'// $store代表整个下面的 $store.state.num 在任何组件里面通过它都可以使用export default createStore({ state: { num: 0, dnum: 0 }, mutations: { // 会默认把state当作参数传递 sub(state) { state.dnum--; }, add(state) { state.dnum++; } }, actions: {}, modules: {}})
5.6.3 Mutations传参问题
<template> <div class="home"> <h2>这是在单页模板中应用</h2> <h2>{{count}}</h2> <button @click="count--">-</button> <button @click="count++">+</button> <hr> <h2>使用全局的状态</h2> <h2>{{$store.state.num}}</h2> <button @click="$store.state.num--">-</button> <button @click="$store.state.num++">+</button> <h2>使用Mutations来修改状态</h2> <h2>{{$store.state.dnum}}</h2> <!-- 通过调用组件里面的方法来调用store/index.js里面的mutations里面的方法 --> <button @click="sub1">-</button> <button @click="add1">+</button> <h2>使用Mutations来修改带一个参数的</h2> <h2>{{$store.state.dnum}}</h2> <button @click="sub2">-</button> <button @click="add2">+</button> <h2>使用Mutations来修改带多个参数的</h2> <h2>{{$store.state.dnum}}</h2> <button @click="sub3">-</button> <button @click="add3">+</button> <h2>用对象样式提交</h2> <button @click="add4">+</button> <hello-world :count="count"></hello-world> </div></template><script>// @ is an alias to /srcimport HelloWorld from '@/components/HelloWorld.vue';export default { name: 'Home', components: { HelloWorld }, data() { return { count: 0 } }, methods: { add1() { this.$store.commit('add'); }, sub1() { this.$store.commit('sub'); }, // 使用带一个参数的 add2() { let count = 2; this.$store.commit('add2', count); }, sub2() { let count = 2; this.$store.commit('sub2', count); }, // 带多个参数的 add3() { // 这样声明传参是不行的 /* let count = 2; let num = 1; */ // this.$store.commit('add3', count, num); const payload = {count: 2, num: 1} this.$store.commit('add3', payload); }, sub3() { /* let count = 2; let num = 1; */ // this.$store.commit('sub3', count, num); const payload = {count: 2, num: 1} this.$store.commit('sub3', payload); }, // 用对象样式提交 add4() { const payload = {count: 2, num: 1} this.$store.commit({ type: 'add4', payload }); } }}</script>
store/index.js
import { createStore } from 'vuex'// $store代表整个下面的 $store.state.num 在任何组件里面通过它都可以使用export default createStore({ state: { num: 0, dnum: 0 }, mutations: { // 会默认把state当作参数传递 sub(state) { state.dnum--; }, add(state) { state.dnum++; }, // 接收一个参数的 参数可以是任意类型 sub2(state, count) { state.dnum -= count; }, add2(state, count) { state.dnum += count; }, // 接收多个参数的 sub3(state, payload) { state.dnum -= (payload.count + payload.num); }, add3(state, payload) { state.dnum += (payload.count + payload.num); }, // 用对象样式提交payload本身就是对象,里面的参数还是对象 add4(state, payload) { state.dnum += (payload.payload.count + payload.payload.num); } }, actions: {}, modules: {}})
Vuex中的计算属性getters应用
<template> <div class="home"> <h2>这是在单页模板中应用</h2> <h2>{{count}}</h2> <!-- 当我们的状态需要经过一系列变化之后显示在组件里面的时候通过计算属性来处理 --> <h2>计算属性:{{mypow}}</h2> <button @click="count--">-</button> <button @click="count++">+</button> <hr> <h2>state中的平方:{{vuexpow}}</h2> <!-- 这样不仅限于本页面使用 可以在任何地方使用 --> <h2>使用getters中的计算属性: {{$store.getters.vxnumpow}}</h2> <h3>购物车的总价:{{$store.getters.totalage}}</h3> <h3>年龄大于30的:{{$store.getters.goodsgt}}</h3> <!-- 通过传递参数改变年龄的选择 --> <h3>年龄大于50的:{{$store.getters.goodself(50)}}</h3> <button @click="$store.state.num--">-</button> <button @click="$store.state.num++">+</button> <hello-world :count="count"></hello-world> </div></template><script>// @ is an alias to /srcimport HelloWorld from '@/components/HelloWorld.vue';export default { name: 'Home', components: { HelloWorld }, data() { return { count: 0 } }, computed: { /* mypow: { get() { } } */ // 如果里面只有一个get方法可以简写 mypow() { return this.count * this.count; },// 用计算属性操作vuex(state)里面的值 在本页面里面可以用 其他的页面就用不了了 所以在vuex里面提供了一个计算属性getter vuexpow() { return this.$store.state.num * this.$store.state.num; } }}</script>
store/index.js
import { createStore } from 'vuex'// $store代表整个下面的 $store.state.num 在任何组件里面通过它都可以使用export default createStore({ state: { num: 0, dnum: 0, cartlist: [ { name: '金香香是大傻子', age: 44 }, { name: '李敏', age: 22 }, { name: '李敏2', age: 72 }, { name: '李敏3', age: 42 } ] }, getters: { // 第一个参数默认是state 起什么名字无所谓 vxnumpow(state) { return state.num * state.num; }, /* totalage(state) { return state.cartlist.reduce((prev, item) => prev + item.age, 0) }, */ totalage(state, getters) { // 这个里面的this指state里面 如果想用到getters里面的计算属性就要把getters当成参数传递进去 return getters.goodsgt.reduce((prev, item) => prev + item.age, 0) }, goodsgt(state) { return state.cartlist.filter(n => n.age > 30); }, // 通过传递参数改变年龄的选择 goodself(state, getters) { // 参数传递给了函数里面的age /* return function(age) { return state.cartlist.filter(n => n.age > age); } */ return age => state.cartlist.filter(n => n.age > age); } }, mutations: {}, actions: {}, modules: {}})
5.6.4 Actions异步处理操作
<template> <div class="home"> <h2>这是在单页模板中应用</h2> <h2>COUNT:{{count}}</h2> <!-- 当我们的状态需要经过一系列变化之后显示在组件里面的时候通过计算属性来处理 --> <button @click="count--">-</button> <button @click="count++">+</button> <hr> <h2>NUM:{{$store.state.num}}</h2> <button @click="$store.state.num--">-</button> <button @click="$store.state.num++">+</button> <h2>DNUM:{{$store.state.dnum}}</h2> <button @click="sub1">-</button> <button @click="add1">+</button> <br> <button @click="cnum">Actions</button> <hello-world :count="count"></hello-world> </div></template><script>// @ is an alias to /srcimport HelloWorld from '@/components/HelloWorld.vue';export default { name: 'Home', components: { HelloWorld }, data() { return { count: 0 } }, methods: { add1() { this.$store.commit('add'); }, sub1() { this.$store.commit('sub'); }, cnum() { // 通过dispatch到store文件夹下index.js文件中的actions里面的demo方法 this.$store.dispatch('demo', {count: 88, name: 'vuex'}).then(() => {}); } }, computed: { /* mypow: { get() { } } */ // 如果里面只有一个get方法可以简写 mypow() { return this.count * this.count; },// 用计算属性操作vuex(state)里面的值 在本页面里面可以用 其他的页面就用不了了 所以在vuex里面提供了一个计算属性getter vuexpow() { return this.$store.state.num * this.$store.state.num; } }}</script>
store/index.js
import { createStore } from 'vuex'import { resolveComponent } from 'vue';// $store代表整个下面的 $store.state.num 在任何组件里面通过它都可以使用export default createStore({ state: { num: 0, dnum: 0, cartlist: [ { name: '金香香是大傻子', age: 44 }, { name: '李敏', age: 22 }, { name: '李敏2', age: 72 }, { name: '李敏3', age: 42 } ] }, getters: { // 第一个参数默认是state 起什么名字无所谓 vxnumpow(state) { return state.num * state.num; }, /* totalage(state) { return state.cartlist.reduce((prev, item) => prev + item.age, 0) }, */ totalage(state, getters) { // 这个里面的this指state里面 如果想用到getters里面的计算属性就要把getters当成参数传递进去 return getters.goodsgt.reduce((prev, item) => prev + item.age, 0) }, goodsgt(state) { return state.cartlist.filter(n => n.age > 30); }, // 通过传递参数改变年龄的选择 goodself(state, getters) { // 参数传递给了函数里面的age /* return function(age) { return state.cartlist.filter(n => n.age > age); } */ return age => state.cartlist.filter(n => n.age > age); } }, mutations: { sub(state) { state.dnum--; }, add(state) { state.dnum++; }, cnum(state) { state.num = 99; } }, actions: { // context是一个对象 里面有state、commit、dispatch等数据 // const {state, commit} = context; demo({ state, commit, getters }, payload) { return new Promise((resolve, reject) => { setTimeout(() => { // 可以更改数据的值 但不是唯一的方法 修改状态的唯一途径是通过mutations // context.state.num = 99; // context.commit('cnum'); state.num = 9; commit('cnum'); console.log(payload); console.log(payload.name); resolve(); }, 1000); }) }, fun(context) { // const {state, commit} = context; } }, modules: {}})
5.6.5 Moduls模块划分和文件拆分
<template> <div class="home"> <h2>user模块中的name</h2> <!-- 相当于把模块加到了state里面 --> <h2>{{$store.state.user.name}}</h2> <h2><button @click="cname">改名字</button></h2> <h2>{{$store.getters.fullname}}</h2> <h2>{{$store.getters.fullname2}}</h2> <h2>{{$store.getters.fullname3}}</h2> <h2><button @click="cname2">异步改名字</button></h2> <h2>这是在单页模板中应用</h2> <h2>COUNT:{{count}}</h2> <button @click="count--">-</button> <button @click="count++">+</button> <hr> <h2>NUM:{{$store.state.num}}</h2> <button @click="$store.state.num--">-</button> <button @click="$store.state.num++">+</button> <h2>DNUM:{{$store.state.dnum}}</h2> <button @click="sub1">-</button> <button @click="add1">+</button> <br> <button @click="cnum">Actions</button> <hello-world :count="count"></hello-world> </div></template><script>// @ is an alias to /srcimport HelloWorld from '@/components/HelloWorld.vue';export default { name: 'Home', components: { HelloWorld }, data() { return { count: 0 } }, methods: { add1() { this.$store.commit('add'); }, sub1() { this.$store.commit('sub'); }, cnum() { // 通过dispatch到store文件夹下index.js文件中的actions里面的demo方法 this.$store.dispatch('demo', {count: 88, name: 'vuex'}).then(() => {}); }, cname() { // 这个不用和访问属性一样找到user,只有状态这块是放到全局的状态里面,其他的方法和我们提交的都是一样的 直接找 会所有的模块去搜索 会先搜索mutations里面的setname,如果有就会调用,没有就会找绑定的模块里面的方法。如果有同名的都会执行 this.$store.commit('setname', '学习快乐'); }, cname2() { this.$store.dispatch('dosome'); } }, computed: { /* mypow: { get() { } } */ // 如果里面只有一个get方法可以简写 mypow() { return this.count * this.count; },// 用计算属性操作vuex(state)里面的值 在本页面里面可以用 其他的页面就用不了了 所以在vuex里面提供了一个计算属性getter vuexpow() { return this.$store.state.num * this.$store.state.num; } }}</script>
store/index.js模块划分
import { createStore } from 'vuex'import { resolveComponent } from 'vue';import getters from './getters';import mutations from './mutations';import actions from './actions';// user模块也可以导出去 单独创建一个usermodule文件夹 './usermodule/use'const user = { // 返回json对象需要在{}外面加上() state: () => ({ name: 'lmonkey', slogen: 'abc' }), getters: { fullname(state) { return state.name + state.slogen; }, // 再次用到getter fullname2(state, getters) { return getters.fullname + '2222'; }, // rootState代表的是根的state对象 fullname3(state, getters, rootState) { console.log(rootState); return getters.fullname2 + rootState.num; } }, mutations: { setname(state, payload) { state.name = payload; } }, actions: { dosome(context) { // {state, commit, getters, rootgetters, rootState} setTimeout(() => { context.commit('setname', 'hello vuex'); }, 1000); } }};const article = { state: {}, getters: {}, mutations: {}, actions: {}};const cart = { state: {}, getters: {}, mutations: {}, actions: {}}const state = { // 相当于把模块里面的user放到了这个里面 所以访问user里面的name时需要输入$store.state.user.name num: 0, dnum: 0, cartlist: [ { name: '金香香是大傻子', age: 44 }, { name: '李敏', age: 22 }, { name: '李敏2', age: 72 }, { name: '李敏3', age: 42 } ] } // $store代表整个下面的 $store.state.num 在任何组件里面通过它都可以使用export default createStore({ state, getters, mutations, actions, modules: { // 划分多个模块分别存放数据和方法 /* user: { state: {}, getters: {}, mutations: {}, actions: {} }, article: { state: {}, getters: {}, mutations: {}, actions: {} }, cart: { state: {}, getters: {}, mutations: {}, actions: {} } */ user, article, cart }})
文件拆分 把根里面的代码拆出去 开发的时候文件更清晰
store/actions.js
export default { // context是一个对象 里面有state、commit、dispatch等数据 // const {state, commit} = context; demo({ state, commit, getters }, payload) { return new Promise((resolve, reject) => { setTimeout(() => { // 可以更改数据的值 但不是唯一的方法 修改状态的唯一途径是通过mutations // context.state.num = 99; // context.commit('cnum'); state.num = 9; commit('cnum'); console.log(payload); console.log(payload.name); resolve(); }, 1000); }) }, fun(context) { // const {state, commit} = context; }}
store/mutations.js
export default { sub(state) { state.dnum--; }, add(state) { state.dnum++; }, cnum(state) { state.num = 99; }, /* setname(state, payload) { state.num = payload; } */}
store/getters.js
export default { // 第一个参数默认是state 起什么名字无所谓 vxnumpow(state) { return state.num * state.num; }, /* totalage(state) { return state.cartlist.reduce((prev, item) => prev + item.age, 0) }, */ totalage(state, getters) { // 这个里面的this指state里面 如果想用到getters里面的计算属性就要把getters当成参数传递进去 return getters.goodsgt.reduce((prev, item) => prev + item.age, 0) }, goodsgt(state) { return state.cartlist.filter(n => n.age > 30); }, // 通过传递参数改变年龄的选择 goodself(state, getters) { // 参数传递给了函数里面的age /* return function(age) { return state.cartlist.filter(n => n.age > age); } */ return age => state.cartlist.filter(n => n.age > age); },}
以上详情可看vue3/demo5
5.7 CompositionAPI组合API
介绍和基本体验 详情可见vue3/demo6文件
官方网址:v3.vuejs.org => Ecosystem => Vuex / Vue Router => Composition API
使用传统的option配置方法写组件的时候问题,随着业务赋值度越来越高,代码量会不断的加大;由于相关业务的代码需要遵循option的配置写到特定的区域,导致后续维护非常的复杂,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的。
Composition API字面意思是组合API,它是为了实现基于函数的逻辑复用机制而生的。主要思想是,我们将它们定义为从新的setup(入口方法)函数返回的JavaScript变量,而不是将组件的功能(例如state、methods、computed等)定义为对象属性。
原生Vue
<template> <div class="home"> <h3>count:{{count}}</h3> <h3>double:{{double}}</h3> <h3><button @click="add">增加</button></h3> </div></template><script>// 原生的所有都在options配置里面export default { name: 'Home', data() { return { count: 3 } }, computed: { double() { return this.count * this.count; } }, methods: { add() { this.count++; } }}</script>
组合式API
<template> <div class="about"> <h3>count: {{data.count}}</h3> <h3>double: {{data.double}}</h3> <h3><button @click="add">增加</button></h3> </div></template><script>// 组合式API 所有的变量和方法随便声明去写,不用受options参数格式的限制,可维护扩展import {reactive, computed} from 'vue';export default { //对象 setup() { //方法 const data = reactive({ count: 2, double: computed(() => data.count * 2), }); // 因为不是在对象里面所以function不能省略 function add() { data.count++; }; return {data, add}; }}</script>
5.7.1 组合API入口方法setup详解
setup()函数是vue3中专门新增的方法,可以理解为Composition API的入口
子组件
<template> <div>这是子组件SubComp</div> <slot>9999999</slot></template><script> export default { name: "SubComp", props: { one: { type: String }, two: { type: String } }, // setup()在beforecreate()之前调用 在创建组件之前被调用,所以setup被执行时,组件实例并没有被创建。因此在setup函数中,我们将没有办法获取到this // props指的是传过来的属性,context是上下文 // context = {attrs, slots, emit} 解构赋值 setup(props, context) { console.log(props.one); // 由于上面的props没有接收 所以获取不到 console.log(props.desc); //undefined // props接收了的获取不到 没接收的可以获取 console.log(context.attrs.one); //undefined console.log(context.attrs.desc); // 获取父组件传过来的插槽插进来的内容 console.log(context.slots.default()); // 向父组件传值 context.emit('myadd', '向父组件传数据') }}</script><style lang="less" scoped></style>
父组件
<template> <div>这是SetUp组件</div> <SubComp @myadd="add" one="111111" two="22222" desc="AAAAA"> <h2>8888888888</h2> </SubComp></template><script>import SubComp from '../components/SubComp';// 只适用于路由的懒加载 组件是不可以的// const SubComp = () => import('../components/SubComp');export default { name: 'SetUp', components: { SubComp }, methods: { add(str) { console.log(str) } }}</script>
5.7.2 Composition中常用的API应用
<template> <div><h1>常用API的应用</h1></div> 原生的:{{num}} <br> 调用方法:{{myfun()}}<br> ref的变量:{{num2}}<br> {{myfun2(55)}}<br> <!-- reactive中的对象:{{user.name}} --> reactive中的对象:{{name}} --- {{age}}</template><script> import { ref, reactive, toRefs, readonly, isRef } from "vue";export default { name: "ComApi", setup() { // 原生的变量是没有响应式的 如果想变成响应式的就要引入ref()方法 let num = 1; let myfun = function() { num++; console.log(num); }; // ref() 返回的是对象 在setup里面访问num2需要num2.value let num2 = ref(22); // 判断num2是不是对象(是原生的还是响应式的) let num3 = isRef(num2) ? num2.value = 44 : num2 = 55; let myfun2 = function(newvalue) { // 在模板中可以直接使用ref的变量 在setup里面需要加上num.value console.log(num2.value) num2.value = newvalue; // 在user里面使用num2: num2 这里就可以使用user.num2 user.num2 == num2.value; }; // 对象类型的数据用reactive声明(也具有响应式) 除此以外的用ref let user = reactive({ name: 'lmonkey', age: 99, sex: '男', num2 }) // 通过readonly()方法可以把响应式数据转换为原生数据 let user2 = readonly(user); // 原生的返回之后才能在上面使用 return { num, myfun, num2, myfun2, /* name: user.name, age: user.age */ // 解构后的user里面数据就不是响应式的了 // ...user // 使用toRefs()方法解决 ...toRefs(user) } }}</script><style scoped></style>
5.7.3 Composition中的computed计算属性API
computed()用来创建计算属性,返回值是一个ref的实例。
<template> <div> <h2>计算属性</h2> firstname: <input type="text" v-model="firstname"><br> lastname: <input type="text" v-model="lastname"><br> {{firstname + '.' + lastname}}<br> {{fullname}}<br> age: <input type="text" v-model="age"><br> {{age}} 岁, {{show}} </div></template><script>import { reactive, toRefs, computed } from 'vue'export default { name: "ComputedDemo", setup() { const user = reactive({ firstname: 'lmonkey', lastname: 'com', age: 20 }); let fullname = computed(() => { return user.firstname + '.' + user.lastname; }); let show = computed(() => { if(user.age < 20) { return '还年轻'; } else if(user.age < 40) { return '小青年'; } else { return '岁数不小了'; } }); return { ...toRefs(user), fullname, show } }}</script><style scoped></style>
5.7.4 CompositionAPI侦听器watch
watch()函数用来监视某些数据项的变化,从而触发某些特点的操作
watchEffect立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。不能指定监听参数的变化。watch中包含它的作用,所以可以不用
<template> <div> <h2>侦听器</h2> <button @click="a++">a= {{a}}</button> <button @click="b++">b= {{b}}</button> </div></template><script>import {ref, watch, watchEffect, reactive, toRefs} from 'vue';export default { name: 'Watch', // setup() { // let a = ref(1); // let b = ref(2); // /* watch(() => { // // 默认先执行一次 // // 里面写哪个变量,就会自动监听哪个变量的变化 // console.log('#######') //即使没有数据变化 这个也会默认执行一次 // console.log(a.value + '------' + b.value); // }); */ // // 指定监听里面a的变化 默认没有执行 // // 第三个参数immediate: true 初始化一次 尽管a没有变化也会默认执行一次 // watch([a, b], ([newA, newB], [oldA, oldB]) => { // console.log('--------'); //a不变化就不会执行 // // 新值和旧值 // console.log(newA + '----' + oldA); // console.log(newB + '----' + oldB); // }, {immediate: true}) // /* watchEffect(() => { // console.log('#######') //会默认执行一次 // }); */ // return { // a, // b // } // } setup() { const user = reactive({ a: 1, b: 2 }); watch([() => user.a, () => user.b], ([newa, newb],[olda, oldb]) => { console.log(user.a + '###' + user.b); console.log(newa + '---' + olda); console.log(newb + '---' + oldb); }) /* watch(user, (newx, oldx) => { console.log(user.a + '###' + user.b); // 打印不出来 不知道是哪个值变化了 console.log(newx + '---' + oldx); }) */ /* watchEffect( () => { console.log(user.a + '------' + user.b); }) */ return { ...toRefs(user) } }}</script><style scoped></style>
5.7.5 Composition中的生命周期API
beforeCreate和created函数不能在setup()中使用
<template> <div> <h2>生命周期</h2> <h2>生命周期</h2> </div></template><script>import {onMounted, onUpdated} from 'vue';export default { name: "Life", setup() { let one = '1111'; onMounted(() => { console.log(one + 'onmounted.....'); }); // 数据或者界面有更新就会调用 onUpdated(() => { console.log('onUpdated'); }) }, // 外面的生命周期函数用不到组合API里面任何东西 beforeCreate() { console.log('beforeCreate'); }, created() { console.log('create'); }, mounted() { console.log('mounted'); }}</script>
5.7.6 在组合API中provide和inject使用
用于父组件向子组件传值,使用场景:当父组件向它的孙子(孙子的孙子的孙子…)传值的时候使用,其他的组件不使用这个值。
<template> <div> <h1>这是根组件</h1> {{title}} <input v-model="title"> <Two></Two> </div></template><script>import Two from '../components/Two';import Three from '../components/Three';import {ref, provide, reactive, toRefs} from 'vue';export default { name: 'Root', components: { Two }, /* data() { return { title: '这是根组件提供的数据' } }, */ setup() { let title = ref('这是根组件提供的数据'); let obj = reactive({ name: 'lmonkey', age: 33, sex: '男' }) // 键值的形式 provide("title",title); provide('user', obj); return { title, ...toRefs(obj) } }, /* provide: { title: 'abc' } */// 不是响应式的 /* provide() { return { title: this.title } } */}</script>
<template> <div> <h3>这是第三层组件</h3> {{title}} <input v-model="title"><br> {{user.name}}---{{user.age}}---{{user.sex}} </div></template><script>import { inject } from 'vue'export default { name: 'Three',// 不是响应式的// inject: ['title']// 数据是响应式的setup() { // inject里面传入的是key let title = inject('title'); let user = inject('user'); return { title, user }}}</script>
5.7.7 CompositionAPI组合路由器使用
由于我们无权访问setup的内部this,因此无法直接访问this. r o u t e 或 t h i s . route或this. route或this.router了,相反,我们使用useRouter和useRoute函数。
请注意,我们仍然可以访问 r o u t e r 和 router和 router和route在模板中,因此无需在router或者route内返回setup。
导航守卫:尽管可以将组件内导航包含与setup功能结合使用,但Vue Router会将更新和离开提供CompositionAPI函数
onBeforeRouteLeave((to, from) => {})
onBeforeRouteUpdate((to, from) => {})
router/index.js
import { createRouter, createWebHistory } from 'vue-router'import Home from '../views/Home.vue'const SetUp = () => import ('../views/SetUp.vue');// import SetUp from '../views/SetUp.vue';const ComApi = () => import ('../views/ComApi.vue');const Computed = () => import ('../views/ComputedDemo.vue');const Watch = () => import ('../views/Watch.vue');const Life = () => import ('../views/Life.vue');const Root = () => import ('../views/Root.vue');const Router = () => import ("../views/RouterApi.vue")const MyPage = () => import ('../views/MyPage.vue')const MyArticle = () => import ('../views/MyArticle.vue')const routes = [{ path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import ( /* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/setup', name: 'SetUp', component: SetUp }, { path: '/comapi', component: ComApi, name: 'ComApi' }, { path: '/computed', component: Computed, name: 'Computed' }, { path: '/watch', component: Watch, name: 'Watch' }, { path: '/life', component: Life }, { path: '/root', component: Root }, { path: '/routerapi', component: Router, children: [{ path: 'page', component: MyPage }, { path: 'page/:id', component: MyPage }, { path: 'article', component: MyArticle } ] }]const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes})export default router
父级路由
<template> <div> <div id="menu"> <h5 v-for="item in pages"><router-link :to="'/routerapi/page/' + item.id">{{item.title}}</router-link> </h5> <h5><hr></h5> <h5 ><router-link :to="{path: '/routerapi/article', query: {name: 'abc', keyword: 'hello'}}">文章一</router-link> </h5> <h5><button @click="$router.push({path: '/routerapi/article', query: {name: 'xuz', keyword: 'world'}})">文章二</button></h5> </div> <div id="content"> <router-view></router-view> </div> </div></template><script>import { reactive } from 'vue'export default { name: "RouterApi", data() { return { pages: [ {id: 1, title: '1111'}, {id: 2, title: '2222'}, {id: 3, title: '3333'}, {id: 4, title: '4444'}, {id: 5, title: '5555'}, ] } }}</script><style scoped lang="scss"> #menu { width: 30%; background-color: pink; height: 300px; float: left; } #content { width: 70%; height: 300px; background-color: blue; float: right; } a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: #42b983; } }</style>
子路由
<template> <div> <h1>MyPage</h1> <h3>{{$route.params.id}}</h3> <h3>{{pid}}</h3> <h2>id: {{id}}</h2> </div></template><script>import {useRoute, useRouter, onBeforeRouteLeave, onBeforeRouteUpdate} from 'vue-router';import {watch, ref} from 'vue';export default { name: "MyPage", setup() { const route = useRoute(); const router = useRouter(); // 这样id的值只会改变一次 使用侦听器解决 // let id = route.params.id; let id = ref(0); watch(() => route.params, (newid) => { console.log(newid.id); id.value = newid.id; // 异步请求服务器api接口查找用户信息 }); onBeforeRouteLeave((to, from) => { let isOk = window.confirm(`你确定要从${from.fullPath}到${to.fullPath}`); if(!isOk) return false; }) // onBeforeRouteUpdate() /* setTimeout(() => { router.push({path: '/routerapi/article', query: {name: '444', keyword: '666'}}) },5000) */ return { id }; }, computed: { pid() { return this.$route.params.id; } }}</script><style scoped lang="scss"> </style>
子路由
<template> <div> <h1>MyArticle</h1> <h3>{{$route.query.name}}</h3> <h3>{{$route.query.keyword}}</h3> </div></template><script>import {useRoute, useRouter} from 'vue-router';import {watch, ref} from 'vue';export default { name: "MyArticle", setup() { const route = useRoute(); const router = useRouter(); // 只能打印一次 需要用到侦听器 console.log(route.query.name, route.query.keyword); return { }; }}</script><style scoped lang="scss"> </style>
5.7.8 CompositionAPI结合Vuex使用
store/index.js
import { createStore } from 'vuex'export default createStore({ state: { num1: 11, num2: 22 }, mutations: { changenum1(state, payload) { state.num1 = payload; }, changenum2(state, payload) { state.num2 = payload; } }, actions: { timecnum1({ commit, state }) { setTimeout(() => { commit('changenum1', 44) }, 3000) }, timecnum2({ commit, state }) { setTimeout(() => { commit('changenum2', 55) }, 3000) } }, modules: {}, getters: { double1(state) { return state.num1 * 2; }, double2(state) { return state.num2 * 2; } }})
<template> <div> <h2>Vuex Api useStore</h2> <h3>num1: {{$store.state.num1}}</h3> <h3>getters-num1: {{$store.getters.double1}}</h3> <button @click="cnum(33)">修改num1: {{$store.state.num1}}</button> <button @click="canum()">修改anum1: {{$store.state.num1}}</button> <hr> <h3>num2: {{num2}}</h3> <h3>getters-double2: {{double2}}</h3> <button @click="cnum2(33)">修改num2: {{num2}}</button> <button @click="canum2(33)">修改anum2: {{num2}}</button> </div></template><script>import {computed} from 'vue';import {useStore} from 'vuex';export default { name: "VuexApi", setup() { const store = useStore(); return { // 用计算属性返回的是ref()实例,是响应式的 num2: computed(() => store.state.num2), double2: computed(() => store.getters.double2), cnum2: (newnum) => { store.commit('changenum2', newnum); }, canum2: () => { store.dispatch('timecnum2'); } } }, methods: { cnum(newnum) { this.$store.commit('changenum1', newnum) }, canum() { this.$store.dispatch('timecnum1') } } }</script><style scoped lang="scss"> </style>
第六章 Vue3.x项目开发
6.1 项目说明和初始化
项目说明:
后台开发:PHP + Laraval + MySQL + Redis + Restrestful api接口规范
前台模板:Vue3.x + Composition API + Vant组件库
后台管理:React
接口文档:https://www.showdoc.com.cn/1207745568269674
6.1.1 使用vue脚手架创建ewshop项目
在桌面创建一个文件夹,在vscode里面打开,打开控制台npm create ewshop 创建项目。
在ewshop文件下新建vue.config.js文件
6.1.2 初始化项目设置目录别名
vue.config.js
module.exports = { configureWebpack: { resolve: { alias: { // 为了访问的时候更方便 不用一层一层的找 'assets': '@/assets', 'components': '@/components', 'utils': '@/utils', 'views': '@/views', } } }, publicPath: './'}
<template> <div class="home"> <!-- 别名前面加波浪线 因为里面不是变量是字符串 --> <div id="demo"></div> <img src="~assets/images/glf1.b6e949b.png" alt=""> <img :src="imgsrc" alt=""> <HelloWorld msg="Welcome to Your Vue.js App"/> </div></template><script>// @ is an alias to /srcimport HelloWorld from '@/components/HelloWorld.vue'export default { name: 'Home', data() { return { // 别名 assets == @/assets == ../assets == src/assets imgsrc: require('assets/images/1.png') } }, components: { HelloWorld }}</script><style scoped> #demo { background: url('~assets/images/2.jpg') no-repeat; background-size: contain; width: 400px; height: 600px; }</style>
6.1.3 初始化项目的全局样式内容
- 在src/css文件夹中引入normalize.css文件(默认的公共样式 从第三方库中提取)
- 在src/css文件夹中创建自己的公共样式文件base.css
/* @import是html、css引入文件的语法单词 */@import './normalize.css'; :root { /* --变量 */ --color-text: #666; --color-high-text: #42bBaa; --color-background: #FFF; --color-tint: #42b983; --font-size: 14px; --line-height: 1.5;}*,*::before,*::after { margin: 0; padding: 0; box-sizing: border-box;}body { /* 禁止用户选择文字或图片 */ user-select: none; /* 使用var()调用变量 */ background-color: var(--color-background); color: var(--color-text); width: 100vw;}a { color: var(--color-text); text-decoration: none;}.left { float: left;}.right { float: right;}
- 将全局的公用样式导入到App.vue中
<template> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view/></template>// 这个style不能使用scoped属性 当style标签具有该scoped属性时,其CSS将仅应用于当前组件的元素 style样式只在这个组件里起作用<style lang="scss">// 将所有全局公用的css导入 这样每个组件都可以用上 统一风格@import 'assets/css/base.css';#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50;}#nav { padding: 30px; a { font-weight: bold; color: #2c3e50; &.router-link-exact-active { color: var(--color-high-text); } }}</style>
6.1.4 初始化项目封装网络请求
- 在src/network文件夹下新建一个request.js文件(写通用的内容)
在控制台下载axios => npm install axios -S
request.js
import axios from 'axios';export function request(config) { const instance = axios.create({ baseURL: 'https://api.shop.eduwork.cn', timeout: 5000 }) // 请求拦截 instance.interceptors.request.use(config => { // 如果有一些接口需要认证才可以访问就在这统一设置 // 直接放行 return config; }, err => {}) // 响应拦截 instance.interceptors.response.use(res => { console.log(res); // 直接放行 return res.data ? res.data : res; }, err => { // 如果有需要授权才可以访问的接口,统一去login授权 // 如果有错误,这里面会去处理,显示错误信息 }) return instance(config);}
-
其他页面单独的内容 在network文件夹下新建home.js
import { request } from './request';export function getHomeAllData() { // 这里需要return 才能返回promise实例 return request({ url: '/api/index', /* method: 'get', params: {} */ })}export function getBonner() {}
src/views/Home.vue
<template> <div class="home"> {{banner}} </div></template><script>import {onMounted, ref} from 'vue';import {getHomeAllData, getBanner} from '../network/home';export default { name: 'Home', setup() { const banner = ref([]); onMounted(() => { getHomeAllData().then(res => { banner.value = res.slides; }).catch(err => {}) }); return { banner } }, components: { }}</script><style scoped> #demo { background: url('~assets/images/2.jpg') no-repeat; background-size: contain; width: 400px; height: 600px; }</style>
6.2 项目的界面
6.2.1 项目的导航菜单制作
更多推荐
所有评论(0)