实际开发中并不需要手动配置webpack
webpack是前端项目工程化的具体解决方案
主要功能:提供了友好的前端模块化开发支持、以及代码压缩混淆、处理浏览器端javaScript的兼容性、性能优化等强大的功能
好处:提高开发效率和项目可维护性
新建项目空目录需要英文路径,并允许npm init -y 命令,初始化包管理配置文件 package.json
新建src源代码目录
新建src->index.html首页和src->index.js脚本文件
运行npm install jquery -S命令,安装jQuery
通过ES6模块化的方式导入jQuery
在项目中配置webpack
module.exports={
mode:'development'//mode用来指定构建模式,可选值有development和production,项目上线时得改成production
}
"scripts":{
"dev":"webpack"//script节点下的脚本,dev属性名字可以自定义,其值为脚本名,可以通过npm run执行脚本,例如npm run dev
}
成功后会出现一个dist文件夹里面有main.js文件,其代码并没有被压缩
webpack代码压缩
将webpack.config.js中的mode改为production(项目上线时才为production)
在执行npm run dev后,运行webpack脚本前会读取这个文件
在webpack 4.x和5.x的版本中,有如下默认约定
注意:可以在webpack.config.js中修改打包的默认约定
entry和output
在webpack.config.js配置文件中通过entry节点指定打包的入口,通过output节点指定打包的出口
const path=require('path')//导入path模块
module.exports={//向外导出一个webpack的配置对象
entry:path.join(__dirname,'./src/index.js'),//打包入口文件的路径
output:{
path:path.join(__dirname,'./dist'),//输出文件的存放路径(指定生成文件要存放的目录)
filename:'bundle.js'//输出文件的名称(指定生成文件的名称)
}
mode:'development'
}
注意:下面两个插件以理解为主,之后会用脚手架自动配置
通过安装和配置第三方的插件,可以拓展webpack的能力,从而让webpack用起来更加方便,最常用的webpack插件有如下两个:
webpack-dev-server
类似于node.js阶段用到的nodemon工具
每当修改了源代码,webpack会自动进行项目的打包和构建
安装命令npm install webpack-dev-server -D
//package.json配置修改
"scripts":{
"dev":"webpack serve"
}
再次运行npm run dev (此时生成的main.js会放到内存中而不是物理磁盘中)
若出现Unable to load ‘@webpack-cli/serve’ command这种错误可以先运行npm install webpack-cli -D 再执行npm run dev
修改代码后终端中会反应,但是网页中没反应
cannot get错误可以修改为下面的devSercer中的static为"./‘’
const path=require('path')//导入path模块
module.exports={
mode:'development',
entry:path.join(__dirname,'./src/index.js'),//打包入口文件的路径
output:{
path:path.join(__dirname,'./dist'),//输出文件的存放路径(指定生成文件要存放的目录)
filename:'main.js'//输出文件的名称(指定生成文件的名称)
},
devServer: {
static: "./"
}
}
在html中加载和引用生成在内存中的main.js
<script src="/main.js"></script>
之后打开http://localhost:8080/点击网页中的src即可看到效果
html-webpack-plugin
webpack中的HTML插件(类似模板引擎插件)
可以通过此插件自封之index.html页面的内容
安装此插件后可以直接进入index首页(是复制出来的在内存中的html文件)
会自动注入内存中的main.js到html(我们就不必手动引入文件)
安装命令npm install html-webpack-plugin -D
//导入插件
const HtmlPlugin=require('html-webpack-plugin')
//创建实例对象
const htmlPlugin=new HtmlPlugin({
template:'./src/index.html',//原文件存放路径
filename:'./index.html'//指定生成文件存放路径
})
module.exports={
mode:'development',
plugins:[htmlPlugin],//通过plugins节点,使htmlPlugin插件生效
}
在webpack.config.js配置文件中,可以通过devServer节点对webpack-dev-server插件进行更多的配置
devServer{
open:true,//首次打包成功后自动打开浏览器
port:8080,//修改端口号
host:"127.0.0.1",//指定运行的主机地址
}
在实际开发中,webpack默认只能处理.js后缀文件,其它的非js后缀文件模块处理不了,需要loader加载器才可以正常打包
loader加载器作用:协助webpack打包处理特定的文件模块,比如:
在webpack中一切皆模块,都可以通过es6语法在js中导入模块包括css文件
import "./index.css"
因为webpack无法处理css等模块,所以需要配置loader
配置css的loader
运行npm i [email protected] [email protected] -D命令,安装处理css文件的loader
在webpack.config.js的module->rules数组中,添加loader规则如下
module:{
rules:[{test:/\.css$/,use:["style.loader",'css.loader']}]
}
其中test表示匹配的文件类型,use表示队友要调用的loader
注意:
配置url-loader
运行npm i [email protected] [email protected] -D命令
在webpack.config.js中
rules:[{test:'/\.jpg|png|gif$/',use:'url-loader?limit=22229'}]
?之后是loader的参属项
limit用来指定图片的大小单位是字节
只有<=limit大小的图片才会被转为base64格式的图片
配置babel-loader
webpack有时无法处理高级的js语法,需要babel-loader来处理
npm i [email protected] @babel/[email protected] @babel/[email protected] -D
{test:/\.js$/,use:'babel-loader',exclude:/node_modules/}
在项目根目录下,创建名为babel.config.js的配置文件,定义babel的配置项如下
module.exports={
plugins:[['@babel/plugin-proposal-decorators',{legacy:true}]]
}
base64图片可以防止发起一些不必要的网络请求
在package.json文件的scripts节点下,新增build命令如下
"scripts":{
"div":"webpack serve",
"build":"webpack --mode production"
}
–mode是一个参数项,用来指定webpack的运行模式,production表示生产环境,会对打包生产的文件进行代码压缩和性能优化
build中只用webpack去掉serve会将生成文件到磁盘而不是内存
注意:通过–mode指定的参数项,会覆盖webpack.config.js中的model选项来打包
优化js文件路径
output:{
path:path.join(__dirname,'dist'),
//生成的文件名,为其放入js文件夹中方便分辨和归类
filename:'js/main.js'
}
优化图片路径
webpack.config.js中的url-loader配置项
//outputPath修改图片打包路径
{
test:/\.jpg|png|gif$/,
use:'url-loader?limit=470&outputPath=images'
}
安装clean-webpack-plugin插件
npm install clean-webpack-plugin -D
//导入
const {CleanWebpackPlugin}=require{'clean-webpack-plugin'};
moudel.export={
///........
plugins:[htmlPlugin,new CleanWebpackPlugin()]
//.....
}
浏览器报错的行号是生成文件里的行号
因此需要配置SourceMap
SourceMap就是一个信息文件,存储着位置信息,也就是说,该文件中存储着压缩混淆后的代码所对应的转换前的位置
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。
在webpack.config.js中添加如下配置即可
module.exports={
devtool:'eval-source-map',
}
发布项目前要把sourcemap关闭,防止源代码被泄露
在实际发布的时候建议把值改为:nosources-source-map,即照顾了调试代码的体验又具有安全性
工程化的前端开发
vue是一套用于构建用户界面的前端框架
框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能
vue的指令、组件、路由、Vuex、vue组件库
好处:当页面数据变化时,页面会自动重新渲染
注意:数据驱动视图是单向的数据绑定
在填充表单时,双向数据绑定可以辅助开发者在不操作DOM的前提下,自动把用户填充的内容同步到数据源中
好处:开发者不再需要手动操作DOM元素,来获取表单元素最新的值
MVVM是vue实现数据驱动视图和双向数据绑定的核心原理。MVVM指的是Model、View和ViewModel,它把每个HTML页面拆分成了这三个部分:
在MVVM概念中:
①导入vue.js的script脚本文件
②在页面中声明一个将要被vue所控制的DOM区域
③创建vm实例对象(vue实例对象)
<body>
<div id="app">
{{username}}}
div>
<script src="./lib/vue-2.6.12.js">script>
<script>
//创建vue实例对象
const vm=new Vue({
//el属性表示当前vm实例要控制那个区域,接收的值是一个选择器
el:'#app',
//data对象就是要渲染的数据
data:{
username:'zhangsan'
}
})
script>
body>
指令是vue为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构
vue中的指令按照不同的用途可以分为6大类:
内容渲染指令用来辅助开发者渲染DOM元素的文本内容,常用的内容渲染指令有如下三个:
//把username对应的值渲染到p标签
<p v-text="username">
p>
//把gender对应的值,渲染到p标签中
//默认的文本“性别”会被gender的值覆盖
<p v-text="gender">
性别
p>
插值表达式(Mustache)
<p>
姓名:{{username}}
性别:{{gender}}
p>
只是占位符,不会覆盖原有内容
不会渲染html标签
只能用于内容节点,不能用于属性值
//info:"<h4>hello world!h4>"
<p v-html="info">
p>
<div id="app">
<input type="text" v-bind:placeholder="tips">
<img v-bind:src="photo">
div>
<script>
const vm=new Vuew({
el:'#app',
data:{
tips:"请输入用户名",
photo:'http://image.....'
}
})
script>
需要动态改变的属性就在属性前写 v-bind:
可以简写为’ : ’
之后属性值就填data中的变量名
在vue提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持javascript表达式的运算
{{number+1}}
{{ok?'Yes':'No'}}
{{message.split('').reverse().join('')}}
<div v-bind:id="'list-'+id">div>
v-bind属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,否则会被认为是data中的变量
<div id="app">
<h3>
count值为:{{count}}
h3>
<button v-on:click="add(1)">
+1
button>
div>
<script>
const vm=new({
le:"#app",
data:{
count:0
},
//methods中定义事件处理函数
methods:{
add(n){
//这里的this相当于vm
//html中可以用()传参,不传参可以不加小括号
this.count+=n
}
}
})
script>
@click,@input,常见的原生事件都可以绑定
<div id="app">
<h3>
count值为:{{count}}
h3>
<button v-on:click="add(1,$event)">
+1
button>
div>
<script>
const vm=new({
le:"#app",
data:{
count:0
},
//methods中定义事件处理函数
methods:{
add(n,e){
this.count+=n
console.log(e)
if(this.count%2===0){
//偶数
e.target.style.backgroundColor="red"
}
else{
e.target.style.backgrounColor=''
}
}
}
})
script>
①**.prevent**
起到e.preventDefault作用
加在事件绑定之后
<a href="src" @click.prevent="show">a>
②**.stop**
阻止事件冒泡
起到e.stopPropagation的作用
在监听键盘事件时,我们经常需要判断详细的按键,此时可以为键盘相关事件添加按钮修饰符
<div id="app">
<input type="text" @keyup.esc="clearInput">
div>
<script>
const vm=new({
le:"#app",
data:{
},
//methods中定义事件处理函数
methods:{
clearInput(e){
console.log("触发esc键")
e.target.value=""
}
}e
})
script>
结合表单使用
将表单中的value替换成v-model即可
<div id="app">
<input type="text" v-model="username">
<p>
用户的名字:{{username}}
p>
div>
<script>
const vm=new({
le:"#app",
data:{
username:'zhangsan'
},
//methods中定义事件处理函数
methods:{
clearInput(e){
console.log("触发esc键")
e.target.value=""
}
}e
})
script>
v-model是双向绑定,而v-bind是单向绑定
且使用v-model后input表单的value属性就没有意义了
只有表单元素才能使用v-model
input\textarea\select\
修饰符 | 作用 | 示例 |
---|---|---|
.number | 自动将用户的输入值转为数值类型 | |
.trim | 自动过滤用户输入的首尾空白字符 | |
.lazy | 在”change“时而非”input“时更新,即不会每次输入一个字符就同步而是回车后同步 |
用来辅助开发者按需控制DOM的显示和隐藏。条件渲染指令有如下两个分别是
<div id="app">
<p v-if="flag">
这是被v-if控制的元素
p>
<p v-show="flag">
这是被v-show控制的元素
p>
div>
<script>
const vm=new({
le:"#app",
data:{
flag:true
},
//methods中定义事件处理函数
methods:{
}
})
script>
需要以item in list的形式使用
需要索引的时候可以(item,index) in list
<div id="app">
<li v-for="(item,index) in list"
key="item.id">
索引:{{index}} id:{{item.id}} 姓名:{{item.name}}
li>
div>
<script>
const vm=new({
le:"#app",
data:{
list:[
{id:1,name:'zs'},
{id:2,name:'ls'}
]
},
//methods中定义事件处理函数
methods:{
}
})
script>
注意:过滤器在vue3中已经被删除,这里只要会一点基础就行
过滤器(Filters)是vue为开发者提供的功能,常用于文本的格式化,过滤器可以用在两个地方:插值表达式和v-bind属性绑定
过滤器应该被添加在javascript表达式的尾部,由”管道符“进行调用,
<div id="app">
<p>
{{message|capi}}
p>
<div v-bind:id="rawId | formatid">div>
div>
<script>
const vm=new Vue({
el:'#app',
data:{
message:'hello vue.js'
}
//过滤器本质是一个函数,需要被定义到filter节点之下
filter:{
capi(val){//val永远是管道符前面的值
const first=val.charAt(0).toUpperCase()//取第一个字符并变大写
const other=val.slice(1)//从1开始截取字符串
return first+other
}
}
})
script>
filter中的过滤器为私有过滤器
只有当前vm控制的标签区域才能使用私有的过滤器
一般都定义全局过滤器
当全局与私有过滤器名冲突,则按就近原则取私有过滤器
//在vm对象外按下面形式定义
Vue.filter('过滤器名',(val)=>{
//处理程序
return 结果
})
定义格式化时间的过滤器
Vue.filter('dataformat',function(time){
//1、对time进行格式化处理得到YYYY-MM-DD HH:mm:ss格式
//2、把格式化的结果return出去
//这里使用dayjs库来导入时间
//直接调用dayjs()得到的是当前时间
//dayjs(给定的时间)得到指定的日期
const timestr=dayjs().format('YYYY-MM-DD HH:mm:ss')
})
{{time|format|xxx|yyy|zzz}}
将值不断的往后传,最后一个return的值就是结果
但是传入的参数在接收时会从形参第二位开始接收,第一位默认是管道符前的值
允许开发者监视数据变化,从而针对数据变化做特定的操作,监听的是data里数据值的变化
<div id="app">
<input v-model="username" type="text">
div>
<script>
const vm=new Vue({
el:'#app',
data:{username:''},
watch:{//定义侦听器,
//1、函数形式侦听器,用相关数据名作为方法名
username(newVal,oldVal){
console.log(newVal,oldVal)
}
//2、对象格式的侦听器,相关数据名作对象名
username:{
handler(newVal,oldVal){//handler是固定写法
}
}
}
})
script>
典型应用场景:判断用户名是否被占用,若占用则清除并警告
函数形式的侦听器不会在初次进入页面时触发
对象格式的侦听器可以通过immediate选项,让侦听器自动触发
watch:{
username:{//对象格式的侦听器
handler(newVal,oldVal){//handler是固定写法
},
immediate:true //true表示初次进入就触发,默认为false
}
}
对象里的数据不会被函数形式的侦听器所侦听
可以使用对象格式,并用deep选项来侦听每一个对象中的数据
data:{
info:{
username:''
}
}
watch:{
info:{
handler(newVal,oldVal){
//..
},
deep:true//开启深度侦听,就能侦听对象中的任何属性的变化
},
'info.username'(){
//也可以用这种方法来侦听具体子属性变化,注意需要单引号
}
}
注意:深度侦听会遍历数据对象的所有嵌套属性,如果是大型数据需要慎用,以免造成性能上的不足
计算属性的值是通过一系列运算后得到的属性值
这个动态计算出来的属性值可以被模板结构或methods方法使用
动态改变背景颜色
<div :style="{backgroundColor:`rgb(${r},${g},${b})`}">
颜色:{{rgb(${r},${g},${b})}}
div>
这种用字符串拼接的形式来动态改变虽然实现了效果,但是当其中一个rgb改成rgba时其余的rgb也得改,这就非常没效率,因此需要计算属性
data:{
r:0,g:0,b:0
},
computed:{//存放所有计算属性
//计算属性需要定义为方法形式
//在vm对象中methods里可以用例如this.rgb调用
//html模板中可以直接调用,
//依赖的数据源变化也会引起计算属性的变化
rgb(){
return `rgb(${this.r},${this.g},${this.b})`
}
}
//实际应用
{{rgb}}
axios是一个专注于网络请求的库
npm -i axios -S
基本语法
import axios from 'axios'
axios({
method:'请求的类型',
url:'请求的url地址',
//Get参数,按需
params:{}
//post请求体参数,按需
data:{}
//调用axios返回的是promise对象,可以用.then
}).then(result)=>{
//.then用来指定请求成功后的回调函数
//形参中的result是请求成功后的结果
}
axios在请求到数据之后,给真正的数据做了一层外包装
data属性里面才是正真的数据
async和await,以及对返回结果解构赋值得到真正数据
解构赋值时在变量名后面用:可以更改变量名
例如
{data:res}=result
则得到结果的变量名为res
axios.get
const {data:res}=axios.get("url地址",
{
params:{请求体数据}
})
axios.post
const {data:res}=axios.post('url',{post请求体数据})
**单页面应用程序(SPA)**顾名思义指一个web网站中只有唯一一个HTML页面,所有的功能与交互都在这唯一的一个页面内完成
vue-cli是vue.js开发的标准工具。简化了程序员基于webpack创建工程化的vue项目的过程
vue-cli是npm上的一个全局包,使用npm install命令,即可方便的把它安装到自己的电脑上:
npm install -g @vue/cli
基于vue-cli快速生成工程化的Vue项目:
vue create 项目名称
建议选择最后一项,可以自定义安装需要的东西
然后选择2.x即vue2.0
assets文件夹存放静态资源文件,比如图片、css样式表
components文件夹存放程序员封装的可复用组件
main.js是项目的入口文件,整个项目的运行需要先运行main.js
App.vue是项目的根组件,可以清空后重写
在工程化项目中,vue要做的事情很单纯,通过main.js把App.vue渲染到index.html的指定区域中
//导入Vue这个包,得到Vue构造函数
import Vue from 'vue'
//导入App.vue根组件,将来要把App.vue中的模板结构渲染到HTML页面中
import App from './App.vue'
Vue.config.productionTip = false
//创键Vue实例对象
new Vue({
//将render函数指定的组件渲染到html页面中
render: h => h(App),
}).$mount('#app')
在index.html中id为app的div在渲染时会被App.vue中的标签替换掉,其相当于成了一个占位符(render指定的结构会替换掉el指定的结构,这里的el用$mount方法替代,两种方法作用一样)
声明一个模板结构,比如Test.vue
//Test.vue
<template>
<div>
<h3>
这是一个自定义的模板
h3>
div>
template>
修改main.js
//main.js
import Vue from 'vue'
import Test from './Test.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(Test),
}).$mount('#app')
根据封装的思想,把页面上可重用的UI结构封装为组件,从而方便项目的开发和维护
vue是一个支持组件化开发的
vue中规定:组件的后缀名是.vue之前接触到的App.vue本质上就是一个Vue组件。
每个.vue组件都由三部分组成:
vue中分别对应template,script,style标签,一般按照此顺序写,且script中必须要有export defualt{} js代码写在括号中
注意:template中只能有一个根div
<template>
<div>
自定义模板
<h1>
用户:{{username}}
h1>
<button @click="changeName">
修改用户名
button>
div>
template>
<script>
//固定写法,默认导出
export default{
//组件中的data必须是一个函数,return一个数据对象
data(){
return {
username:'zs'
}
},
//组件中定义方法
methods:{
changeName(){
this.username="ls"
}
},
//当前组件中的侦听器
watch:{
},
computed:{
//计算属性
},
filter:{
//过滤器
}
}
script>
<style>
div {
background:'red';
}
style>
组件被封装好后彼此之间是相互独立的,不存在父子关系
在使用组件时,根据彼此之间的嵌套关系,形成了父子关系、兄弟关系
import xxx from '@components/xxx.vue'
export default{
components:{
xxx
}
}
<div id="app">
<xxx>xxx>
<div>
即被注册的组件在哪个组件中注册则只能在该组件中使用
在main.js中通过Vue.component()方法,
import xxx from '@/components/xxx.vue'
Vue.component("注册名称",xxx)//注册名称可以起任意名,但建议以大写开头
props是组件的自定义属性,在封装通用组件的时候,合理的使用props可以极大提高组件的复用性
export default{
//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
props:[
'自定义属性1',
'自定义属性2',
...
],
}
通过调用组件并给自定义属性赋值可以传值给props
<xxx a="9">xxx>
Tip:结合v-bind可以使传入的数值为数值型而不是原来的字符串型(实质是使引号中的内容变为js代码)
<xxx :a="9">xxx>
props的数据可以直接在html模板结构中被使用
可以将从props读取的数据传入到data的变量中
比如props中有个变量init,data中有个变量count则可以这样赋值
data(){
return{
count:this.init
}
}
如此可以修改data中的值而不必修改props的值尚且其值也并不能被修改。
可以设置props的默认值
语法:
export default{
props:{
a:{
//用default属性定义属性的默认值
default:0
}
}
}
在声明自定义属性值时,可以通过type来定义属性的值类型
export default{
props:{
init:{
//default定义属性的默认值
default:0,
//用type属性定义属性的值类型
//如果传递过来的值不符合此类型,则会在终端报错
type:Number
}
}
}
export default{
props:{
init:{
//default定义属性的默认值
default:0,
//用type属性定义属性的值类型
//如果传递过来的值不符合此类型,则会在终端报错
type:Number,
//required:true表示该自定义属性必须填写
required:true
}
}
}
默认情况下,写在vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
style标签的scoped属性
只要给当前组件的style标签加上scoped属性就可以解决
<style scoped>
style>
加上deep可以修改子组件中样式
/deep/ h5{
color:red;
}
当使用第三方组件库的时候可以使用该方法在父组件中修改引入的组件的样式
生命周期:是指一个组件从创键->运行->销毁的整个阶段,强调的是一个时间段
生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自当按次序执行
beforeCreate()函数调用时。组件的props/data/methods尚未创键,都处于不可用状态,所以此刻该函数并不能做什么事情,因此一般不用
created()函数调用时,组件的props/data/methods已经初始化,都处于可用阶段,但是组件的模板结构尚未生成,此函数十分重要,可在此阶段发起ajax请求,来获取数据并转存到data
两种最常见的关系
props自定义属性
//父组件
<Son :msg="message" :user="userinfo">Son>
data(){
return{
message:'hello vue.js',
userinfo:{name:'zs',age:20}
}
}
//子组件
<template>
<div>
<h5>
Son组件
h5>
<p>
父组件传递过来的msg值为:{{msg}}
p>
<p>
父组件传递过来的user值为:{{user}}
p>
div>
template>
props:['msg','user']
子组件向父组件共享数据使用自定义事件
//子组件
export default {
data(){
return {count:0}
},
methods:{
add(){
this.count+=1
//修改数据时通过$emit()触发自定义事件
this.$emit('numchange',this.count)
}
}
}
//父组件
<son @numchange="getNewCount"></
son>
export default{
data(){
return {countFromson:0}
},
methods:{
getNewCount(val){
this.countFromSon=val
}
}
}
EventBus
1、定义一个eventBus.js文件,导出一个空的Vue实例
//eventBus.js
import Vue from 'vue'
//向外共享Vue的实例
export default bus=new Vue()
2、发送方用emit发送
//兄弟组件A(数据发送方)
import bus from './eventBus.js'
export default{
data(){
return {
msg:'hello'
}
},
methods:{
sendMsg(){
bus.$emit('share',this.msg)
}
}
}
3、接收方用on接收
//兄弟组件B(数据接收方)
import bus from './eventBus.js'
export default{
data(){
return {
msgFrombrother:''
}
},
created(){//可以在created生命周期函数中调用
bus.$on('share',val=>{
this.msgFromLeft=val
})
}
}
ref用来辅助开发者在不依赖于jQuery的情况下获取DOM元素或组件的引用。
每个vue的组件实例上,都包含一个** r e f s ∗ ∗ 对象,里面存储着对应的 D O M 元素或组件的引用。默认情况下,组件的 ∗ ∗ refs**对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的** refs∗∗对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的∗∗refs**指向一个空对象。
<template>
<h1 ref="myh1">
组件
h1>
<button @click="show">
点我变红
button>
template>
<script>
export default{
data(){
return {
}
},
methods:{
show(){
//首先需要在标签上取ref的名字
console.log(this.$refs.myh1)//获取对应名字的dom并打印
this.$refs.myh1.style.color="red"
}
}
}
script>
<template>
{{count}}
<xxxx ref="myComponent"/>
<button @click="show">
button>
template>
<script>
import xxxx from "@components/xxxx.vue"
export default{
data(){
return {
}
}
components:{
xxxx
}
methods:{
show(){
console.log(this.$refs.myComponent)
this.$refs.myComponent.该组件方法/属性
//得到该组件中可以直接使用该组件方法和属性
}
}
}
script>
延迟到DOM更新后调用callback
this.$nextTick(()=>{
//....
})
有利于处理因为DOM没更新导致的undifined
vue提供了一个内置的组件,专门用来实现动态组件的渲染,相当于一个占位符
is中填写要渲染的子组件名,可以用:绑定来实现动态组件
<template>
<div>
<component is="组件名">component>
div>
template>
<template>
<div>
<component :is="comName">component>
div>
template>
<script>
import Cart from"./components/Cart/Cart.vue"
export default{
data(){
return {
comName:'Cart'
}
},
components:{
Cart
}
}
script>
注意:component切换组件时原先组件会被销毁
防止切换时销毁组件,会将组件缓存
<keep-alive>
<component :is="comName">component>
keep-alive>
当组件被缓存时,会自动触发组件的deactivated生命周期函数。
当组件被激活时,会自动触发组件的activated生命周期函数
组件第一次激活时会触发created函数,但之后的激活不会再触发
include属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间用逗号分隔
<keep-alive include="component1,component2">
<component :is="comName">component>
keep-alive>
exclude用来指定:名称匹配的组件不会被缓存,用法与include一样
注意:它不能与include同时使用
插槽是vue为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的,希望由用户指定的部分定义为插槽
//com组件
<template>
<div>
<slot>slot>
div>
template>
<template>
<div class="app">
<com>
<p>
这是在com组件的内容区域,声明的p标签
p>
com>
div>
template>
vue规定每一个插槽都有一个name名称
如果省略了name属性则会有默认名称叫做default
默认情况下在使用组件的时候提供的内容都会填充到default的插槽中
//com组件
<template>
<div>
<slot name="default">slot>
div>
template>
可以指定内容放入名为何值得插槽,但需要template标签,template标签是虚拟的元素不会被渲染
<template>
<div class="app">
<com>
<template v-slot:default>
<p>
将p标签放入com组件的名为default的插槽中
p>
template>
com>
div>
template>
v-slot: 可以简写为#
<template>
<div>
<slot>这是这个插槽的默认内容,如果使用组件时没有用插槽则会展示这段内容,官方将此称为后备内容slot>
div>
template>
带name名字的插槽就是具名插槽
<template>
<div class="article-container">
<div class="header-box">
<slot name="header">slot>
div>
<div class="content-box">
<slot name="content">slot>
div>
<div class="footer-box">
<slot name="footer">slot>
div>
div>
template>
<script>
export default{
name:'Article'
}
script>
<style lang="less" scoped>
style>
<template>
<div class="app-container">
<Article>
<tamplate #header>
<h3>
一首诗
h3>
tamplate>
<tamplate #content>
<div>
这是一首诗
div>
tamplate>
<tamplate #footer>
<div>
这是脚注
div>
tamplate>
Article>
div>
template>
可以在slot中通过自定义属性来传值,并在使用插槽时在template中的插槽名称后面跟上=”名称“来接收一个装有自定义属性值的对象,名称可以随意。
这种提供属性对应值的用法叫做作用域插槽
<template>
<div class="article-container">
<div class="header-box">
<slot name="header" msg="hello vue">slot>
div>
<div class="content-box">
<slot name="content">slot>
div>
<div class="footer-box">
<slot name="footer">slot>
div>
div>
template>
<script>
export default{
name:'Article'
}
script>
<style lang="less" scoped>
style>
<template>
<div class="app-container">
<Article>
<template #header="scope">
<div>
{{scope.msg}}
div>
template>
Article>
div>
template>
<template>
<div class="article-container">
<div class="header-box">
<slot name="header" msg="hello vue" user="userinfo">slot>
div>
<div class="content-box">
<slot name="content">slot>
div>
<div class="footer-box">
<slot name="footer">slot>
div>
div>
template>
<script>
export default{
name:'Article'
}
script>
<style lang="less" scoped>
style>
<template>
<div class="app-container">
<Article>
<template #header="{msg,user}">
<div>
{{msg}}{{user}}
div>
template>
Article>
div>
template>
vue官方提供了很多常用的指令,除此之外vue还允许开发者自定义指令
<template>
<div>
<h1 v-color>
变红
h1>
div>
template>
<script>
export default{
directives:{
color:{
//为绑定到的HTML元素设置红色的文字 //当指令绑定到元素后会立即触发bind函数,指令需要以v-开头
bind(el){
//形参中的el时绑定了此指令的、原生的DOM对象,是固定写法
el.style.color='red'
}
}
}
}
script>
<template>
<div>
<h1 v-color=“color”>
变颜色
h1>
<h1 v-color="'red'">
红色
h1>
div>
template>
<script>
export default{
data(){
return{
color:'blue'
}
},
directives:{
color:{
bind(el){
el.style.color=binding.value
}
}
}
}
script>
bind只会在第一次绑定到元素上的时候触发,当DOM结构更新后不会触发,需要update函数
<template>
<div>
<h1 v-color=“color”>
变颜色
h1>
<h1 v-color="'red'">
红色
h1>
div>
template>
<script>
export default{
data(){
return{
color:'blue'
}
},
directives:{
color:{
bind(el){
el.style.color=binding.value
},
update(el){
//DOM更新时触发
el.style.color=binding.value
}
}
}
}
script>
bind只会在第一次绑定时生效,update只会在第一次不生效
当bind和update函数中的逻辑完全相同时,则对象格式的自定义指令可以简写成函数格式
directives:{
color(el,binding){//简写
el.style.color=bing.value
}
}
全局自定义指令需要通过Vue.directive()进行声明
//参数1:字符串,表示全局自定义指令的名字
//参数2:对象,用于接收指令的参数值,可以简写成一个function
Vue.directive('color',function(el,bindng){
el.style.color=binding.value
})
用于约束代码风格
略(以后要用再学~~)vscode直接装个eslint插件,公司项目里一般都配置好了eslint格式,直接用一建修复就可自动改变代码格式
//main.js
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
//1、全局配置axios的请求根路径
axios.defaults.baseURL="请求根路径"
//2、将axios挂载到vue的原型,这样就不用每次使用都导入axios,而是调用this.$http
Vue.prototype.$http=axios
//
new Vue({
render:h=>h(App)
}).$mount(#app)
const {data:res}=await this.$http.get('/api/get')
axios挂载到原型的缺点:无法实现api接口的复用
优化:分离axios并封装
创键一个utils工具文件夹,新建reques.js文件再里面写入如下代码
//封装axios
import axios from 'axios'
const http=axios.create({
//通用请求的地址前缀
baseURL:'/api',
timeout:10000,//超时时间ms
})
//请求拦截器
http.interceptors.request.use(function(config){
//在发送前处理代码
return config
},function(error){
//对请求错误时处理代码
return Promise.reject(error)
});
//响应拦截器
http.interceptors.response.use(function(response){
//对响应数据处理代码
return response;
},function(error){
//响应错误时的处理代码
return Promise.reject(error)
})
export default http
之后创建一个api文件夹,其中创建一个index.js文件或者其它你喜欢的命名的js文件,在里面导入http并用它创建各种api请求并用export导出
最后你就可以在需要使用api请求的文件中导入相应的请求函数并使用
//将api请求都独立封装在此文件
import http from '../utils/request.js'
//定义接口
//请求首页表格数据
export const getData=()=>{
return http.get('/home/getData')
}
//请求首页折线图数据
export const getGraphData=()=>{
return http.get('/home/getGraph')
}
//请求首页柱状图数据
export const getGrap_1Data=()=>{
return http.get('/home/getActive')
}
import {getData} from '../api'
路由(router)就是对应关系,这里是Hash地址和组件之间的对应关系
SPA单页面应用程序
vue-router是vue.js官方给出的路由解决方案。它只能结合vue项目进行使用,能够轻松的管理SPA项目中组件的切换
vue-router的官方文档地址是:https://router.vuejs.org/zh/
在vue2项目中 npm i [email protected] -S
在src源代码目录下,新建router/index.js路由模块,并初始化如下代码
import Vue from 'vue'
import VueRouter from 'vue-router'
//调用Vue.use()函数 把VueRouter安装为Vue的插件
Vue.use(VueRouter)
//创键路由的实例对象
const router =new VueRouter()
//向外共享路由的实例对象
export default router
//main.js
//导入路由模块(模块化导入时若只有一层路径,则默认加载路径下index.js的文件)
import router from'./router'
new Vue({
render:h=>h(App),
//挂载 router(属性名和变量名相同可以es6简写)
router
}).$mount('#app')
//App.vue
<template>
<div class="app-container">
<a href="#/home">首页a>
<a href="#/movie">电影a>
<a href="#/about">关于a>
//只要在项目中安装和配置了vue-router就可以使用router-view组件了,作用就是个给组件的占位符
<router-view >router-view>
div>
template>
<script>
script>
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
//导入需要的组件
import Home from "./components/Home"
import Movie from "./components/Movie"
import About from "./components/About"
//调用Vue.use()函数 把VueRouter安装为Vue的插件
Vue.use(VueRouter)
//创键路由的实例对象
const router =new VueRouter({
//routes是一个数组,作用是定义“hash地址”与“组件”之间的对应关系
routes:[
{path:'/home',component:Home},
{path:'./movie',component:Movie},
{path:'./about',component:About},
]
})
//向外共享路由的实例对象
export default router
可以用a但是不建议使用a标签
//App.vue
<template>
<div class="app-container">
//当安装了vue-router后就可以使用router-link来替代普通的a链接
<router-link to="/home">首页router-link>
<router-link to="/movie">电影router-link>
<router-link to="/about">关于router-link>
//只要在项目中安装和配置了vue-router就可以使用router-view组件了,作用就是个给router-link指向的组件的占位符
<router-view >router-view>
div>
template>
<script>
script>
路由重定向指:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面,通过路由规则的redirect属性,指定一个新的路由地址,可以很方便地设置路由地重定向
const router=new VueRouter({
routes:[
{path:'/',redirect:'/home'},
{path:'/home',component:Home}
]
})
通过路由实现组件的嵌套展示,叫做嵌套路由
//about.vue
<template>
<div class="about-container">
<router-link to="/about/tab1">router-link>
<router-link to="/about/tab2">router-link>
<router-view>router-view>
div>
template>
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Tab1 from './components/Tab/Tab.vue'
const router=new VueRouter({
routes:[
{
path:'/about',
component:About,
children:[//子路由不能斜线开头
{path:'tab1',component:Tab1}
]
}
]
})
如果children数组中某个路由规则的path值为空字符串,则这条路由规则叫做“默认子路由”
children:{
{path:'',component:Tab1}//默认展示Tab1,router-link中也不需要/tab1
{path:'tab2',component:Tab2}
}
指把Hash地址中的可变部分定义为参数项,从而提高路由规则的复用性
在vue-router中使用 : 来定义路由的参数项,参数项名可任取
{path:'/movie/:id',component:Movie}
那如何根据id来展示Movie组件中的对应内容呢
在组件中this.$route.params.id 可以拿到对应参数项
props:true
{path:'/movie/:id',component:Movie,props:true}
<h3>Movie组件--{{id}}h3>
<script>
export default{
props:['id']
}
script>
相比第一种传参这种传参更加方便,推荐使用
query
<router-link to="/movie/2?name=zs&age=20">router-link>
在?后面的参数叫做查询参数,需要用this.$route.query来访问查询参数
fullpath
fullpath即包含了路径又包含了参数,是完整的地址
而path则只包含路径
在浏览器中,点击链接实现导航的方式叫做声明式导航。例如:
在浏览器中,调用API方法实现的导航叫做编程式导航。例如:
this.$router.push(‘hash地址’)
this.$router.replace(‘hash地址’)
this.$router.go(数值n)
在template行内使用编程式导航时需要省略this
字符串形式:
this.$router.push('/search'
+'?k='+参数)
模板字符串
this.$router.push(`/search?=${参数}`)
this.$router.push({name:'search',params:{变量名:动态参数},query:{变量名:查询参数}})
控制路由访问权限
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制
const router=new VueRouter({
})
//调用路由实例对象的beforEach方法即可声明全局前置守卫
//每次发送路由导航跳转的时候,都会自动触发fn这个回调函数
router.beforeEach(fn)
router.beforEach((to,from,next)=>{
//to是将要访问的路由的信息对象
//from是将要离开的路由的信息对象
//next是一个函数,调用next()表示放行,允许这次路由导航
console.log(to,from)
next()//放行
})
router.beforEach((to,from,next)=>{
if(to.path==='/main'){
const token=localStorage.getItem('token')
if(token){//token存在即已经登录
next()
}else{//没有登陆则强制跳转到登录页
next('/login')
}
}else{
next()
}
})
可以不要linter 他是esline(会严格限制代码格式规范,错误格式会报错并阻止程序运行…非常痛苦)
相比之前多了个views文件夹
该文件夹是用于存放组件的与components相同,但是要通过路由来切换的组件存放到views中,否则存放在components中
移动端的vue组件库
pc端的vue组件库
es6模块化规定:
默认导出语法:
export default 默认导出的成员
每个模块中只允许一个默认导出
默认导入:
import 接收名称 from ‘模块标识符’
默认导入的接收名称可以任意,但是需要合法的命名
按需导出
export 导出成员
按需导入:
import {导出成员名称1 as 重命名,导出成员名称2,…} from ‘模块标识符’
多层回调函数的相互嵌套,就形成了回调地狱
回调地狱缺点:
Promise解决回调地狱
promise是一个构造函数
promise.prototype上的then()方法
then()方法用来预先指定成功和失败的回调函数
Promise是异步的,但是**这里的异步是指他的.then()和.catch()方法,Promise本身还是同步的,**所以当执行一个函数时,会先执行new Promise()的同步方法,再执行回调resolve或者是reject。
这里就形成了一个回调地狱
npm install then-fs
上述代码无法保证文件读取顺序,需要进一步改进
升级改进=》
第一个then又返回了一个新的promise实例,就可以继续then,采用链式编程的方式按顺序读取文件内容
在链式操作中如果发生了错误就可以使用Promise.prototype.catch方法对链式操作中所有产生的错误进行捕获和处理
如果不希望错误导致后续的then停止执行则可以将catch提前
会发起并行的Promise异步操作,等所有的异步操作(等待机制)全部结束后才会执行下一步的.then操作
读取的顺序就是数组里实例的顺序
会发起并线的Promise异步操作,只要任何一个异步操作完成就立即执行下一步的then操作
因此这里then得到的result是promise执行最快的实例的结果
async/await是ES8引入的新语法,用来简化Promise异步操作,在这之前开发者只能通过链式then的方法处理Promise异步操作。
.then虽然解决了回调地狱的问题但是代码依旧冗余且阅读性差
await用来修饰promise对象,加了await后只有当promise执行完成后才会返回值再继续执行函数中下面的代码,只有async修饰的函数才能使用await
async function getAllFile(){
const r1=await thenFs.readFile('./files/1.txt','utf8')
const r2=await thenFs.readFile('./files/2.txt','utf8')
const r3=await thenFs.readFile('./files/2.txt','utf8')
}
上面代码中,先输出A B遇到await后其后面的代码就被视为异步执行,所以先输出C之后等异步完成后输出r1 r2 r3 D
为了防止某个耗时任务导致程序假死,JavaScript把待执行的任务分成了两类:
非耗时任务,值得是主线程上排队执行的任务
只有前一个任务执行完毕,才能执行后一个任务
JS主线程从任务队列中读取异步任务的回调函数,放到执行栈中依次执行,这个过程是循环不断地鹅,这种运行机制称为EventLoop(事件循环)
延时器只有0ms因此会立刻放入到任务队列中
js把异步任务做了进一步划分,异步任务分为两类:
宏任务
微任务
注意:promise本身是同步的,then是异步的
为什么微任务先执行?
因为当我们的主线程的代码执行完毕之后,在Event Loop执行之前,首先会尝试DOM渲染,这个时候,微任务是在DOM渲染之前执行,DOM渲染完成了之后,会执行宏任务,这也就是文章开头那段代码的解释,微任务要比宏任务更早执行!!! 那么我们的最终图示如下
父向子:props
子向父:自定义事件
兄弟组件共享:eventbus
是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享
一般只有组件之间共享的数据才有必要存储到Vuex中,对于组件中的私有数据,依旧存储在组件自身的data中即可
npm install vuex --save
import Vuex form 'vuex'
Vue.use(Vuex)
const store=new Vuex.Store({
//state中存放的就是全局共享的数据
state:{count:0}
})
new Vue({
el:'#app',
render:h=>h(app),
router,
store
})
state提供唯一的公共数据源,所有共享的数据都要统一放到Store的state中进行储存
const store=new Vuex.Store({
state:{
data1:0
}
})
组件中访问state中数据
this.$store.state.数据名
//从vuex中导入mapState函数
import {mapState} from 'vuex'
//通过导入的mapState函数将当前组件需要的全局数据映射为当前组件的computed计算属性
computed:{
...mapState(['数据名1','数据名2'])
}
注意:这里是导入computed中,若在data中则不会更新数据和视图
用于修改store中的数据
const store= new Vuex.Store({
state:{
count:0
},
mutations:{
add(state){
state.count++
}
}
})
调用mutations函数
法一
methods:{
handle(){
this.$store.commit('add')
}
}
//定义motation
const store= new Vuex.Stroe({
state:{
count:0
},
mutations:{
addN(state,step){
state.count+=step
}
}
})
//组件
methods:{
hanle(){
this.$store.commit('addN',3)
}
}
法二
//导入mapMutations函数
import {mapMutations} from 'vuex'
//将指定的mutations函数映射为当前组件的methods函数
methods:{
...mapMutations(['add','addN']),
fn1(){
this.add()
this.addN(3)
}
}
action用于处理异步任务,异步任务必须通过action执行,而不能使用mutation,但在action中还是要通过触发mutation的方式来间接更改数据
const store=new Vuex.Store({
//省略代码
mutations:{
add(state){
state.count++
}
},
actions:{
addAsync(context){
setTimeout(()=>{
context.commit('add')
},1000)
}
}
})
组件使用action
法一
methods:{
this.$store.dispatch('addAsync'[,'这里可以传参'])
}
法二
import {mapActions} from 'vuex'
methods:{
...mapActions(['addAsync','addAsyncN'])
}
用于对Store中的数据进行加工处理形成新的数据
const store=new Vuex.Store({
state:{
count:0
},
getters:{
showNum:state=>{
return state.count+1
}
}
})
组件中使用getter
法一
this.$store.getters.名称
法二
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['showNum'])
}
何为响应式?即当数据变化后,视图相应自动更新
Vue内部使用Object.defineproperty()将data中对象的每一个成员都转换为getter/setter形式(数据劫持)
当你访问对象的属性时getter会自动执行相应方法
当你修改对象的属性时setter会自动执行相应方法
getter会提醒watcher记录render用到了哪些属性(依赖收集),日后这些属性更新时也就是相应的setter执行后使得watcher去重新执行render函数进行重新渲染(发布订阅)