工程化开发模式
基于构建工具(例如:webpack、vite、rollup等)的环境中开发 Vue。
Vue CLI是Vue官方提供的一个全局命令工具。可以快速创建一个集成了webpack配置的用于开发Vue的标准化基础架子。
- 使用步骤:
- 安装
vue脚手架:npm i @vue/cli -g或yarn global add @vue/cli - 创建项目:
vue create project-name(项目名) - 启动项目:
npm run serve或yarn serve(package.json文件中查找运行命令)
- 安装
组件化开发:一个页面可以拆分成
一个个的组件,每个组件都有着自己独立的结构、样式、行为。
好处:便于
维护,利于复用→ 提升开发效率
<template> // 结构
<div class="app"></div>
</template>
<script> // 行为
export default {
data() {
return {}
}
}
</script>
<style lang="less" scoped></style> // 样式
// 开启less 样式预处理。需要装对应的包 npm i less less-loader
// scoped 解决样式冲突,
// 添加 scoped 后,style内的样式只作用于当前.vue文件
// 不添加,则默认样式作用于全局
data
一个组件的data选项必须是一个函数 → 保证每个组件实例,维护 独立 的一份数据对象。
原理:每次创建新的组件实例,都会新执行一次 data 函数,得到一个新的对象
scoped原理:
1、当前组件内标签都被添加 data-v-hash 值的属性
2、css选择器都被添加 [data-v-hash]值 的属性选择器
最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到
组件注册
- 局部注册:只能在注册的组件内使用
- 创建
.vue文件 - 在使用的组件内
导入并注册
//使用 <组件对象 /> // 导入 import 组件对象 from '文件路径' export default { // 注册 components: { 组件对象 } } - 创建
- 全局注册:所有组件内都能使用
- 创建
.vue文件 main.js中进行全局注册
// 导入 import 组件对象1 from '文件路径' import 组件对象2 from '文件路径' // 调用 Vue.component 进行全局注册 Vue.component('组件名1', 组件对象1) Vue.component('组件名2', 组件对象2) - 创建
组件通信
- 概念:指
组件与组件之间的数据传递。- 组件的数据是
独立的,无法直接访问其他组件的数据 - 想用其他组件的数据 → 组件通信
- 组件的数据是
组件关系分类:
- 父子关系
- 非父子关系
组件通信解决方案:
方案一: props 与 $emit
适用于 父子组件间 的通信
父组件 通过
props将数据传递给 子组件。注意: props只读,不可以被修改(单向数据流)子组件 利用
$emit通知 父组件修改更新
单向数据流:父级prop的数据更新,会向下流动,影响子组件。
props定义:
组件上 注册的一些 自定义属性
props特点:
1、可以传递任意数量的prop
2、可以传递任意类型的prop
props接收语法:
// 写法1:
props: [ 'msg' ]
// 写法2: 添加数据类型
props: { msg: String }
// 写法3:添加数据校验
props: {
校验的属性名:{
type: 类型,
required: true, // 是否必填
default: 默认值,
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
}
props传值
$emit发送消息通知给父组件,父组件通过监听事件,提供对应的处理函数,来更新数据
方案二、event bus 事件总线
适用于 非父子组件间,简易消息传递(复杂场景 → vuex)
1、创建一个都能访问到的事件总线(空Vue实例)→ `utils/EventBus.js`
import Vue from 'vue';
const Bus = new Vue();
export default Bus;
2、组件A(接收方),监听`Bus实例`的事件
created () {
Bus.$on('sendMsg', (msg) => {
this.msg = msg;
})
}
3、组件B(发送方),触发`Bus实例`的事件
Bus.$emit('sendMsg', '这是一个消息')
方案三、provide & inject
适用于 跨层级组件间 的通信。
子组件不限嵌套层级,只要调用
inject即可注入provide中提供的数据。
1、 父组件 `provide` 提供数据
export default {
provide () {
return {
// 普通类型(非响应式)
color: this.color,
// 复杂类型(响应式)
userInfo: this.userInfo
}
}
}
2、子/孙组件 `inject`注入取值使用
export default {
inject: ['color', 'userInfo'],
created() {
console.log(this.color, this.userInfo)
}
}
方案四、vuex
通用解决方案,适合复杂业务场景,包括 兄弟组件、跨级组件间的通信
v-model 原理
- 原理:本质是一个
语法糖。例如:在输入框上,就是:value属性和@input事件的 合写
因此,父子组件中传值时,可以通过
v-model来简化代码,给组件直接绑定数据。相当于,给子组件传递了
:value和@input。本质上,实现了
子组件 与 父组件数据 的双向绑定
- 作用:提供数据的双向绑定
- 数据变,视图跟着变
:value - 视图变,数据跟着变
@input
<template> <div id='app'> <input type='text' v-model='msg' /> <input type='text' :value='msg' @input='msg = $event.target.value' /> </div> </template>注意:
$event用于模板中,用于获取事件的形参。 - 数据变,视图跟着变
.sync 修饰符
- 作用:可以实现
子组件 与 父组件数据 的双向绑定, 简化代码 - 特点: 与
v-model相比,prop属性名可以自定义,非固定的value - 本质:就是
:属性名和@update:属性名的合写
// 父组件
<SonComponent :visible.sycn='isShow' />
// 等价于
<SonComponent :visible='isShow' @update:visible=“isShow = $event” />
// 子组件
props: {
visible: Boolean
},
this.$emit('update:visible', false)
ref & $refs
- 作用:利用
ref和$refs可以获取dom元素或组件实例- 用于
普通 DOM 元素上,引用的是DOM元素
<div ref='elementRef'>获取dom元素</div> // 使用 this.$refs.elementRef- 用在
组件上,引用指向组件实例,可以通过实例直接调用组件的方法或访问数据
<SonComponent ref='componentRef' /> // 使用 this.$refs.componentRef.组件的方法() - 用于
插槽
-
默认插槽
作用:让组件内部的一些
结构支持自定义。场景:当组件内
某一部分结构不确定,想要自定义时,可以用插槽slot占位封装语法:
1、组件内需要定制的结构部分,改用`<slot></slot>`占位 <template> <div class='model-component'> <div>标题</div> <slot></slot> </div> </template> 2、使用组件时,`<ModelComponent></ModelComponent>`标签内部 传入的`具体结构`替换 `<slot>`插槽-后备内容:也叫插槽的默认值。封装组件时,可以为
slot插槽提供默认内容语法: 在
<slot>标签内,放置内容,作为默认显示内容效果: 1、使用组件时,不传内容,则显示
slot中的默认内容2、使用组件时,传内容,则
slot标签整体被替换 -
具名插槽
作用:一个组件内有多处结构,需要外部传入标签,进行定制。
语法:
1、多个
slot使用name属性区分名字2、
template配合v-slot:名字来分发对应标签3、
v-slot:插槽名可以简化为#插槽名1、区分名字 <template> <div> <div class='model-header'> <slot name='header'></slot> </div> <div class='model-content'> <slot name='content'></slot> </div> <div class='model-footer'> <slot name='footer'></slot> </div> </div> </template> 2、使用 <ModelComponent> <template v-slot:header> 替换header 插槽 </template> <template v-slot:content> 替换content 插槽 </template> <template #footer> 替换 footer 插槽 <button>按钮</button> </template> </ModelComponent> -
作用域插槽
作用:定义
slot插槽时,可以给插槽绑定数据,将来使用组件时可以用语法:
1、给
slot标签,以添加属性的方式传值。<slot :id="item.id" msg='测试文本'></slot>2、所有添加的属性,都会被收集到一个对象中。
obj = { id: 3, msg: '测试文本' }3、在
template中,通过#插槽名 = "obj"接收,默认插槽名为default<ModelComponent :list="list"> <template #default="obj"> <button @click='del(obj.id)'>删除</button> </template> </ModelComponent>
Vue异步更新 & $nextTick
需求:编辑标题,编辑框自动聚焦
1、点击编辑,显示编辑框
2、让编辑框,`立刻自动获取焦点` (`autofocus`在`safari`浏览器中存在兼容问题)
// 错误实现方式: 编辑框显示后,并没有自动获取焦点
this.isShowEdit = true;
this.$refs.inp.focus()
原因 → `Vue是异步更新DOM`
// 正确解决方式
this.$nextTick(() => {
this.$refs.inp.focus()
})
$nextTick
等DOM更新后,才会触发执行此方法中的函数体。即:想要在DOM 更新完成之后做某件事,可以使用 $nextTick
-
语法:
this.$nextTick(函数体) -
应用场景:
- 在
created()钩子函数中进行DOM操作,一定要放在$nextTick中。
在
created()钩子函数执行的时候,DOM并未进行任何渲染- 在
数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进$nextTick的函数体中。
- 在