第一部分 前端工程化 Webpack
学习时间:2022年10月26日-~2022年11月7日
概念:
webpack
是前端项目工程化的具体解决方案.主要功能:
JavaScript
的兼容性、性能优化等强大的功能.好处:
注意:
Vue,React
等前端项目,基本上都是基于webpack
进行工程化开发的.新建项目空白目录,并运行npm init -y
命令,初始化包管理配置文件 package.json
.
新建src
源代码目录.
新建src -> index.html
首页和src -> index.js
脚本文件.
初始化首页基本的结构.
运行npm install jquery -s
命令,安装jQuery
.
通过ES6
模块化的方式导入jQuery
.
在终端运行如下的命令,安装webpack
相关的两个包:
npm install webpack@5.42.1 webpack-cli@4.7.2 -D
使用npm淘宝源:
npm config set registry https://registry.npm.taobao.org
查看当前的下载源:
npm config get registry
webpack.config.js
的webpack
配置文件,并初始化如下的基本配置:javascript//使用Node.js 中的导出语法,向外导出一个 webpack的配置对象
module.exports = {
//代表 webpack运行的模式,可选值有两个 development和production
mode: 'development'
}
package.json
的scripts
节点下,新增dev
脚本如下:javascript "scripts": {
"dev":"webpack" //添加脚本,可以通过npm run 执行dev
},
npm run dev
命令,启动webpack
进行项目的打包构建dist
中的main.js
注意:在Node.js-v16以上的版本,会出现打包失败的情况.
mode配置项:
development
开发使用,节省打包时间production
发布使用,节省体积在webpack 4.x和5.x
的版本中,有如下的默认约定:
默认的打包入口文件为src -> index.js
默认的输出文件路径为dist -> main.js
**注意:**可以在webpack.config.js
中修改打包的默认约定
在webipack.config.js
配置文件中,通过entry
节点指定打包的入口.通过output
节点指定打包的出口
代码演示:
javascriptconst path = require('path')//导入node.js中专门操作路径的模块
module.exports = {
mode:'development',
entry: path.join(__dirname,'./src/index.js'),//打包入口文件的路径
output:{
path:path.join(__dirname,'./dist'),//输出文件的存放路径
filename:'bundle.js'//输出文件的名称
}
}
通过安装和配置第三方的插件,可以拓展webpack
的能力,从而让webpack
用起来更方便。最常用的webpack
插件有如下两个:
webpack-dev-server
node.js
阶段用到的nodelmon
工具webpack
会自动进行项目的打包和构建html-webpack-plugin
webpack
中的HTML
插件(类似于一个模板引擎插件index.html
页面的内容安装:
npm install webpack-dev-server@3.11.2 -D
修改package.json -> scripts
中的dev
命令如下:
javascript"scripts":{
"dev":"webpack serve"//script节点下的脚本,可以通过npm run执行
}
再次运行npm run dev
命令,重新进行项目的打包
注意:此处使用最新的webpack-cli
版本
注意:如果localhost:8080
端口被占用,使用netstat -ano
命令查看占用的进程的PID
,使用taskkill /F /PID xxx
命令结束进程
注意:此时生成的bundle.js
文件位于[http://localhost:8080]:目录下,index.html
需要以../bundle.js
目录重新引入文件
安装:
npm install html-webpack-plugin@5.3.2 -D
javascript//1.导入HTML插件,得到一个构造函数
const HtmlPlugin = require('html-webpack-plugin')
//2.创建HTML插件的实例对象
const htmlPlugin = new HtmlPlugin({
template:'./src/index.html',//指定原文件的存放路径 filename:'./index.html'//指定生成的文件的存放路径
})
module.exports = {
mode: 'development',
plugins: [htmlPlugin]
// 3,通过 plugins节点,使htmlPlugin 插件生效
}
在webpack.config.js
配置文件中,可以通过devServer
节点对webpack-dev-server
插件进行更多的配置
代码演示:
javascriptdevServer:{
open: true,//初次打包完成后,自动打开浏览器
host: '127.0.0.1 ',//实时打包所使用的主机地址
port: 80//实时打包所使用的端口号
}
**注意:**凡是修改了webpack.config.js
配置文件,或修改了package.json
配置文件,必须重启实时打包的服务器,否则最新的配置文件无法生效!
在实际开发过程中,webpack
默认只能打包处理以.js
后缀名结尾的模块.其他非.js
后缀名结尾的模块,webpack
默认处理不了,需要调用loader
加载器才可以正常打包,否则会报错
loader 加载器的作用:协助webpack打包处理特定的文件模块.比如:
.css
相关的文件.less
相关的文件webpack
无法处理的高级JS
语法步骤:
运行npm i style-loader@3.0.0 css-loader@5.2.6 -D
命令,安装处理css
文件的loader
在webpack.config.js
的module -> rules
数组中,添加 loader
规则如下:
javascriptmodule:{//所有第三方文件模块的匹配规则
rules: [ //文件后缀名的匹配规则
{test: /\.css$/,use:['style-loader','css-loader']}
]
}
其中,test
表示匹配的文件类型,use
表示对应要调用的loader
注意:
use
数组中指定的loader
顺序是固定的
多个loader
的调用顺序是从后往前调用
npm i less-loader@10.0.1 less@4.1.1 -D
命令 webpack.config.js
的module -> rules
数组中,添加loader
规则如下:javascriptmodule:{//所有第三方文件模块的匹配规则
rules:[//文件后缀名的匹配规则
{test:/\.less$/,use:['style-loader','css-loader','less-loader']},
]
}
运行npm i url-loader@4.1.1 file-loader@6.2.0 -D
命令
在webpack.config.js
的module -> rules
数组中,添加loader
规则如下:
javascriptmodule: {//所有第三方文件模块的匹配规则
rules: [//文件后缀名的匹配规则
{test:/\.jpg|png|gif$/,use:'ur1-loader?limit=22229'},
]
}
其中?
之后的是 loader
的参数项:
limit
用来指定图片的大小,单位是字节(byte)
≤limit
大小的图片,才会被转为base64
格式的图片webpack
只能打包处理一部分高级的JavaScript
语法。对于那些webpack
无法处理的高级js
语法,需要借助于babel-loader
进行打包处理.例如webpack
无法处理下面的JavaScript
代码:
javascript//1.定义了名为info的装饰器
function info( target){
//2.为目标添加静态属性info
target.info = 'Person info'
}
//3.为 Person类应用info装饰器
@info
class Person{}
//4.打印 Person的静态属性info
console.log(Person.info)
运行如下的命令安装对应的依赖包:
npm i babel-loader@8.2.2 @babel/core@7.14.6 @babel/plugin-proposal-decorators@7.14.5 -D
在webpack.config.js
的 module -> rules
数组中,添加loader
规则如下:
javascript//注意:必须使用exclude指定排除项;因为node_modules目录下的第三方包不需要被打包
{test:/\.js$/,use:'babel-loader', exclude:/node_modules/}
在项目根目录下,创建名为babel.config.js
的配置文件,定义Babel
的配置项如下:
javascriptmodule.exports = {
//声明babel可用的插件
plugins:[['@babel/plugin-proposal-decorators',{legacy:true}]]
}
在package.json
文件的scripts
节点下,新增build
命令如下:
javascript"scripts":{
"dev:"webpack serve",//开发环境中,运行dev命令
"buiid":"webpack --mode production"//项目发布时,运行build命令
}
--model
是一个参数项,用来指定webpack
的运行模式.production
代表生产环境,会对打包生成的文件进行代码压缩和性能优化.
注意:
通过--model
指定的参数项,会覆盖webpack.config.js
中的 model
选项.
在webpack.config.js
配置文件的output
节点中,进行如下的配置:
javascriptoutput: {
path:path.join(__dirname,'dist'),
//明确告诉 webpack 把生成的 bundle.js文件存放到dist目录下的 js子目录中
filename:'js/bundle.js',
}
修改webpack.config.js
中的url-loader
配置项,新增outputPath
选项即可指定图片文件的输出路径:
javascripttest:/\.jpg|png|gif$/,
use:{
loader:'ur1-loader',
options:{
limit: 22228,
//明确指定把打包生成的图片文件,存储到dist目录下的 image文件夹中
outputPath: 'image',
},
},
}
为了在每次打包发布时自动清理掉dist
目录中的旧文件,可以安装并配置clean-webpack-plugin
插件:
npm install clean-webpack-plugin@3.0.0 -D
javascript//1.按需导入插件、得到插件的构造函数之后,创建插件的实例对象
const {CleanwebpackPlugin} = require('clean-webpack-plugin')
const cleanPlugin = new CleanWebpackPlugin()
//2.把创建的 cleanPlugin 插件实例对象,挂载到plugins节点中
plugins:[htmlPlugin,cleanPlugin],//挂载插件
Source Map
就是一个信息文件,里面储存着位置信息.也就是说,Source Map
文件中存储着压缩混淆后的代码,所对应的转换前的位置.
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试.
略
略
官方概念:Vue (读音/vju:/,类似于view)是一套用于构建用户界面的前端框架.
vue框架的特性,主要体现在如下两方面:
在使用了vue
的页面中,vue
会监听数据的变化,从而自动重 新渲染页面的结构。
**好处:**当页面数据发生变化时,页面会自动重新渲染!
注意:数据驱动视图是单向的数据绑定.
在填写表单时,双向数据绑定可以辅助开发者在不操作DOM的前提下,自动把用户填写的内容同步到数据源中。示意图如下:
**好处:**开发者不再需要手动操作DOM
元素,来获取表单元素最新的值!
MWVM
是vue
实现数据驱动视图和双向数据绑定的核心原理。MVVM
指的是 Model、View和ViewModel
,它把每个HTML
页面都拆分成了这三个部分,如图所示:
在MVVM概念中:
Model
表示当前页面渲染时所依赖的数据源.
View
表示当前页面所渲染的DOM
结构.
ViewModel
表示vue
的实例,它是MVVM
的核心.
ViewModel
作为MVVM
的核心,是它把当前页面的数据源(Model
)和页面的结构(View
)连接在了一起.
ViewModel
监听到,VM
会根据最新的数据源自动更新页面的结构VM
监听到,VM
会把变化过后最新的值自动同步到Model
数据源中当前,vue
共有3个大版本,其中:
2.x版本的vue
是目前企业级项目开发中的主流版本
3.x版本的vue
于2020-09-19发布,生态还不完善,尚未在企业级项目开发中普及和推广
1.x版本vue
几乎被淘汰,不再建议学习与使用
总结:
3.x版本的vue
是未来企业级项目开发的趋势;
2.x版本的vue
在未来(1~2年内)会被逐渐淘汰;
基本步骤:
vue.js
的script
脚本文件vue
所控制的DOM
区域vm
实例对象(vue
实例对象)html<body>
<!-- 2.在页面中声明一个将要被vue所控制的DOM区域-->
<div id="app">{{username]}</div>
<!-- 1.导入vue.js 的script脚本文件-->
<script src="./lib/vue-2.6.12.js"></ script> <script>
//3.创建vm实例对象(vue实例对象)
const vm = new Vue({
//3.1指定当前vm实例要控制页面的哪个区域
el:'#app',
//3.2指定Model数据源
data: {
username: 'zs'
}
})
</script>
</body>
指令(Directives)
vue中的指令按照不同的用途可以分为如下6大类:
内容渲染指令
属性绑定指令
事件绑定指令
双向绑定指令
条件渲染指令
列表渲染指令
语法: <p>{{}}</p>
vue
提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持Javascript
表达式的运算.例如:
{{number+1}}
{{ok?''YES':'NO'}}
内容渲染指令用来辅助开发者渲染DOM元素的文本内容。
常用的内容渲染指令有如下3个:
v-text
{{ }}
v-html
v-text
语法:
xml<div v-text="username"></div>
注意:v-text
语法会覆盖元素内的默认值
{{ }}
语法: <p>{{}}</p>
vue
提供的{{}}
语法,专门用来解决v-text
会覆盖默认文本内容的问题.这种{{}}
语法的专业名称是插值表达式(英文名为).v-html
语法:
xml<div v-html="username"></div>
v-text
指令和插值表达式只能渲染纯文本内容。如果要把包含HTML
标签的字符串渲染为页面的HTML
元素,则需要用到v-html
这个指令:注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中.
如果需要为元素的属性动态绑定属性值,则需要用到v-bind
属性绑定指令
语法:<div v-bind:bgcolor="">
简写::
在使用v-bind
属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号.
vue
提供了v-on
事件绑定指令,用来辅助程序员为DOM
元素绑定事件监听.
简写:@
语法:
xml<button @click="add"></button>
methods:{
add(){
//在此访问data中的数据
this.count++;
}
}
$event
为默认的事件对象,如果被覆盖,可以手动传递应该$event
参数.
xml<button @click="add(n,$event)"></button>
methods:{
add(n,e){
}
}
vue提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的5个事件修饰符如下:
事件修饰符 | 说明 |
---|---|
.prevent | 阻止默认行为(例如:阻止a 连接的跳转、阻止表单的提交等) |
.stop | 阻止事件冒泡 |
.capture | 以捕获模式触发当前的事件处理函数 |
.once | 绑定的事件只触发1次 |
.self | 只有在event.target 是当前元素自身时触发事件处理函数 |
语法:
xml<button @click.prevent="add"></button>
在监听键盘事件时,我们经常需要判断详细的按键.此时,可以为键盘相关的事件添加按键修饰符
语法:
xml<button @keyup.enter="submit"></button>
<button @keyup.esc="back"></button>
vue
提供了v-model
双向数据绑定指令,用来辅助开发者在不操作DOM
的前提下,快速获取表单的数据.
xml<input type="text" v-model="username"></input>
xml<select v-model="city">
<option value="">请选择</option>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">广州</option>
</select>
<input type="text" v-model="city">
修饰符 | 作用 | 示例 |
---|---|---|
.number | 自动将用户的输入值转为数值类型 | <input v-model.number="age"/> |
.trim | 自动过滤用户输入的首尾空白字符 | <input v-model.trim="msg"/> |
.lazy | 在“change”时而非“input”时更新 | <input v-model.lazy="msg"/> |
条件渲染指令用来辅助开发者按需控制DOM的显示与隐藏.条件渲染指令有如下两个,分别是:
v-if
v-show
语法:
html<p v-if="flag">这是v-if控制的元素</p>
<p v-show="flag">这是v-show控制的元素</p>
注意:
v-show
的原理是:动态为元素添加或移除display: none
样式,来实现元素的显示和隐藏
v-if
的原理是:每次动态创建或移除元素,实现元素的显示和隐藏
如果要频繁的切换元素的显示状态,用v-show
性能会更好
如果初始状态为false,而且后期也很可能不需要展示出来的,使用v-if
在绝大多数情况下,不用考虑性能,直接使用v-if
就行
v-else和v-else-if
v-else
指令
v-if
的else
块v-else-if
指令
v-if
的“else-if
块”,可以连续使用.注意:
v-else-if
和v-else
指令必须配合v-if
指令一起使用,否则它将不会被识别!代码演示:
xml<input type="text" v-model="score"/>
<div v-if="score>=90">优秀</div>
<div v-else-if="score>=80&&score<90">良好</div>
<div v-else-if="score>=60&&score<80">及格</div>
<div v-else-if="score>=0&&score<60">不及格</div>
vue
提供了v-for
列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for
指令需要使用item in items
形式的特殊语法,其中:
items
是待循环的数组item
是被循环的每一项代码演示:
xml<table class="table table-border table-hover table-striped"><!-- 使用bootstrap样式表 -->
<thead>
<th>索引</th>
<th>ID</th>
<th>姓名</th>
</thead>
<tbody>
<tr v-for="(item,index) in list" :key="item.id" :title="'项'+(index+1)">
<td>{{index+1}}</td>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
注意:
v-for
指令,那么就要绑定一个 :key
属性:key
属性只能是 字符串或者数字类型.key
值不可重复,否则终端会报错key
值不能使用index
(因为index
的值不具备唯一性)过滤器(Filters)是vue
为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方︰插值表达式和v-bind
属性绑定.
在最新Vue3
中 过滤器 已被废除
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作.
代码演示:
javascriptconst vm = new Vue({
el: '#app',
data:{
username:''
},
watch:{
//监听username值的变化
//newVal 是“变化后的新值”,oldVal是“变化之前的旧值”
username(newVal,oldVal){
console.log(newVal,oldVal)
}
}
})
方法格式侦听器:
javascriptusername(newVal,oldVal){
console.log(newVal,oldVal)
}
对象格式侦听器:
javascriptusername{
hander(newVal,oldVal){
console.log(newVal,oldVal);
},
//immediate 选项默认值是 false
//immediate 的作用是 控制侦听器是否自动触发一次
immediate:true,
}
immediate
选项自动触发deep
选项侦听对象中每一个属性的变化注意:
如果要侦听的是对象的子属性的变化,必须包裹一层单引号
'info.username'(newVal){}
计算属性指的是通过一系列运算之后,最终得到一个属性值。
这个动态计算出来的属性值可以被模板结构或methods
方法使用.
代码演示:
html<div class="bg" :style="{background:rgb}">{{rgb}}</div>
<script>
const vm = new Vue({
el: '.app',
data: {
r:0,
g:0,
b:0,
},
computed:{
rgb(){
return `rgb(${this.r},${this.g},${this.b})`
}
}
});
</script>
注意:
{}
插入想要的数据${}
传递数据(ES6语法)特点:
axios
是一个专注于网络请求的库中文官网:
https://axios-http.com/zh/docs/intro
语法:
javascriptaxios({
method:'请求的类型",
url: '请求的 URL地址',
//url中的查询参数
params:{
id:1
}
}).then((result) => {
//.then用来指定请求成功之后的回调函数
//形参中的 result是请求成功之后的结果
})
返回值:
Promise
实例注意:
使用url
时应注意url
的协议
result
并不是服务器返回的真实数据,result.data
中的才是
axios
在请求到数据之后,在真正的数据之外,还套用了一层壳
代码演示:
html
vue-cli
是Vue.js
开发的标准工具。它简化了程序员基于webpack
创建工程化的Vue
项目的过程
中文官网: [https://cli.vuejs.org/zh/]:
vue-cli
是npm
上的一个全局包,使用npm install
命令,即可方便的把它安装到自己的电脑上
npm install -g @vue/cli
使用步骤:
vue create 项目的名称
**注意:**如果出现脚本无法运行的情况,请在管理员权限下的powershell
中运行以下命令set-ExecutionPolicy RemoteSigned
选择Y,即可执行脚本
在出现的选择界面选择指定配置项Manually select features
配置界面中选择以下项目:babel
,CSS Pre-processors
在选择Vue
版本界面选择 Vue 2.x
版本
在样式表选择界面选择Less
配置项安放位置选择默认 In dedicated config files
独立文件中
设置 配置项 是否设为预设
设置 预设 名称
vue-cli 自动创建项目
cd .\项目名目录\
npm run serve
使用浏览器访问http://localhost:8080/
(默认为该地址)
components
目录下main.js
vue
是一个支持组件化开发的前端框架.
vue
中规定:组件的后缀名是.vue
.
javascriptimport 组件 from './组件名.vue'
new Vue({
el:'#app', //index.html中指定的div元素
render:h=>h(组件名),
}).$mount('#app') //与el属性完全相同
每个.vue
组件都由3部分构成,分别是:
template
->组件的模板结构script
->组件的JavaScript
行为style
->组件的样式代码演示:
html<template>
<!-- template 标签下只能有一个根节点 -->
<div class="test-box">
<h3>这是用户自定义的Vue组件---{{username}}</h3>
</div>
</template>
<script>
//默认导出
export default{
data() {
return {
username:'admin',
}
},
}
</script>
<style lang="less">
/* 使用less语法 */
div{
background-color: pink;
}
</style>
注意:
data
不能指向对象data
必须是一个函数template
标签下只能有一个根节点使用import
语法导入需要的组件
使用components
节点注册组件
以标签形式使用刚才注册的组件
代码演示:
html<template>
<div class="app-container">
<h1>Vue根节点</h1>
<hr/>
<!-- 使用组件标签 -->
<Left></Left>
</div>
</template>
<script>
//导入vue组件
import left from '@/components/left.vue'
//默认导出
export default {
//注册组件
//在components节点下注册的组件为私有组件
components:{
'Left':left,
}
};
</script>
<style lang="less">
.app-container{
background-color: rgb(230,230,230);
}
</style>
在vue
项目的main.js
入口文件中,通过Vue.component()
方法,可以注册全局组件.
代码演示:
javascriptimport left from '@/components/left.vue'
Vue.component('Left',left)
props
是组件的自定义属性,在封装通用组件的时候,合理地使用props
可以极大的提高组件的复用性
props
中的数据,可以直接在模板结构中使用
代码演示:
html<component init="6"></component>
props:['init'] //'init为自定义属性名'
注意:
props
属性需要在组件标签内定义<p init="6"></p>
属性中的值为字符串型v-bind
绑定属性后,属性值为数字型props
是只读的,如果后期需要修改,应该将props
中的值转存到 data
中在声明自定义属性时,可以通过default
来定义属性的默认值。
代码演示:
javascriptprops:{
init:{
default:0,
}
}
在声明自定义属性时,可以通过type
来定义属性的值类型。
代码演示:
javascriptprops:{
init:{
default:0,
type:Number,
}
}
代码演示:
javascriptprops:{
init:{
default:1,
type:Number,
required:true,
}
}
默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题
导致组件之间样式冲突的根本原因是︰
解决方法:
scoped
属性语法:
<style scoped></style>
原理:
style
包含的元素标签内自动添加一个data-v-xxx
属性style
样式中多了一个data-v-xxx
的属性选择器语法:
/deep/ element{样式表}
作用: 在父组件中修改子组件样式,当使用第三方UI库时,如果有修改组件默认样式的需求,需要使用/deep/
组件的实例化过程:
Vue
组件需要在使用标签引用后才会真正实例化,不使用标签则不实例化生命周期(Life Cycle)
Init Event & Lifecycle
beforeCreate
props data methods
尚未被创建,都处于不可用状态Init injections & reactivity
props data methdos
created
props data methods
已创建好,都处于可用的状态,但是组件的模板结构尚未生成beforeMount
Create vm.$el and replace "el" with it
HTML
结构,替换掉el
属性指定的DOM
元素.mounted
HTML
结构,成功的渲染到了浏览器之中。此时浏览器中已然包含了当前组件的DOM
结构.beforeUpdate
Virtual DOM re-render and patch
DOM
结构Updated
DOM
结构的重新渲染.beforeDestroy
Teardown watchers ,child components and event listeners
destroyed
DOM
结构已被完全移除生命周期函数:
vue
框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。函数 | 描述 |
---|---|
beforeCreate() | 组件中的props data methods 尚未被创建,都处于不可用状态 |
created() | 组件的props data methods 已创建好,都处于可用的状态,但是组件的模板结构尚未生成 |
beforeMount() | 将要把内存中编译好的HTML结构渲染到浏览器中。此时浏览器中还没有当前组件的DOM结构. |
mounted() | 已经把内存中的HTML 结构,成功的渲染到了浏览器之中。此时浏览器中已然包含了当前组件的DOM 结构. |
beforeUpdate() | 将要根据变化过后,最新的数据,重新渲染组件的模板结构 |
Updated() | 已经根据最新的数据,完成了组件DOM 结构的重新渲染. |
beforeDestroy() | 将要销毁此组件,此时尚未销毁 |
destroyed() | 组件已经被销毁,此组件在浏览器中对应的DOM 结构已被完全移除 |
代码演示:
javascript<script>
//默认导出
export default {
beforeCreate:{
},
created:{
},
};
</script>
组件之间常见的关系:
父组件向子组件共享数据需要使用自定义属性
代码演示:
html//父组件
<father :msg="message" :user="userinfo"></father>
data(){
return{
message:'Vue father',
userinfo:{name:'zs',age:18}
}
}
html//子组件
<template>
<div>
<h5>Son组件</h5>
<p>父组件传递的 msg 值{{msg}}</p>
<p>父组件传递的 user 值{{user}}</p>
</div>
</template>
props:['msg','user']
注意:
props
中子组件向父组件共享数据需要使用自定义属性
代码演示:
java//子组件
export default {
data(){
return { count: 0}
},
methods:{
add(){
this.count += 1
//修改数据时,通过 $emit()触发自定义事件
this.$emit('numnchamge',this.count)
}
}
}
javascript//父组件
<Son@numchange="getNewCount"></Son>
export default {
data(){
return { countFromSon: 0 }
},
methods:{
getNewCount(val){
this.countFromSon = val
}
}
}
在vue2.x
中,兄弟组件之间数据共享的方案是EventBus
.
EventBus的使用步骤
eventBus.js
模块,并向外共享一个Vue
的实例对象bus.$emit
('事件名称',要发送的数据)方法触发自定义事件bus.$on
('事件名称',事件处理函数)方法注册一个自定义事件代码演示:
javascriptimport bus from './eventBus.js'
//兄弟组件A(数据发送方)
export default {
data(){
return{
msg:'hello vue.js'
}
},
methods:{
sendMsg(){
bus.$emit('share',this.msg)
}
}
}
javascriptimport Vue from 'vue'
//eventBus.js
//向外共享Vue的实例对象
export default new Vue()
javascriptimport bus from './eventBus.js'
//兄弟组件B(数据接收方)
export defaul{
data(){
return{
msgFromLeft:'',
}
},
created(){
bus.$on('share',val=>{
this.msgFromLeft = val
})
}
}
ref
用来辅助开发者在不依赖于jQuery的情况下,获取DOM
元素或组件的引用。
每个vue
的组件实例上,都包含一个$refs
对象,里面存储着对应的DOM
元素或组件的引用。默认情况下,组件的$refs
指向一个空对象.
代码演示:
html<h3 ref="x1">count组件</h3>
<script>
this.$refs.x1.style.color='red'
</script>
代码演示:
html<component ref="x1">count组件</component>
<script>
this.$refs.x1.count = 0;
</script>
组件的$nextTick(callback)
方法,会把callback
回调推迟到下一个DOM
更新周期之后执行.
**作用:**使回调函数可以操作到最新的DOM
元素
代码演示:
javascriptthis.$nextTick(()=>{
this.$refs.com.func()
})
**注意:**不能使用update()
函数,因为组件数据更新时会触发update()
,而不是组件DOM
更新时触发.
**特点:**在找到元素后跳出循环
代码演示:
javascriptconst arr=['23','31','23','234'];
arr.some((item,index)=>{
if(item=='31')
{
console.log(item);
return true;
}
})
**特点:**全部为某个值是结果才为某个值
代码演示:
javascriptconst result = arr.every(item =>item.state===true)
**特点:**循环累加
代码演示:
javascriptconst result = arr.filter(item =>item.state).reduce((累加的结果,当前循环项)=>{
return amt += item.price * item.count
},初始值)
javascript//reduce简写
const result = arr.filter(item =>item.state).reduce((累加的结果,当前循环项)=>amt += item.price * item.count,初始值)
动态组件:
vue
提供了一个内置的<component>
组件,专门用来实现动态组件的渲染.
语法:
<component is="test"></component>
静态绑定<component :is="test"></component>
动态绑定默认情况下,切换动态组件时无法保持组件的状态.此时可以使用vue
内置的<keep-alive>
组件保持动态组件的状态.
语法:
<keep-alive><component :is="test"></component></keep-alive>
函数 | 描述 |
---|---|
deactivated | 当组件被缓存时,会自动触发组件的deactivated 生命周期函数. |
activated | 当组件被激活时,会自动触发组件的activated 生命周期函数. |
include
属性用来指定:只有名称匹配的组件会被缓存.
exclude
属性用来指定:那些组件不会被缓存.
多个组件名之间使用英文的逗号分隔
html<keep-alive include="MyLeft,MyRight">
<component :is="comName"></component>
</keep-alive>
组件注册名称:
xml<header></header>
<body></body>
<footer></footer>
components: {
header,
body,
footer,
}
组件声明名称name:
xml<keep-alive include="MyLeft,MyRight"></keep-alive>
//Left.vue
export default {
name:'MyLeft',
}
主要作用:
<keep-alive>
标签实现组件缓存的功能,以及在调试工具中查看组件的 name
名称注意:
name
内名称首字母应该大写插槽(Slot)是vue
为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽.
代码演示:
xml<!-- component.vue -->
<slot name="default"></slot><!-- 接收App.vue传递的组件和标签 -->
<!-- App.vue -->
<component>
<template v-slot:default>
<div>向组件传递的一个div标签</div>
</template>
</component>
语法:
v-slot
->#
封装组件时,可以为预留的<slot>
插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效.
代码演示:
xml<slot><h1>这是slot标签的后背内容</h1></slot>
代码演示:
xml<template #content="obj">
<h1>{{obj}}</h1>
</template>
<!-- component.vue -->
<slot name="content" msg="hello world"></slot>
vue中的自定义指令:
私有自定义指令
全局自定义指令
在每个vue
组件中,可以在directives
节点下声明私有自定义指令
javascriptdirectives:{
color:{
//为绑定到的 HTML 元素设置颜色
bind(el,binding){
//形参中的el是绑定了此指令的,原生的DOM对象
el.style.color = binding.value;
}
}
}
bind
函数只调用一次:当指令第一次绑定到元素时调用,当DOM更新时bind
函数不会被触发.update
函数会在每次DOM更新时被调用.
javascriptdirectives:{
color:{
//每次 DOM 更新时被调用
update(el,binding){
el.style.color = binding.value;
}
}
}
javascriptdirectives:{
color(el,binding):{
el.style.color = binding.value;
}
}
全局共享的自定义指令需要通过Vue.directive()
进行在main.js
文件中声明
javascriptVue.directive('color',function(el,binding){
el.style.color = binding.value;
})
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:
Vue 提供了 transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
v-if
)v-show
)html<!-- html -->
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
javascript//Javascript
new Vue({
el: '#demo',
data: {
show: true
}
})
css/*CSS*/
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
注意:
transition
的name
属性类名 | 描述 |
---|---|
v-enter | 定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 |
v-enter-active | 定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。 |
v-enter-to | 2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。 |
v-leave | 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。 |
v-leave-active | 定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。 |
v-leave-to | 2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。 |
路由(router)是对应关系
即Hash 地址与组件之间的对应关系
vue-router
是vue.js
官方给出的路由解决方案。它只能结合vue
项目进行使用,能够轻松的管理SPA项目中组件的切换.
vue-router
的官方文档地址: [https://router.vuejs.org/zh/]:
vue-router
包安装vue-router包
npm i vue-router@3.5.2 -S
在src
源代码目录下,新建router/index.js
路由模块,并初始化如下的代码
javascript//1.导入 Vue和 VueRouter 的包
import Vue from 'vue '
import VueRouter from 'vue-router'
//2.调用Vue.use()函数,把 VueRouter安装为Vue的插件
Vue.use(VueRouter)
//3.创建路由的实例对象
const router = new VueRouter({
routes:[]
})
//4.向外共享路由的实例对象
export default router
在main.js
中导入并挂载路由模块
javascriptimport router from '@/router/index.js'
new Vue({
router:router
})
声明路由链接和占位符
javascript// @/router/index.js
//导入的组件
import home from '@/component/home.vue'
import movie from '@/component/movie.vue'
import about from '@/component/about.vue'
const router = new VueRouter({
routes:[
{path: '/home',component: home},
{path: '/movie',component: movie},
{path: '/about',component: about},
]
})
xml//占位符
<router-view></router-view>
语法:
xml<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
路由重定向:
redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向javascriptconst router =new VueRouter({
routes: [
{path:'/',redirect: '/home'},
{path: '/home',component: home},
{path: '/movie',component: movie},
{path: '/about',component: about},
]
})
通过路由实现组件的嵌套展示,叫做嵌套路由.
xml<!-- about.vue 下 -->
<router-link to="/about/tap1">tap1 </router-link>
<router-link to="/about/tap2">tap2</router-link>
<router-view></router-view>
使用 children
属性声明子路由规则
javascript//在/router/index.js下
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: home },
{ path: '/movie', component: movie },
{
path: '/about',
component: about,
children: [
{path:'/',redirect:'tap1'},
{ path: 'tap1', component: tap1 },
{ path: 'tap2', component: tap2 },
]
},
]
})
默认子路由:
children
组中,某个路由规则的path
值为空字
符串,则这条路由规则,叫做**“默认子路由”**javascriptchildren: [
{ path: '', component: tap1 }, //默认子路由
{ path: 'tap2', component: tap2 },
]
动态路由:把 Hash
地址中可变的部分定义为参数项,从而提高路由规则的复用性.
在vue-router
中使用英文的冒号(:
)来定义路由的参数项
{path: '/movie/:id', component: movie}
在子组件中获得路由的参数项:
this.$route.params.id
路由规则
javascript{path: ':id',component: movie, props: true}
接收props
javascriptprops:['id']
在路由“参数对象”中,需要使用this.$route.query
来访问查询参数
在this.$route
中,path
只是路径部分;fullPath
是完整的地址
在浏览器中,点击链接实现导航的方式,叫做声明式导航.
<a>
链接、vue
项目中点击<router-link>
都属于声明式导航在浏览器中,调用API方法实现导航的方式,叫做编程式导航.
location.href
跳转到新页面的方式,属于编程式导航vue-router提供了许多编程式导航的API,其中最常用的导航API分别是:
函数 | 描述 |
---|---|
this.$router.push('hash地址') | 跳转到指定hash 地址,并增加一条历史记录 |
this.$router.replace('hash地址') | 跳转到指定hash 地址,并替换掉当前的历史记录 |
this.$router.go(数值 n) | 前进或者后退 n 个历史记录 |
this.$router.forward() | 前进一个历史记录 |
this.$router.back() | 后退一个历史记录 |
导航守卫可以控制路由的访问权限.
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:
javascriptrouter.beforeEach((to,from,next) =>{
//to是将要访问的路由的信息对象
//from是将要离开的路由的信息对象
//next是一个函数,调用next()表示放行,允许这次路由导航
})
只要发生了路由的跳转,必然会触发beforeEach
指定的function
回调函数
方式 | 描述 |
---|---|
next() | 当前用户拥有后台主页的访问权限,直接放行 |
next('/login') | 当前用户没有后台主页的访问权限,强制其跳转到登录页面 |
next(false) | 当前用户没有后台主页的访问权限,不允许跳转到后台主页 |
要拿到用户将要访问的hash
地址
判断hash
地址是否等于/main
.
/main
,证明需要登录之后,才能访问成功/main
,则不需要登录,直接放行next()
如果访问的地址是/main
.则需要读取localStorage
中的token
值
token
,则放行token
,则强制跳转到/login
登录页javascriptrouter.beforeEach((to,from,next) =>{
if(to.path === '/main'){
const token = localStorage.getItem('token')
if (token){
next() //访问的是后台主页,且有token的值
}else {
next('/login')//访问的是后台主页,但是没有 token的值
}
}else{
next()//访问的不是后台主页,直接放行
}
})
vue<script> export default { data(){ return { count:0 } }, methods:{ addCount(){ this.count++ } } } </script>
vue<script setup> import { ref } from 'vue' const count = ref(0) const addCount = ()=> count.value++ </script>
特点:
create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应
前置条件 - 已安装16.0或更高版本的Node.js
执行如下命令,这一指令将会安装并执行 create-vue
bashnpm init vue@latest
熟悉项目和关键文件
写法
vue<script> export default { setup(){ }, beforeCreate(){ } } </script>
执行时机
在beforeCreate钩子之前执行
在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用
vue<script> export default { setup(){ const message = 'this is message' const logMessage = ()=>{ console.log(message) } // 必须return才可以 return { message, logMessage } } } </script>
<script setup>
语法糖script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句
vue<script setup> const message = 'this is message' const logMessage = ()=>{ console.log(message) } </script>
接受对象类型数据的参数传入并返回一个响应式的对象
vue<script setup> // 导入 import { reactive } from 'vue' // 执行函数 传入参数 变量接收 const state = reactive({ msg:'this is msg' }) const setSate = ()=>{ // 修改数据更新视图 state.msg = 'this is new msg' } </script> <template> {{ state.msg }} <button @click="setState">change msg</button> </template>
接收简单类型或者对象类型的数据传入并返回一个响应式的对象
vue<script setup> // 导入 import { ref } from 'vue' // 执行函数 传入参数 变量接收 const count = ref(0) const setCount = ()=>{ // 修改数据更新视图必须加上.value count.value++ } </script> <template> <button @click="setCount">{{count}}</button> </template>
计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法
vue<script setup> // 导入 import {ref, computed } from 'vue' // 原始数据 const count = ref(0) // 计算属性 const doubleCount = computed(()=>count.value * 2) // 原始数据 const list = ref([1,2,3,4,5,6,7,8]) // 计算属性list const filterList = computed(item=>item > 2) </script>
侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate控制立刻执行,deep开启深度侦听
vue<script setup> // 1. 导入watch import { ref, watch } from 'vue' const count = ref(0) // 2. 调用watch 侦听变化 watch(count, (newValue, oldValue)=>{ console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`) }) </script>
侦听多个数据,第一个参数可以改写成数组的写法
vue<script setup> // 1. 导入watch import { ref, watch } from 'vue' const count = ref(0) const name = ref('cp') // 2. 调用watch 侦听变化 watch([count, name], ([newCount, newName],[oldCount,oldName])=>{ console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName]) }) </script>
在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调
vue<script setup> // 1. 导入watch import { ref, watch } from 'vue' const count = ref(0) // 2. 调用watch 侦听变化 watch(count, (newValue, oldValue)=>{ console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`) },{ immediate: true }) </script>
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep
vue<script setup> // 1. 导入watch import { ref, watch } from 'vue' const state = ref({ count: 0 }) // 2. 监听对象state watch(state, ()=>{ console.log('数据变化了') }) const changeStateByCount = ()=>{ // 直接修改不会引发回调执行 state.value.count++ } </script> <script setup> // 1. 导入watch import { ref, watch } from 'vue' const state = ref({ count: 0 }) // 2. 监听对象state 并开启deep watch(state, ()=>{ console.log('数据变化了') },{deep:true}) const changeStateByCount = ()=>{ // 此时修改可以触发回调 state.value.count++ } </script>
- 导入生命周期函数
- 执行生命周期函数,传入回调
vue<scirpt setup> import { onMounted } from 'vue' onMounted(()=>{ // 自定义逻辑 }) </script>
生命周期函数执行多次的时候,会按照顺序依次执行
vue<scirpt setup> import { onMounted } from 'vue' onMounted(()=>{ // 自定义逻辑 }) onMounted(()=>{ // 自定义逻辑 }) </script>
基本思想
- 父组件中给子组件绑定属性
- 子组件内部通过props选项接收数据
基本思想
- 父组件中给子组件标签通过@绑定事件
- 子组件内部通过 emit 方法触发事件
概念:通过 ref标识 获取真实的 dom对象或者组件实例对象
实现步骤:
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
默认情况下在
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
实现步骤
- 顶层组件通过
provide
函数提供数据- 底层组件通过
inject
函数提供数据
在调用provide函数时,第二个参数设置为ref对象
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
背景说明:
有 <script setup>
之前,如果要定义 props
, emits
可以轻而易举地添加一个与setup
平级的属性。
但是用了 <script setup>
后,就没法这么干了 setup
属性已经没有了,自然无法添加与其平级的属性。
为了解决这一问题,引入了 defineProps
与 defineEmits
这两个宏。但这只解决了 props
与 emits
这两个属性。
如果我们要定义组件的 name
或其他自定义的属性,还是得回到最原始的用法——再添加一个普通的 <script>
标签。
这样就会存在两个 <script>
标签。让人无法接受。
所以在 Vue 3.3 中新引入了 defineOptions
宏。顾名思义,主要是用来定义 Options API
的选项。可以用 defineOptions
定义任意的选项, props
, emits
, expose
, slots
除外(因为这些可以使用 defineXXX 来做到)
在Vue3中,自定义组件上使用v-model
, 相当于传递一个modelValue
属性,同时触发 update:modelValue
事件
我们需要先定义 props
,再定义 emits
。其中有许多重复的代码。如果需要修改此值,还需要手动调用 emit
函数。
于是乎 defineModel
诞生了。
生效需要配置 vite.config.js
jsximport { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true
}
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
单页面应用程序(英文名: Single Page Application)简称SPA
,顾名思义,指的是一个 Web
网站中只有唯一的一个HTML
页面,所有的功能与交互都在这唯一的一个页面内完成.
特点:
单页面应用程序将所有的功能局限于一个web
页面中,仅在该web
页面初始化时加载相应的资源(HTML
、JavaScript
和CSS
).
一旦页面加载完成了,SPA
不会因为用户的操作而进行页面的重新加载或跳转。而是利用JavaScript
动态地变换HTML
的内容,从而实现页面与用户的交互.
良好的交互体验
单页应用的内容的改变不需要重新加载整个页面
获取数据也是通过Ajax异步获取
没有页面之间的跳转,不会出现“白屏现象”
良好的前后端工作分离模式
减轻服务器的压力
解决方案1:
解决方案2:
vue官方提供了两种快速创建工程化的SPA项目的方式:
vite
创建SPA项目vue-cli
创建SPA项目vite | vue-cli | |
---|---|---|
支持的vue 版本 | 仅支持vue3.x | 支持3.x 和2.x |
是否基于webpack | 否 | 是 |
运行速度 | 快 | 较慢 |
功能完整度 | 小而巧(逐渐完善) | 大而全 |
是否建议在企业级开发中使用 | 目前不建议 | 建议在企业级开发中使用 |
javascriptnpm init vite-app 项目名称 cd 项目名称 npm install npm run dev
在实际开发中,前端开发者可以把自己封装的.vue
组件整理、打包、并发布为npm
的包,从而供其他人下载和使用。这种可以直接下载并在项目中使用的现成组件,就叫做 vue
组件库.
二者之间存在本质的区别:
bootstrap
只提供了纯粹的原材料(css样式、HTML结构以及JS特效),需要由开发者做进一步的组装和改造
vue
组件库是遵循vue
语法、高度定制的现成组件,开箱即用
PC端
Element Ul ( https://element.eleme.cn/#/zh-CN)
View Ul ( http://v1.iviewui.com/)
移动端
Element UI
是饿了么前端团队开源的一套PC端vue组件库。支持在vue2
和vue3
的项目中使用:
vue2
的项目使用旧版的Element UI( https://element.eleme.cn/#/zh-CN )
vue3
的项目使用新版的Element Plus (https://element-plus.gitee.io/#/zh-CN)
安装
npm i element-ui -S
引入 element ui
开发者可以一次性完整引入所有的element-ui
组件,或是根据需求,只按需引入用到的element-ui
组件:
完整引入
javascript//1. 引入组件
import ElementUI from 'element-ui'
//2. 引入样式
import 'element-ui/lib/theme-chalk/index.css'
//3. 把ElementUI 注册为 vue 的插件
//注册后即可在每个组件中直接使用element ui 的组件
Vue.use(ElementUI)
按需引入
借助 babel-plugin-component
,我们可以只引入需要的组件,以达到减小项目体积的目的。
babel-plugin-component
npm install babel-plugin-component -D
babel.config.js
配置文件,新增plugins
节点如下:javascriptmodule.exports = {
presets: [ '@vue/cli-plugin-babel/preset' ],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk',
},
],
]
}
Button
,那么需要在main.js
中写入以下内容:javascriptimport {Button} from 'element-ui'
Vue.component(Button.name,Button)
//或 Vue.use(Button)
步骤
src
目录下新建element-ui/index.js
模块,并声明如下的代码:javascript//→模块路径 /src/element-ui/index.js
import Vue from 'vue'
//按需导入 element ui 的组件
import { Button,Input } from 'element-ui'
/注册需要的组件
vue.use(Button)
Vue.use(Input)
//在main.js中导入
import './element-ui'
Vant 是一个轻量、可定制的移动端组件库,于 2017 年开源。
安装:
npm i vant@next -S
全部引入
javascript// 1. 引入你需要的组件
import Vant from 'vant';
// 2. 引入组件样式
import 'vant/lib/index.css';
const Vue = createApp();
// 3. 注册你需要的组件
Vue.use(Vant);
安装axios
javascriptnpm install axios@0.21.1 -S
需要在main.js
入口文件中,通过Vue
构造函数的prototype
原型对象全局配置 axios
:
javascript//1. 导入 axios
import axios from 'axios'
//2. 配置请求根路径
axios.defaults.baseURL = 'https://www.escook.cn'
//3. 通过 Vue 构造函数的原型对象,全局配置 axios
Vue.prototype.$http = axios
javascript//在App.vue组件中发起axios请求
methods:{
async getInfo(){
const {data: res} = await this.$http.get('/api/get',{params:{name: 'zs',age:20}})
console.log(res)
}
}
拦截器(英文: Interceptors)会在每次发起ajax请求和得到响应的时候自动被触发。
通过axios.interceptors.request.use(成功的回调,失败的回调)
可以配置请求拦截器.
javascriptaxios.interceptors.request.use( function (config){
//Do something before request is sent
return config;
}, function (error){
//Do something with request error
return Promise.reject(error);
})
注意:失败的回调函数可以被省略
javascriptimport axios from 'axios"
axios.defaults.baseURL = "https://www.escook.cn'
//配置请求的拦截器
axios.interceptors.request.use(config =>{
//为当前请求配置Token认证字段
config.headers.Authorization = 'Bearer xxx'
return config
})
Vue.prototype.$http = axios
借助于element ui
提供的Loading
效果组件(https://element.eleme.cn/#lzh-CN/component/loading)可以方便的实现`Loading`效果的展示:
javascript//1.按需导入 loading效果组件
import { Loading } from 'element-ui'
//2.声明支量,用来存储Loading组件的实例对象
let loadingInstance = null
//配置请求的拦截器
axios.interceptors.request.use(config =>{
//3.调用loading组件的 service()方法,创建loading组件的实例,并全屏展示 loading效果
loadingInstance = Loading.service({ fullscreen: true })
return config
})
通过axios.interceptors.response.use(成功的回调,失败的回调)
可以配置响应拦截器。
javascript//配置响应拦截器
axios.interceptors.response.use(function (response) {
//关闭 loading 效果
return response;
},function (error) {
return Promise.reject(error);
});
注意:失败的回调函数可以被省略
由于当前的API接口没有开启CORS跨域资源共享,因此默认情况下,上面的接口无法请求成功!
通过vue-cli
创建的项目在遇到接口跨域问题时,可以通过代理的方式来解决:
把axios 的请求根路径设置为vue项目的运行地址(接口请求不再跨域)
vue项目发现请求的接口不存在,把请求转交给proxy 代理
代理把请求根路径替换为devServer.proxy
属性的值,发起真正的数据请求
代理把请求到的数据,转发给axios
main.js
入口文件中,把** axios
的请求根路径改造为当前web
项目的根路径**:vue.config.js
的配置文件,并声明如下的配置;javascriptmodule.exports = {
devServer:{
//当前项目在开发调试阶段,
//会将任何未知请求(没有匹配到静态文件的请求)代理到https://ww.escook.cn
proxy:'https://www.escook.cn',
},
}
注意:
devServer.proxy
提供的代理功能,仅在开发调试阶段生效Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享。
在最新的vue3中,全局状态管理工具已经替换为了pinia,不再推荐使用vuex
使用vuex统一管理状态的好处
一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;对于组件中的私有数据,依旧存储在组件自身的data中即可。
bashnpm install vuex --save
jsimport Vuex from 'vuex'
Vue.use(Vuex)
jsconst store = new Vuex.store({
// state 中存放的就是全局共享的数据
state: {count: 0}
})
jsnew vue({
el: '#app',
render: h => h(app),
router,
store
})
State提供唯一的公共数据源,所有共享的数据都要统一放到Store的State中进行存储。
jsconst store = new Vuex.Store({
state: {count: 0}
})
组件访问State中数据
js// 第一种方式
this.$store.state.全局数据名称
// 第二种方式
// 1. 从vuex中按需导入mapState函数
import { mapState } from 'vuex'
// 2. 将全局数据,映射为当前组件的计算computed属性
computed: {
...mapState (['count'])
}
Mutation用于变更Store中的数据。
jsconst store = new Vuex.Store({
state: {
count: 0
},
mutation: {
add(state,step) {
// 变更状态
state.count += step
}
}
})
js// 触发mutation
methods: {
handle1 () {
// 触发mutations 的第一种方式
this.$store.commit('add',step)
}
}
js// 触发mutation的第二种方式
import { mapMutations } from 'vuex'
methods: {
...mapMutation(['add','sub']),
handler(){
this.sub()
}
}
Action用于处理异步任务。
如果通过异步操作变更数据,必须通过Action,而不能使用Mutation,但是在Action中还是要通过触发Mutation 的方式间接变更数据。
jsconst store = new Vuex.Store({
// ...
mutations: {
add(state) {
state.count++
}
},
actions: {
addAsync(context) {
setTimeout(() => {
// Action中不能直接修改state的数据
context.commit('add')
},1000)
}
}
})
js// 触发Action
methods: {
handle() {
// 第一种方式,dispatch用于触发Action
this.$store.dispatch('addAsync')
}
}
触发actions异步任务时携带参数
jsconst store = new Vuex.Store({
// ...
mutations: {
addN(state,step) {
state.count += step
}
},
actions: {
addNAsync(context,step) {
setTimeout(() => {
context.commit('addN',step)
},1000)
}
}
})
触发actions的第二种方式
js// 1. 导入mapActions函数
import { mapActions } from 'vuex'
// 2. 将指定的actions函数,映射为当前组件的methods函数
methods: {
...mapActions(['addAsync','addNAsync']),
handler() {
this.addAsync()
}
}
Getter用于对 Store中的数据进行加工处理形成新的数据。
jsconst store = new Vuex.Store({
state: {
count: 0
},
getters: {
showNum: state => {
return '当前最新的数量是['+ state.count +']'
}
}
})
访问getter的第一种方式
jsthis.$store.getters.名称
访问getters的第二种方式
jsimport { mapGetters } from 'vuex'
computed: {
...mapGetters(['showNum'])
}
bashvue create vuex-todos
bashnpm i vuex axios ant-design-vue@1.7.8 npm i eslint less -D
js// prettierrc.js
module.exports = {
printWidth: 200, // 200个字符每行
tabWidth: 2,
semi: false, // 不使用分号
singleQuote: true, // 使用单引号
trailingComma: 'none', // 不使用尾逗号,eslint会报错
bracketSpacing: true, // 在对象字面量声明所使用的的花括号后({)和前(})输出空格
insertSpaceBeforeFunctionParenthesis: true, // 在函数的左括号之前输出一个空格
}
js// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'plugin:vue/vue3-essential',
'standard',
'eslint:recommended'
],
overrides: [
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: [
'vue'
],
rules: {
}
}
组件或者视图通过store访问数据,可以实现异步访问(Actions)和数据加工(Getter)的操作
js// main.js
// 导入Vue
import Vue from 'vue'
import App from './App.vue'
// 导入store
import store from '@/store'
// 导入ant-design-vue
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
Vue.config.productionTip = false
// 使用ant-design-vue
Vue.use(Antd)
// 创建一个Vue实例,挂载到#app上
new Vue({
store,
render: (h) => h(App)
}).$mount('#app')
vue<template> <div id="app"> <h1>{{ title }}</h1> <div class="row"> <a-input class="input" placeholder="请输入任务" :value="$store.state.inputValue" @change="ValueChangeHandler" /> <a-button type="primary" class="button" @click="addItemToList">添加</a-button> </div> <a-list class="list" bordered> <a-list-item v-for="list in this.$store.getters.viewList" :key="list.index"> <a-checkbox :checked="list.status" @click="cbStatusChange(list.index)"></a-checkbox> <a-list-item-meta :title="list.info" /> <a @click="removeItemById(list.index)">删除</a> </a-list-item> <div class="footer"> <span>{{ this.$store.getters.unDoneLength }}条未完成任务</span> <a-button-group> <a-button :type="this.$store.state.viewKey === 'all' ? 'primary' : 'default'" @click="changeList('all')">全部</a-button> <a-button :type="this.$store.state.viewKey === 'undone' ? 'primary' : 'default'" @click="changeList('undone')">未完成</a-button> <a-button :type="this.$store.state.viewKey === 'done' ? 'primary' : 'default'" @click="changeList('done')">已完成</a-button> </a-button-group> <a @click="cleanDone">清除已完成</a> </div> </a-list> </div> </template> <script> export default { name: 'App', data () { return { title: '待办事项' } }, created () { // 使用异步获取数据 this.$store.dispatch('getList') }, methods: { // 改变输入框的值 ValueChangeHandler (e) { this.$store.commit('setInputValue', e.target.value) }, // 添加任务 addItemToList () { if (this.$store.state.inputValue.length <= 0) { // 如果输入框为空,提示用户 this.$message.error('请输入任务') } else { this.$store.commit('addItemToList', this.$store.state.inputValue.trim()) } }, // 删除任务 removeItemById (index) { this.$store.commit('removeItemById', index) }, // 改变任务状态 cbStatusChange (index) { this.$store.commit('itemStatusChangeById', index) }, // 清除已完成 cleanDone () { this.$store.commit('cleanDone') }, // 改变列表 changeList (key) { this.$store.commit('changeViewList', key) } } } </script> <style lang="less" scoped> #app { margin: 0 auto; width: 500px; height: 650px; .row { display: flex; align-items: center; } .footer { display: flex; justify-content: space-around; align-items: center; } } </style>
js// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 导出一个store对象
export default new Vuex.Store({
state: {
lists: [], // 列表数据
inputValue: 'input', // input的值
nextId: 0, // 下一个id
viewKey: 'all' // 显示列表的状态
},
mutations: {
// 初始化列表数据
initList (state, list) {
state.lists = list
state.nextId = state.lists.length // 初始化nextId为lists的长度
},
// 改变input的值
setInputValue (state, value) {
state.inputValue = value
},
// 添加任务
addItemToList (state, value) {
const obj = {
index: state.nextId++,
info: value,
status: false
}
state.lists.push(obj)
},
// 通过id删除任务
removeItemById (state, id) {
const i = state.lists.findIndex((x) => x.index === id)
if (i !== -1) {
state.lists.splice(i, 1)
}
},
// 通过id改变任务的状态
itemStatusChangeById (state, id) {
const i = state.lists.findIndex((x) => x.index === id)
if (i !== -1) {
state.lists[i].status = !state.lists[i].status
}
},
// 清除已完成的任务
cleanDone (state) {
// 直接赋值会导致视图不更新,所以需要使用splice方法
for (let i = 0; i < state.lists.length; i++) {
if (state.lists[i].status) {
state.lists.splice(i, 1)
i--
}
}
},
// 改变显示列表的状态
changeViewList (state, key) {
state.viewKey = key
}
},
actions: {
// 获取列表数据
getList (context) {
// 发起axios请求,这里省略
context.commit('initList', [
{ index: 0, info: '任务1', status: false },
{ index: 1, info: '任务2', status: true },
{ index: 2, info: '任务3', status: true },
{ index: 3, info: '任务4', status: false }
])
}
},
getters: {
// 未完成的任务数量
unDoneLength (state) {
return state.lists.filter((x) => x.status === false).length
},
// 显示列表
viewList (state) {
if (state.viewKey === 'all') return state.lists
if (state.viewKey === 'undone') return state.lists.filter((x) => x.status === false)
if (state.viewKey === 'done') return state.lists.filter((x) => x.status === true)
}
}
})
目前前端流行的三大框架,都有自己的路由实现:
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。 我们可以访问其官方网站对其进行学习:https://router.vuejs.org/zh/
vue-router是基于路由和组件的
bashnpm install vue-router --save
在模块化工程中使用它(因为是一个插件,所以可以通过Vue.use()来安装路由功能)
导入路由对象,并且调用Vue.use(VueRouter)
创建路由实例,并且传入路由映射配置
在Vue实例中挂载创建的路由实例
使用vue-router的步骤:
第一步:创建路由组件
第二步:配置路由映射:组件和路径映射关系
第三步:使用路由:通过<router-link>
和<router-view>
JSX是React中的用法,可以理解为将DOM元素当作变量使用
jsxconst element = <h1>Hello, world!</h1>;
jsexport default {
name: 'CustomJSX',
data () {
return {}
}
}
render
函数,通过return
返回组件jsxexport default {
name: 'CustomJSX',
data () {
return {}
},
render (h) {
return (
<div>
<div>Custom JSX</div>
<div>Custom JSX</div>
</div>
)
}
}
vue<div class="footer"><Customjsx /></div> import Customjsx from './components/infocard.jsx' components: { Customjsx },
行内样式,使用-
连接的属性改为大写,属性与属性之间使用,
分割
jsxrender (h) {
return (
<div>
<div style={{ color: '#1890ff', marginTop: '10px' }}>Custom JSX</div>
<div>Custom JSX</div>
</div>
)
}
文件引入样式
jsximport './style.less' //文件引入
export default {
name: 'CustomJSX',
data () {
return {}
},
render (h) {
return (
<div>
<div style={{ color: '#1890ff', marginTop: '10px' }}>Custom JSX</div>
<div class={'title-c'}>Custom JSX</div>
</div>
)
}
}
jsximport './style.less'
export default {
name: 'CustomJSX',
data () {
return {}
},
props: { // 接收父组件传递的参数
msg: {
type: String,
default: ''
}
},
render (h) {
const { msg } = this // 获取父组件传递的参数
return (
<div>
<div>{ msg }</div>
</div>
)
}
}
vue<Customjsx :msg="`title`"/>
jsximport './style.less'
export default {
name: 'CustomJSX',
data () {
return {}
},
methods: { // 事件处理函数
handleClick (event, info) {
this.$emit('on-click', { info }) // 传递参数info
}
},
render (h) {
const { handleClick } = this
return (
<div>
<div onClick={ ($event) => handleClick($event, '子组件参数') }>title</div>
</div>
)
}
}
vue<Customjsx @on-click="onClick"/> methods: { onClick (val) { console.log('click', val) } },
jsximport './style.less'
export default {
name: 'CustomJSX',
data () {
return {}
},
methods: { // 事件处理函数
handleClick (event) {
console.log(event, 'click')
}
},
render (h) {
const { handleClick } = this
return (
<div>
<div onClick={ ($event) => handleClick($event) }>title</div> // 获取原生事件
</div>
)
}
}
使用生命周期函数,可以使组件发起网络请求
jsximport './style.less'
export default {
name: 'CustomJSX',
data () {
return {}
},
methods: { // 事件处理函数
handleClick (event, info) {
this.$emit('on-click', { info }) // 传递参数info
}
},
created () { // 生命周期函数
console.log('created子组件创建')
},
mounted () {
console.log('mounted子组件挂载')
},
render (h) {
const { handleClick } = this
return (
<div>
<div onClick={ ($event) => handleClick($event, '子组件参数') }>title</div>
</div>
)
}
}
子组件
jsx// 人员列表组件
const ListItem = {
name: 'ListItem',
props: {
personList: Array
},
render (h) {
const { personList } = this
return (
<ul class="list-view">
{
/* 循环渲染 */
personList.map((mp, index) => {
return (
<li>
<p class={'tag-short-view'}>{ index + 1}</p>
<p>姓名:{mp.name}</p>
<p>年龄:{mp.age}</p>
</li>
)
})
}
</ul>
)
}
}
export default {
name: 'Customjsx',
data () {
return {}
},
props: {
dataList: {
type: Array,
default: () => []
}
},
methods: {},
created () {},
mounted () {},
render (h) {
return (
<div>
{/* 人员列表组件 */}
<ListItem personList={this.dataList} />
</div>
)
}
}
父组件
vue<Customjsx :dataList="dataList"/> data () { return { dataList: [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, { name: 'Charlie', age: 20 }, { name: 'David', age: 28 }, { name: 'Eve', age: 22 } ] } },
在vue中不能使用
在jsx中定义插槽
jsx// 人员列表组件
const ListItem = {
name: 'ListItem',
props: {
personList: Array
},
render (h) {
const { personList } = this
return (
<div>
<ul class="list-view">
{
/* 循环渲染 */
personList.map((mp, index) => {
return (
<li>
<p class={'tag-short-view'}>{ index + 1}</p>
<p>姓名:{mp.name}</p>
<p>年龄:{mp.age}</p>
</li>
)
})
}
</ul>
{
/* 插槽位置 */
this.$scopedSlots.header()
}
</div>
)
}
}
// 插槽内容
const SlotContent = {
render (h) {
return (
<div>插槽内容</div>
)
}
}
export default {
name: '',
data () {
return {}
},
props: {
dataList: {
type: Array,
default: () => []
}
},
methods: {},
created () {},
mounted () {},
render (h) {
// 具名插槽
const scopedSlots = {
header: (props) => <SlotContent></SlotContent>
}
return (
<div>
{/* 人员列表组件 */}
<ListItem personList={this.dataList} scopedSlots={scopedSlots}/> {/* 向子组件传递插槽 */}
</div>
)
}
}
插槽区域
jsxdata () {
return {
userInfo: {
name: '张三',
age: 18
}
}
},
render (h) {
const { userInfo } = this
return (
{
/* 插槽位置 */
this.$scopedSlots.header({ userInfo }) /* 向插槽传递参数 */
}
)
}
父组件
jsxconst scopedSlots = {
header: (props) => <strong>{ props.userInfo.name } { props.userInfo.age }</strong>
}
前言 全局状态管理工具
Pinia 有如下特点:
官方文档Pinia
git 地址 https://github.com/vuejs/pinia
bashyarn add pinia or npm install pinia
jsimport { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
const store = createPinia()
let app = createApp(App)
app.use(store)
app.mount('#app')
js// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++
},
},
})
vue<script setup> import { useCounterStore } from '@/stores/counter' const counter = useCounterStore() counter.count++ // 自动补全! ✨ counter.$patch({ count: counter.count + 1 }) // 或使用 action 代替 counter.increment() </script> <template> <!-- 直接从 store 中访问 state --> <div>Current Count: {{ counter.count }}</div> </template>
setup()
类似) 来定义一个 Storejsexport const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
pinia的state支持以下特性
vue<template> <div> <button @click="Add">+</button> <div> {{Test.current}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.current++ } </script> <style> </style>
在他的实例上有$patch方法可以批量修改多个值
vue<template> <div> <button @click="Add">+</button> <div> {{Test.current}} </div> <div> {{Test.age}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.$patch({ current:200, age:300 }) } </script> <style> </style>
推荐使用函数形式 可以自定义修改逻辑
vue<template> <div> <button @click="Add">+</button> <div> {{Test.current}} </div> <div> {{Test.age}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.$patch((state)=>{ state.current++; state.age = 40 }) } </script> <style> </style>
$state
您可以通过将store的属性设置为新对象来替换store的整个状态
缺点就是必须修改整个对象的所有属性
vue<template> <div> <button @click="Add">+</button> <div> {{Test.current}} </div> <div> {{Test.age}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.$state = { current:10, age:30 } } </script> <style> </style>
定义Actions
在actions 中直接使用this就可以指到state里面的值
jsimport { defineStore } from 'pinia'
export const useTestStore = defineStore('test', {
state:()=>{
return {
current:1,
age:30
}
},
actions:{
setCurrent () {
this.current++
}
}
})
使用方法直接在实例调用
vue<template> <div> <button @click="Add">+</button> <div> {{Test.current}} </div> <div> {{Test.age}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.setCurrent() } </script> <style> </style>
在pinia是不允许直接解构,解构会使pinia失去响应性的
jsconst Test = useTestStore()
const { current, name } = Test
console.log(current, name);
解决方案可以使用 storeToRefs
jsimport { storeToRefs } from 'pinia'
const Test = useTestStore()
const { current, name } = storeToRefs(Test)
支持同步异步
jsimport { defineStore } from 'pinia'
export const useTestStore = defineStore('test', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
vue<template> <div> <button @click="Add">+</button> <div> {{Test.counter}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.randomizeCounter() } </script> <style> </style>
async
await
修饰jsimport { defineStore } from 'pinia'
const Login = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: '小满',
isChu: true
})
}, 3000)
})
}
export const useTestStore = defineStore('test', {
state: () => ({
user: {},
name: "123"
}),
actions: {
async getLoginInfo() {
const result = await Login()
this.user = result;
}
},
})
vue<template> <div> <button @click="Add">test</button> <div> {{Test.user}} </div> </div> </template> <script setup lang='ts'> import {useTestStore} from './store' const Test = useTestStore() const Add = () => { Test.getLoginInfo() } </script> <style> </style>
getLoginInfo
setName
jsstate: () => ({
user: <Result>{},
name: "default"
}),
actions: {
async getLoginInfo() {
const result = await Login()
this.user = result;
this.setName(result.name)
},
setName (name:string) {
this.name = name;
}
},
this
this
指向已经改变指向undefined
修改值请用state
主要作用类似于computed
数据修饰并且有缓存
jsgetters:{
newPrice:(state)=> `$${state.user.price}`
},
this
jsgetters:{
newCurrent () {
return ++this.current
}
},
getters
互相调用jsgetters:{
newCurrent () {
return ++this.current + this.newName
},
newName () {
return `$-${this.name}`
}
},
$reset,重置store
到他的初始状态
订阅state的改变
类似于Vuex 的abscribe 只要有state 的变化就会走这个函数
jsTest.$subscribe((args,state)=>{
console.log(args,state);
})
第二个参数
如果你的组件卸载之后还想继续调用请设置第二个参数
jsTest.$subscribe((args,state)=>{
console.log(args,state);
},{
detached:true
})
只要有actions被调用就会走这个函数
jsTest.$onAction((args)=>{
console.log(args);
})
Pinia持久化插件
官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/
bashnpm i pinia-plugin-persistedstate
jsimport persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
jsimport { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
...
return {
count,
doubleCount,
increment
}
}, {
persist: true
})
本文作者:peepdd864
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!