Vue基础入门(3)- 组件化开发、根组件和普通组件、组件的三大组成部分(结构/样式/逻辑)、组件通信、props、非父子组件通信、v-model、ref和$refs、异步更新、自定义指令、插槽和路由、ESlint代码规范及手动修复
文章目录
- Vue基础入门(3)- 组件化开发、根组件和普通组件、组件的三大组成部分(结构/样式/逻辑)、组件通信、props、非父子组件通信、v-model、ref和$refs、异步更新、自定义指令、插槽和路由、ESlint代码规范及手动修复
8 组件化开发
组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用 → 提升开发效率。
组件分类:普通组件、根组件。
比如:下面这个页面,可以把所有的代码都写在一个页面中,但是这样显得代码比较混乱,难易维护。咱们可以按模块进行组件划分
9 根组件 App.vue
9.1 根组件介绍
整个应用最上层的组件,包裹所有普通小组件
9.2 组件是由三部分构成
-
语法高亮插件
-
App.vue是三部分构成
- template:结构 (有且只能一个根元素)结构
- script: js逻辑 行为
- style: 样式 (可支持less,需要装包) 样式
-
让组件支持less
什么是less?
Less (Leaner Style Sheets 的缩写) 是一门向后兼容的 CSS 扩展语言。是css预处理语言中的一种。通过使用less可以让我们的样式更加精简方便。
-
style标签,lang=“less” 开启less功能
<template> <div class="App"> <div class="box" @click="fn"></div> </div> </template> <script> // 导出的是当前组件的配置项 // 里面可以提供 data(特殊) methods computed watch 生命周期八大钩子 export default { created () { console.log('我是created') }, methods: { fn () { alert('你好') } } } </script> <style lang="less"> /* 让style支持less 1. 给style加上 lang="less" 2. 安装依赖包 less less-loader yarn add less less-loader -D (开发依赖) */ .App { width: 400px; height: 400px; background-color: pink; .box { width: 100px; height: 100px; background-color: skyblue; } } </style>
-
装包: yarn add less less-loader -D 或者npm i less less-loader -D
-
10 普通组件的注册使用(重点为了解两种语法)
10.1 局部注册
只能在注册的组件内使用
10.1.1 步骤
- 创建.vue文件(三个组成部分)
- 在使用的组件内先导入再注册,最后使用
10.1.2 使用方式
直接将组件名当成html标签使用即可 <组件名></组件名>
10.1.3 注意
组件名规范 → 大驼峰命名法, 如 HmHeader
10.1.4 语法
App.vue中
<template>
<div class="App">
<!-- 头部组件 -->
<HmHeader></HmHeader>
<!-- 主体组件 -->
<HmMain></HmMain>
<!-- 底部组件 -->
<HmFooter></HmFooter>
<!-- 如果 HmFooter + tab 出不来 → 需要配置 vscode
设置中搜索 trigger on tab → 勾上
-->
</div>
</template>
<script>
import HmHeader from './components/HmHeader.vue'
import HmMain from './components/HmMain.vue'
import HmFooter from './components/HmFooter.vue'
export default {
components: {
// '组件名': 组件对象
HmHeader: HmHeader,
HmMain,
HmFooter
}
}
</script>
<style>
.App {
width: 600px;
height: 700px;
background-color: #87ceeb;
margin: 0 auto;
padding: 20px;
}
</style>
10.1.5 运行
如何启动Vue.app
如何查看这样一个Vue的工程在网页上的效果,这里没有package.json
这是不能查看的,
一定要在一个含有package.json的项目下:
npm install:安装需要的包,才生成node_modules
npm run serve:运行程序
具体可浏览README.md文档,有些可能明确要求需要yarn
10.2 全局注册
全局注册的组件,在项目的任何组件中都能使用
10.2.1 步骤
- 创建.vue组件(三个组成部分)
- main.js中进行全局注册
10.2.2 使用方式
直接将组件名当成html标签使用即可 <组件名></组件名>
10.2.3 注意
组件名规范 → 大驼峰命名法, 如 HmHeader
10.2.4 语法
Vue.component('组件名', 组件对象)
例如:在main.js中
// 导入需要全局注册的组件
import HmButton from './components/HmButton'
Vue.component('HmButton', HmButton)
在其任意一个vue中就可以使用HmButton了
在HmFooter.vue中:
<template>
<div class="hm-footer">
我是hm-footer
<HmButton></HmButton>
</div>
</template>
11 组件的三大组成部分(结构/样式/逻辑)
11.1 scoped解决样式冲突
组件的三大组成部分之一 → 样式
写在组件中的**样式默认情况下**会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
例如,如果我在A.vue组件中的style规定了div的格式样式,那么在在一个页面中,只要我用了组件A.vue,那么该页面下的所有div(包括B.vue C.vue)都会受到影响
-
全局样式: 默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响
-
局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件
-
组件都应该有独立的样式,推荐加scoped
11.1.1 scoped原理
- 当前组件内标签都被添加data-v-hash值 的属性
- css选择器都被添加 [data-v-hash值] 的属性选择器
造成的结果:必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到 → 达到效果
11.2 data是一个函数
在 A.vue 组件中的结构和样式部分:
<template>
<div class="base-count">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
data() {
console.log('函数执行了')
return {
count: 100,
}
},
// data: function () {
// return {
// count: 100,
// }
// }
}
</script>
一个组件的 data 选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次data 函数,得到一个新对象。
12 组件通信
组件通信,就是指组件与组件之间的数据传递
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想使用其他组件的数据,就需要组件通信
12.1 组件之间如何通信
12.1.1 组件关系分类
- 父子关系
- 非父子关系
12.1.2 通信解决方案
12.1.3 父子通信流程
- 父组件通过 props 将数据传递给子组件
- 子组件利用 $emit 通知父组件修改更新
12.1.4 父向子通信代码示例
父组件通过props将数据传递给子组件
父组件App.vue:
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 1.给组件标签,添加属性方式 赋值 -->
<Son :title="myTitle"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '父组件传递给子组件的信息',
}
},
components: {
Son,
},
}
</script>
<style>
</style>
子组件Son.vue:
<template>
<div class="son" style="border:3px solid #000;margin:10px">
<!-- 3.直接使用props的值 -->
我是Son组件 {{title}}
</div>
</template>
<script>
export default {
name: 'Son-Child',
// 2.通过props来接受
props:['title']
}
</script>
<style>
</style>
通讯示意图:
msg(父组件中data) → :title(父组件中绑定属性) → title(子组件中props) → title(子组件中插值表达式)
父向子传值步骤:
- 给子组件以添加属性的方式传值
- 子组件内部通过props接收
- 模板中直接使用 props接收的值
12.1.5 子向父通信代码示例
子组件利用 $emit 通知父组件,进行修改更新
父组件App.vue代码
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 2.父组件对子组件的消息进行监听 -->
<Son :title="msg" @changTitle="handleChange"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
msg: '父组件传递给子组件的信息',
}
},
components: {
Son,
},
methods: {
// 3.提供处理函数,提供逻辑
handleChange(newMsg) {
this.msg = newMsg
},
},
}
</script>
<style>
</style>
子组件Son.vue代码
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
我是Son组件 {{ title }}
<button @click="changeFn">修改title</button>
</div>
</template>
<script>
export default {
name: 'Son-Child',
props: ['title'],
methods: {
changeFn() {
// 通过this.$emit() 向父组件发送通知
this.$emit('changTitle','新的组件信息')
},
},
}
</script>
<style>
</style>
$emit发送通知,具体包括监听的变量和一个参数 → 监听接收到,并传递给绑定的函数,将参数传入 → 相关函数运行, 里面有处理参数的逻辑
子向父传值步骤
- $emit触发事件,给父组件发送消息通知
- 父组件监听$emit触发的事件
- 提供处理函数,在函数的性参中获取传过来的参数
13 props
- 什么是props:组件上注册的一些 自定义属性
- Props 作用:向子组件传递数据
13.1 props的使用
-
可以 传递 任意数量 的prop
-
可以 传递 任意类型 的prop
13.2 props校验
对写入props的值做一定的要求,例如:传入的范围必须是0-100之间
校验内容包括:
- 类型校验
- 非空校验
- 默认值
- 自定义校验
语法:
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
注意:
1.default和required一般不同时写(因为当时必填项时,肯定是有值的)
2.default后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值
完整代码:
<template>
<div class="base-progress">
<div class="inner" :style="{ width: w + '%' }">
<span>{{ w }}%</span>
</div>
</div>
</template>
<script>
export default {
// 1.基础写法(类型校验)
// props: {
// w: Number,
// },
// 2.完整写法(类型、默认值、非空、自定义校验)
props: {
w: {
type: Number,
required: true,
default: 0,
validator(val) {
// console.log(val)
if (val >= 100 || val <= 0) {
console.error('传入的范围必须是0-100之间')
return false
} else {
return true
}
},
},
},
}
</script>
13.3 props&data、单向数据流
13.3.1 props&data
共同点:
- 都可以给组件提供数据
区别:
- data 的数据是**自己**的 → 随便改
- prop 的数据是**外部**的 → 不能直接改,要遵循 单向数据流
- 口诀:谁的数据谁负责
13.3.2 单向数据流
父级props 的数据更新,会向下流动,影响**子组件。这个数据流动是单向的**
14 非父子通信
- event bus 事件总线
- provide&inject
14.1 event bus 事件总线
非父子组件之间,进行简易消息传递。(复杂场景→ Vuex(全局状态管理))
14.1.1 步骤
-
创建一个都能访问的事件总线 (空Vue实例)(单独的js文件)
import Vue from 'vue' const Bus = new Vue() export default Bus
-
A组件(A.vue 接受方),监听Bus的 $on事件
created () { Bus.$on('sendMsg', (msg) => { this.msg = msg }) }
-
B组件(B.vue 发送方),触发Bus的$emit事件
Bus.$emit('sendMsg', '这是一个消息')
14.1.2 代码示例
EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
BaseA.vue(接受方)
<template>
<div class="base-a">
我是A组件(接受方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
},
}
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
BaseB.vue(发送方)
<template>
<div class="base-b">
<div>我是B组件(发布方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
methods: {
sendMsgFn() {
Bus.$emit('sendMsg', '今天天气不错,适合旅游')
},
},
}
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
sendMsg就像是一个端口号
App.vue
<template>
<div class="app">
<BaseA></BaseA>
<BaseB></BaseB>
</div>
</template>
<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
export default {
components:{
BaseA,
BaseB
}
}
</script>
<style>
</style>
接收方可以修改数据吗?
在Vue的事件总线(Event Bus)通讯中,接收方是可以修改数据的。事件总线是一种在组件之间进行通信的模式,它允许一个组件触发事件,而其他组件监听并响应这些事件。
注意:事件总线模式适用于简单的通信场景,但随着应用变得更加复杂,可能会导致事件的管理和维护变得困难。对于更复杂的通信需求,你可能会考虑使用其他通信方式,如 Vuex 或其他状态管理工具。
14.2 provide&inject
跨层级共享数据
14.2.1 语法
- 父组件 provide提供数据
<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {
provide() {
return {
// 简单类型 是非响应式的
color: this.color,
// 复杂类型 是响应式的
userInfo: this.userInfo,
}
},
data() {
return {
color: 'pink',
userInfo: {
name: 'zs',
age: 18,
},
}
},
methods: {
change() {
this.color = 'red'
this.userInfo.name = 'ls'
},
},
components: {
SonA,
SonB,
},
}
</script>
- 子/孙组件 inject获取数据
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color, this.userInfo)
}
}
注意:
- provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
- 子/孙组件通过inject获取的数据,不能在自身组件内修改
什么是响应式数据,什么是非响应式数据?
在Vue中,
响应式数据是指被Vue框架追踪的数据,当这些数据发生变化时,相关的视图会自动更新。
**非响应式数据是指Vue框架无法追踪的数据,即使其值发生变化,视图也不会自动更新。**例如,如果直接给一个已经创建的对象添加新的属性,这个属性就不会是响应式的:
// 非响应式数据 this.myObject.newProperty = 'Some value';
15 v-model原理
原理:
v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写
“语法糖”(Syntactic Sugar)是一种编程语言设计中的概念,它指的是一种在语法上提供的方便的、简洁的写法,其实际上是对底层的语法结构的一种更友好的包装。语法糖并不会引入新的功能,而是使得代码更易读、更易写,提高了开发效率。
<template>
<div id="app" >
<input v-model="msg" type="text">
<!-- v-model的底层其实就是:value和 @input的简写 -->
<input :value="msg" @input="msg = $event.target.value" type="text">
</div>
</template>
$event.target获取事件源:输入框
$event.target.value获取输入框的值
$event 用于在模板中,可直接获取当前事件的形参。即这里也可以简写成
<input :value="msg" @input="msg = $event" type="text">
模板:
<template></template>
15.1 表单类组件封装(不同模块中的数据绑定)
**需求目标:**实现子组件和父组件数据的双向绑定 (实现App.vue中的selectId和子组件选中的数据进行双向绑定)
-
父传子:数据(输入的数据,待提交的数据)应该是父组件props传递过来的,v-model拆解绑定数据
在子组件中,v-model不能直接绑定父组件传过来的值props
因为v-model意味着双向绑定,但我们知道,子组件是不能够直接修改父组件中传过来的值的,如果用v-model绑定父组件中传入的props的值,会报错
所以**如果要实现父与子的“双向绑定”效果,就需要利用v-model的原理并结合子传父的语法**
-
子传父:监听输入,子传父传值给父组件修改
代码示例:
子组件:
<template>
<div>
<select :value="selectId" @change="selectCity">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>
<script>
export default {
props: {
selectId: String,
},
methods: {
selectCity(e) {
this.$emit('changeCity', e.target.value)
},
},
}
</script>
<style>
</style>
父组件:
<template>
<div class="app">
<BaseSelect
:selectId="selectId"
@changeCity="selectId = $event"
></BaseSelect>
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
}
</script>
<style>
</style>
图解:
不同的表单元素, v-model在底层的处理机制是不一样的。比如给checkbox使用v-model
底层处理的是 checked属性和change事件。
不过咱们只需要掌握应用在文本框上的原理即可
15.2 v-model简化代码
父组件通过v-model 简化代码,实现子组件和父组件数据 双向绑定
- 子组件:props通过value接收数据,事件触发 input
- 父组件:v-model直接绑定数据
上述代码中,父组件变成:
<template>
<div class="app">
<BaseSelect
v-model="selectId"
></BaseSelect>
</div>
</template>
子组件中,父组件传过来的值,一定要命名为value
<script>
export default {
props: {
value: String,
},
methods: {
selectCity(e) {
this.$emit('input', e.target.value)
},
},
}
</script>
15.3 .sync修饰符
意义:可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
简单理解:子组件可以修改父组件传过来的props值,不一定要为value
本质:.sync修饰符 就是 :属性名 和 @update:属性名 合写
//.sync写法 <BaseDialog :visible.sync="isShow" />
//完整写法 <BaseDialog :visible="isShow" @update:visible="isShow = $event" />
完整代码:
父组件:
<template>
<div class="app">
<button @click="openDialog">退出按钮</button>
<!-- isShow.sync => :isShow="isShow" @update:isShow="isShow=$event" -->
<BaseDialog :isShow.sync="isShow"></BaseDialog>
</div>
</template>
<script>
import BaseDialog from './components/BaseDialog.vue'
export default {
data() {
return {
isShow: false,
}
},
methods: {
openDialog() {
this.isShow = true
// console.log(document.querySelectorAll('.box'));
},
},
components: {
BaseDialog,
},
}
</script>
子组件:
<template>
<div class="base-dialog-wrap" v-show="isShow">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button class="close" @click="closeDialog">x</button>
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button>确认</button>
<button>取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isShow: Boolean,
},
methods:{
closeDialog(){
this.$emit('update:isShow',false)
}
}
}
</script>
图示:
16 ref和$refs
利用ref 和 $refs 可以用于 获取 dom 元素 或 组件实例
查找范围 → 当前组件内(更精确稳定)
之前用document.querySelect(‘.box’) 获取的是整个页面中的盒子
语法:
-
获取dom
-
目标标签 - 添加ref属性
<div ref="chartRef">我是渲染图表的容器</div>
-
获取时通过 $refs获取 this.$refs.chartRef 获取目标标签
mounted () { console.log(this.$refs.chartRef) }
-
-
获取组件(父组件调用子组件里面的方法)
-
目标标签 - 添加ref属性
<BaseForm ref="baseForm"></BaseForm>
-
恰当时机,在父组件(App.vue)中获取时通过 $refs获取 this.$refs.chartRef 获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
-
一个Echarts的案例
主模块:
<template>
<div class="app">
<BaseChart></BaseChart>
</div>
</template>
<script>
import BaseChart from './components/BaseChart.vue'
export default {
components:{
BaseChart
}
}
</script>
视图模块:
<template>
<div class="base-chart-box" ref="baseChartBox">子组件</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例
// document.querySelector 会查找项目中所有的元素
// $refs只会在当前组件查找盒子
// var myChart = echarts.init(document.querySelector('.base-chart-box'))
var myChart = echarts.init(this.$refs.baseChartBox)
// 绘制图表
myChart.setOption({
title: {
text: 'ECharts 入门示例',
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20],
},
],
})
},
}
</script>
演示效果:
17 异步更新 & $nextTick
引入案例:
- 点击编辑,显示编辑框
- 让编辑框,立刻获取焦点
达到效果为:
初步代码:
<template>
<div class="app">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="inp" />
<button>确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button @click="editFn">编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: '大标题',
isShowEdit: false,
editValue: '',
}
},
methods: {
editFn() {
// 显示输入框
this.isShowEdit = true
// 获取焦点
this.$refs.inp.focus()
} },
}
</script>
问题:
“显示之后”,立刻获取焦点是不能成功的!
原因:
Vue 是异步更新DOM (提升性能)
意思是会让该修改的修改完了之后,再统一更新;不会运行一行代码,更新一下
// 显示输入框
this.isShowEdit = true
// 获取焦点
this.$refs.inp.focus()
解决方案:
$nextTick:等 DOM更新后,才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例
setTimeout:
在浏览器环境下,
setTimeout
的回调函数通常是在 DOM 更新完之后执行的,而不是在 DOM 更新期间。请注意,具体的执行时间可能受到其他因素的影响,例如主线程的负载情况。但总体上说,
setTimeout
回调的执行通常是在当前任务队列的末尾,即在当前代码块执行完成之后。
所以setTimeout也可以完成上面的功能:
setTimeout(() => {
this.$refs.inp.focus()
}, 0)
但仍推荐使用this.$nextTick
18 自定义指令
18.1 指令介绍
指令分为两种,内置指令和自定义指令
-
内置指令:v-html、v-if、v-bind、v-on… 这都是Vue给咱们内置的一些指令,可以直接使用
-
自定义指令:同时Vue也支持让开发者,自己注册一些指令。这些指令被称为自定义指令
每个指令都有自己各自独立的功能
概念:自己定义的指令,可以封装一些DOM操作,扩展额外的功能
18.2 自定义指令语法
-
全局注册
//在main.js中 Vue.directive('指令名', { "inserted" (el) { // 可以对 el 标签,扩展额外功能 el.focus() } })
-
局部注册
<script> //在Vue组件的配置项中 directives: { "指令名": { inserted (el) { // 可以对 el 标签,扩展额外功能 el.focus() } } } </script>
-
使用指令
注意:在使用指令的时候,一定要先注册,再使用,否则会报错
使用指令语法: v-指令名。如:注册指令时不用加v-前缀,但使用时一定要加v-前缀
-
一定要在vue2中使用,vue3中会出现问题
指令中的配置项介绍
inserted: 被绑定元素插入父节点时调用的钩子函数
el:使用指令的那个DOM元素
**需求:**输入框自动获取焦点
代码示例:
<script>
export default {
// mounted () {
// this.$refs.inp.focus()
// }
// 2. 局部注册指令
directives: {
// 指令名:指令的配置项
focus: {
inserted (el) {
el.focus()
}
}
}
}
</script>
或者在全局中main.js:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 1. 全局注册指令
Vue.directive('focus', {
// inserted 会在 指令所在的元素,被插入到页面中时触发
inserted (el) {
// el 就是指令所绑定的元素
// console.log(el);
el.focus()
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
展示效果:
18.3 自定义指令 - 指令的值
**需求:**实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
18.3.1 语法
-
在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值
<div v-color="color">我是内容</div>
-
通过 binding.value 可以**拿到指令值**,指令值修改会 触发 update 更新函数(拿到 + 更新)
<script> export default { data () { return { color1: 'red', color2: 'orange' } }, directives: { color: { // 1. inserted 提供的是元素被添加到页面中时的逻辑 inserted (el, binding) { // console.log(el, binding.value); // binding.value 就是指令的值 el.style.color = binding.value }, // 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑 update (el, binding) { console.log('指令的值修改了'); el.style.color = binding.value } } } } </script>
-
效果展示:
18.4 自定义指令 - v-loading指令的封装
问题:实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好
需求:封装一个 v-loading 指令,实现加载中的效果
分析:
1.本质 loading效果就是一个蒙层,盖在了盒子上
2.数据请求中,开启loading状态,添加蒙层
3.数据请求完毕,关闭loading状态,移除蒙层
实现:
1.准备一个 loading类,通过伪元素定位,设置宽高,实现蒙层
2.开启关闭 loading状态(添加移除蒙层),本质只需要添加移除类即可
3.结合自定义指令的语法进行封装复用
代码实现:
1 需要安装axios包
2 使用 vue3 完成实现,vue2 总是出现问题
<template>
<div class="main">
<div class="box" v-loading="isLoading">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
<div class="box2" v-loading="isLoading2"></div>
</div>
</template>
<script>
// 安装axios => yarn add axios
import axios from 'axios'
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: [],
isLoading: true,
isLoading2: true
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
setTimeout(() => {
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
this.isLoading = false
}, 2000)
},
directives: {
loading: {
inserted (el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update (el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
}
}
</script>
19 插槽
19.1 默认插槽
默认插槽就是指没有名字的插槽。
作用:
让组件内部的一些 结构 支持 自定义
需求:将需要多次显示的对话框,封装成一个组件
问题:组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办
插槽的基本语法:
-
组件内需要定制的结构部分,改用**
<slot></slot>
**占位 -
使用组件时, **
<MyDialog></MyDialog>
**标签内部, 传入结构替换slot -
给插槽传入内容时,可以传入**纯文本、html标签、组件**
例如,在MyDialog.vue中:
19.2 后备内容(默认值)
**后备内容:**封装组件时,可以为预留的 <slot>
插槽提供后备内容(默认内容)。
语法:
显示效果:
-
外部使用组件时,不传东西,则slot会显示后备内容
-
外部使用组件时,传东西了,则slot整体会被换掉
19.3 具名插槽
需求:一个组件内有多处结构,需要外部传入标签,进行定制 (即有多个地方需要外部传入值)
**方法:**多个slot使用name属性区分名字
在MyDialog.vue中:
<slot name="插槽名"></slot>
<template>
<div class="dialog">
<div class="dialog-header">
<!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 -->
<slot name="head"></slot>
</div>
<div class="dialog-content">
<slot name="content"></slot>
</div>
<div class="dialog-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
在App.vue中:
<template v-slot:head>如果有内容的话...</template>
<template>
<div>
<MyDialog>
<!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
<template v-slot:head>
<div>我是大标题</div>
</template>
<template v-slot:content>
<div>我是内容</div>
</template>
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</MyDialog>
</div>
</template>
**简写:**v-slot写起来太长,vue给我们提供一个简单写法 v-slot → #
<template v-slot:footer>
<button>取消</button>
<button>确认</button>
</template>
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
19.4 作用域插槽
19.4.1 插槽分类
-
默认插槽
-
具名插槽
插槽只有两种,作用域插槽不属于插槽的一种分类
19.4.2 作用
作用域插槽:定义slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用
作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
19.4.3 案例-表格的渲染
需求:封装表格组件
- 对于表格的内容:父传子,动态渲染表格内容(父子组件知识点)
- 对于表格的操作:利用插槽,定制操作列。使用默认插槽(结构不同,需要定制 → 使用插槽 插槽知识点)
方案一:
<template>
<div>
<template>
<MyTable :data="list">
<button>删除</button>
</MyTable>
<MyTable :data="list2">
<button>查看</button>
</MyTable>
</template>
</div>
</template>
删除或查看需要传递相关id的值,而id数据是在子组件中,我们需要利用插槽,将数据传递回来(从MyTable.vue传递到App.vue)
方案二:
父组件中通过template #插槽名="变量名"
接收子组件(MyTable.vue)传来的数据
<template>
<div>
<MyTable :data="list">
<!-- 3. 通过template #插槽名="变量名" 接收子组件(MyTable.vue)传来的数据 -->
<template #default="obj">
<button @click="del(obj.row.id)">
删除
</button>
</template>
</MyTable>
<MyTable :data="list2">
<template #default="{ row }">
<button @click="show(row)">查看</button>
</template>
</MyTable>
</div>
</template>
子组件中通过slot标签传值到父组件
-
给 slot 标签, 以添加属性的方式传值
<slot :id="item.id" msg="测试文本"></slot>
-
所有添加的属性, 都会被收集到一个对象中
{ id: 3, msg: '测试文本' }
子组件代码:
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>
<!-- 1. 给slot标签,添加属性的方式传值 -->
<slot :row="item" msg="测试文本"></slot>
<!-- 2. 将所有的属性,添加到一个对象中 -->
<!--
{
row: { id: 2, name: '孙大明', age: 19 },
msg: '测试文本'
}
-->
</td>
</tr>
</tbody>
</table>
</template>
展示效果:
接受参数什么时候加{},什么时候不加
接受变量表示一整个对象时,可以不用加大括号;
表示对象被解构中的一部分时,需要加上大括号。
- 第一个
<template>
中,#default="obj"
,在这里整个对象被赋值给了变量obj
,所以在使用时不需要大括号。- 第二个
<template>
中,#default="{row}"
,在这里对象被解构,所以需要使用大括号将解构的变量名包裹起来。
19.5 综合案例 - MyTag组件
19.5.1 商品列表-MyTag组件抽离
19.5.2 MyTag组件控制显示隐藏
19.5.3 MyTag组件进行v-model绑定
19.5.4 封装MyTable组件-动态渲染数据
19.5.5 封装MyTable组件-自定义结构
20 路由
20.1 单页应用程序介绍
-
单页应用程序:SPA【Single Page Application】是指所有的功能都在一个html页面上实现
-
示例:
- 单页应用网站: 网易云音乐 https://music.163.com/
- 多页应用网站:京东 https://jd.com/
-
单页应用 VS 多页面应用
SEO(Search Engine Optimization,搜索引擎优化)是一种通过优化网站内容和结构,提高网站在搜索引擎中排名的技术和方法。其目的是使网站在搜索引擎结果页面(SERP)中获得更高的可见性,从而增加网站的流量和用户数量。
单页应用类网站:系统类网站 / 内部网站 / 文档类网站 / 移动端站点
多页应用类网站:公司官网 / 电商类网站
20.2 路由
单页面应用程序,之所以开发效率高,性能好,用户体验好;最大的原因就是:页面按需更新
比如当点击【发现音乐】和【关注】时,只是更新下面部分内容,对于头部是不更新的
要按需更新,首先就需要明确:访问路径和 组件的对应关系
访问路径 和 组件的对应关系如何确定呢? 路由
20.2.1 路由的介绍
生活中的路由:设备和ip的映射关系
Vue中的路由:路径和组件的映射关系
20.2.2 路由的基本使用
认识插件 VueRouter,掌握 VueRouter 的基本使用步骤
修改地址栏路径时,切换显示匹配的组件
Vue 官方的一个路由插件,是一个第三方包:https://v3.router.vuejs.org/zh/
VueRouter的使用(5+2)
固定:5个固定的步骤
-
下载 VueRouter 模块到当前工程,版本3.6.5
yarn add vue-router@3.6.5
-
main.js中引入VueRouter
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
-
创建路由对象
const router = new VueRouter()
-
注入,main.js中将路由对象注入到new Vue实例中,建立关联
new Vue({ render: h => h(App), router:router }).$mount('#app')
-
代码示例
当我们配置完以上5步之后 就可以看到浏览器地址栏中的路由 变成了 /#/的形式。表示项目的路由已经被Vue-Router管理了
两个核心步骤
-
创建需要的组件 (views目录),配置路由规则
main.js中:
-
配置导航,配置路由出口(路径匹配的组件显示的位置)
App.vue中:
<template> <div> <div class="footer_wrap"> <a href="#/find">发现音乐</a> <a href="#/my">我的音乐</a> <a href="#/friend">朋友</a> </div> <div class="top"> <!-- 路由出口 → 匹配的组件所展示的位置 --> <router-view></router-view> </div> </div> </template>
20.2.3 组件的存放目录问题
20.2.3.1 组件分类
.vue文件分为2类,都是 .vue文件(本质无区别)
-
页面组件 (配置路由规则时使用的组件)
-
复用组件(多个组件中都使用到的组件)
20.2.3.2 存放目录
分类开来的目的就是为了 更易维护
-
src/views文件夹
页面组件 - 页面展示 - 配合路由用
-
src/components文件夹
复用组件 - 展示数据 - 常用于复用
20.2.4 路由的封装抽离
问题:所有的路由配置都在main.js中合适吗?
目标:将路由模块抽离出来(放在名为router文件夹下一个单独的index.js)。 好处:拆分模块,利于维护
路径简写:
脚手架环境下 @指代src目录,可以用于快速引入组件
回顾:什么叫脚手架环境?
在Vue.js中,脚手架环境指的是使用Vue官方提供的脚手架工具来创建和管理Vue项目的开发环境。Vue CLI(Command Line Interface,命令行界面)是Vue.js官方提供的脚手架工具,它提供了一套开发工具和标准的项目结构,能够帮助开发者快速搭建、开发和部署Vue.js项目。
Vue脚手架环境提供了一套完整的开发工具和流程,能够帮助开发者快速搭建、开发和部署Vue.js项目,提高开发效率和项目质量。
封装抽离前后对比
封装前:
封装后:
20.3 声明式导航
20.3.1 导航链接
需求:实现导航高亮效果
如果使用a标签进行跳转的话,需要给当前跳转的导航加样式,同时要移除上一个a标签的样式,麻烦。
解决方案:vue-router 提供了一个全局组件 router-link (取代 a 标签)
-
能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
-
能高亮,默认就会提供高亮类名,可以直接设置高亮样式
使用router-link跳转后,我们发现。当前点击的链接默认加了两个class的值
router-link-exact-active
和router-link-active
我们可以给任意一个class属性添加高亮样式即可实现功能
-
代码示例
<template> <div> <div class="footer_wrap"> <router-link to="/find">发现音乐</router-link> <router-link to="/my">我的音乐</router-link> <router-link to="/friend">朋友</router-link> </div> <div class="top"> <!-- 路由出口 → 匹配的组件所展示的位置 --> <router-view></router-view> </div> </div> </template> <script> export default {}; </script> <style> body { margin: 0; padding: 0; } .footer_wrap { position: relative; left: 0; top: 0; display: flex; width: 100%; text-align: center; background-color: #333; color: #ccc; } .footer_wrap a { flex: 1; text-decoration: none; padding: 20px 0; line-height: 20px; background-color: #333; color: #ccc; border: 1px solid black; } .footer_wrap a.router-link-active { background-color: #be2121; } .footer_wrap a:hover { background-color: #555; } </style>
20.3.2 两个类名
当我们使用<router-link></router-link>
跳转时,自动给当前导航加了两个类名
-
router-link-active
模糊匹配(用的多)
to=“/my” 可以匹配 /my /my/a /my/b …
只要是以/my开头的路径 都可以和 to="/my"匹配到
-
router-link-exact-active
精确匹配
to=“/my” 仅可以匹配 /my
20.3.3 自定义类名
router-link的两个高亮类名太长了
解决方案:
我们可以在创建路由对象时,额外配置两个配置项即可。 linkActiveClass
和linkExactActiveClass
const router = new VueRouter({
routes: [...],
linkActiveClass: "类名1",
linkExactActiveClass: "类名2"
})
20.3.4 路由传参
在跳转路由时,进行传参
比如:现在我们在搜索页点击了热门搜索链接,跳转到详情页,需要把点击的内容带到详情页,改怎么办呢?
有两种传参方式:
- 查询参数传参
- 动态路由传参
20.3.4.1 查询参数传参
-
如何传参?
<router-link to="/path?参数名=值"></router-link>
-
如何接受参数
固定用法:
$router.query.参数名
代码示例:
Home.vue中:
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search?key=百度">百度</router-link>
<router-link to="/search?key=美团">美团</router-link>
<router-link to="/search?key=字节跳动">字节跳动</router-link>
</div>
</div>
</template>
Search.vue中:
<template>
<div class="search">
<p>搜索关键字: {{ $route.query.key }} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
演示效果:
20.3.4.2 动态路由传参
-
配置动态路由
动态路由后面的参数可以随便起名,但要有语义
const router = new VueRouter({ routes: [ ..., { path: '/search/:words', component: Search } ] })
-
配置导航链接
to=“/path/参数值”
<template> <div class="home"> <div class="logo-box"></div> <div class="search-box"> <input type="text"> <button>搜索一下</button> </div> <div class="hot-link"> 热门搜索: <router-link to="/search/百度">百度</router-link> <router-link to="/search/美团">美团</router-link> <router-link to="/search/字节跳动">字节跳动</router-link> </div> </div> </template>
-
对应页面组件接受参数
$route.params.参数名
params后面的参数名要和动态路由配置的参数保持一致
<template> <div class="search"> <p>搜索关键字: {{ $route.params.words }} </p> <p>搜索结果: </p> <ul> <li>.............</li> <li>.............</li> <li>.............</li> <li>.............</li> </ul> </div> </template>
-
动态路由参数的可选符
如果search后面不一定需要传递参数,应该怎样定义动态路由语法呢?
/search/:words 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符"?"
const router = new VueRouter({ routes: [ ... { path: '/search/:words?', component: Search } ] })
20.3.4.3 查询参数传参 VS 动态路由传参
-
查询参数传参 (比较适合传多个参数)
- 跳转:to=“/path?参数名=值&参数名2=值”
- 获取:$route.query.参数名
-
动态路由传参 (优雅简洁,传单个参数比较方便)
- 配置动态路由:path: “/path/:参数名”
- 跳转:to=“/path/参数值”
- 获取:$route.params.参数名
注意:动态路由也可以传多个参数,但一般只传一个
20.4 Vue路由
20.4.1 重定向
问题:网页打开时, url 默认是 / 路径,未匹配到组件时,会出现空白
解决:重定向 → 匹配 / 后, 强制跳转 /home 路径
语法:
{ path: 匹配路径, redirect: 重定向到的路径 },
比如:
{ path:'/' ,redirect:'/home' }
在index.js中:
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
]
})
export default router
20.4.2 404
**作用:**当路径找不到匹配时,给个提示页面
语法:
path: “*” (任意路径) – 前面不匹配就命中最后这个
import NotFind from '@/views/NotFind'
const router = new VueRouter({
routes: [
...
{ path: '*', component: NotFind } //最后一个
]
})
在index.js中:
import Home from '@/views/Home'
import Search from '@/views/Search'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
export default router
其中,NotFound.vue类似于:
<template>
<div>
<h1>404 Not Found</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
20.4.3 模式设置
**问题:**路由的路径看起来不自然, 有#,能否切成真正路径形式?
- hash路由(默认) 例如: http://localhost:8080/#/home
- history路由(常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持,开发环境webpack给规避掉了history模式的问题)
语法:
const router = new VueRouter({
mode:'histroy', //默认是hash
routes:[]
})
注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
示例:
// 创建了一个路由对象
const router = new VueRouter({
// 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
mode: 'history',
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
20.4.4 编程式导航
编程式导航:用JS代码来进行跳转。(点击按钮进行跳转的那种跳转)
20.4.4.1 两种路由跳转方式
两种语法:
-
path 路径跳转 (简易方便)
特点:简易方便
//在vue的行为(script)中定义 //简单写法 this.$router.push('路由路径') //完整写法 this.$router.push({ path: '路由路径' })
-
name 命名路由跳转 (适合 path 路径长的场景)
特点:适合 path 路径长的场景
语法:
-
路由规则,必须配置name配置项
{ name: '路由名', path: '/path/xxx', component: XXX },
-
通过name来进行跳转
this.$router.push({ name: '路由名' })
-
-
代码示例
<script> export default { name: 'FindMusic', methods: { goSearch () { // 1. 通过路径的方式跳转 // (1) this.$router.push('路由路径') [简写] // this.$router.push('/search') // (2) this.$router.push({ [完整写法] // path: '路由路径' // }) this.$router.push({ path: '/search' }) // 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径 // this.$router.push({ // name: '路由名' // }) this.$router.push({ name: 'search' }) } } } </script>
20.4.4.2 path路径跳转传参
参数传递有两种:
1.查询参数
2.动态路由传参
两种跳转方式,对于两种传参方式都支持:
① path 路径跳转传参
② name 命名路由跳转传参
path路径跳转传参(query传参):
//简单写法
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
//完整写法
this.$router.push({
path: '/路径',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
接受参数的方式依然是:$route.query.参数名
path路径跳转传参(动态路由传参):(此时要配置动态路由,才能动态路由传参)
//简单写法
this.$router.push('/路径/参数值')
//完整写法
this.$router.push({
path: '/路径/参数值'
})
接受参数的方式依然是:$route.params.参数值
20.4.4.3 name命名路由传参
name 命名路由跳转传参 (query传参):
this.$router.push({
name: '路由名字',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
name 命名路由跳转传参 (动态路由传参):
this.$router.push({
name: '路由名字',
params: {
参数名: '参数值',
}
})
代码示例(连同上面path路径):
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch () {
// 1. 通过路径的方式跳转
// (1) this.$router.push('路由路径') [简写]
// this.$router.push('路由路径?参数名=参数值')
this.$router.push('/search')
this.$router.push(`/search?key=${this.inpValue}`)
this.$router.push(`/search/${this.inpValue}`)
// (2) this.$router.push({ [完整写法] 更适合传参
// path: '路由路径'
// query: {
// 参数名: 参数值,
// 参数名: 参数值
// }
// })
this.$router.push({
path: '/search',
query: {
key: this.inpValue
}
})
this.$router.push({
path: `/search/${this.inpValue}`
})
// 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
// this.$router.push({
// name: '路由名'
// query: { 参数名: 参数值 },
// params: { 参数名: 参数值 }
// })
this.$router.push({
name: 'search',
// query: {
// key: this.inpValue
// }
params: {
words: this.inpValue
}
})
}
}
}
</script>
20.4.5 案例—面经基础版
21 ESlint代码规范及手动修复
ESLint:是一个代码检查工具,用来检查你的代码是否符合指定的规则 (你和你的团队可以自行约定一套规则)。
在创建vue项目时,会询问是否选择eslint的风格
在创建项目时,我们使用的是 JavaScript Standard Style 代码风格的规则。
21.1 JavaScript Standard Style 规范说明
https://standardjs.com/rules-zhcn.html
下面是这份规则中的一小部分:
- 字符串使用单引号 – 需要转义的地方除外
- 无分号 – 这没什么不好。不骗你!
- 关键字后加空格
if (condition) { ... }
- 函数名后加空格
function name (arg) { ... }
- 坚持使用全等
===
摒弃==
一但在需要检查null || undefined
时可以使用obj == null
- …
21.2 代码规范错误
如果你的代码不符合standard的要求,eslint会提示你。
下面我们在main.js中随意做一些改动:添加一些空行,空格。
import Vue from 'vue'
import App from './App.vue'
import './styles/index.less'
import router from './router'
Vue.config.productionTip = false
new Vue ( {
render: h => h(App),
router
}).$mount('#app')
按下保存代码之后:
你将会看在控制台中输出如下错误:
21.3 手动修正
根据错误提示来一项一项手动修正。
如果你不认识命令行中的语法报错是什么意思,你可以根据错误代码(func-call-spacing, space-in-parens,…)去 ESLint 规则列表中查找其具体含义。
打开 ESLint 规则表,使用页面搜索(Ctrl + F)这个代码,查找对该规则的一个释义。
21.4 通过eslint插件来实现自动修正
- eslint会自动高亮错误显示
- 通过配置,eslint会自动帮助我们修复错误
-
安装
-
配置
// 当保存的时候,eslint自动帮我们修复错误 "editor.codeActionsOnSave": { "source.fixAll": true }, // 保存代码,不自动格式化 "editor.formatOnSave": false
-
注意
- eslint的配置文件必须在根目录下,这个插件才能才能生效。打开项目必须以根目录打开,一次打开一个项目
- 使用了eslint校验之后,把vscode带的那些格式化工具全禁用了 Beatify
-
settings.json 参考
{ "window.zoomLevel": 2, "workbench.iconTheme": "vscode-icons", "editor.tabSize": 2, "emmet.triggerExpansionOnTab": true, // 当保存的时候,eslint自动帮我们修复错误 "editor.codeActionsOnSave": { "source.fixAll": true }, // 保存代码,不自动格式化 "editor.formatOnSave": false }