Web前端笔记-14、Vue-工程化开发、组件化、组件注册、组件通信子传父父传子、props校验、EventBus、依赖注入、ref refs、sync修饰符、异步更新 & \$nextTick
工程化开发
真正开发使用。
核心包传统开发模式:基于 html / css /js 文件,直接引入核心包,开发 Vue。
工程化开发模式:基于构建工具(例如:webpack)的环境中开发 Vue。
工程化开发和脚手架Vue CLI
Vue CLI是 Vue 官方提供的一个全局命令工具。
可以帮助我们快速创建一个开发 Vue 项目的标准化基础架子。【集成了 webpack 配置】
好处:
- 开箱即用,零配置
- 内置 babel 等工具
- 标准化
使用步骤:
- 全局安装(只需安装一次即可)
yarn global add @vue/cli或者npm i @vue/cli -g - 查看vue/cli版本:
vue --version - 创建项目架子:
vue create project-name -m npm(项目名不能使用中文) - 启动项目:
yarn serve或者npm run serve(命令不固定,找package.json) - 安装插件:vue-Official ,Vue3 snippets

main.js入口,将组件放入html的#app中。
一些细节
组件.vue起名和3个组成部分。
起名规范:组件名必须是多词格式。小驼峰、大驼峰或中横线格式。
补充:文件名不符合多词格式,可以在export default中加属性name:'多词'即可。
template要求:Vue2有且只能有一个根元素。Vue3可以。
script要求:vue代码都要写到default对象里面,包括methods, computed, watch。
注意:data的写法发生变化。
一个组件的 data 选项必须是一个函数。保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。
<script>
export default {
data() {
return {}
},
methods: {},
computed: {},
watch: {},
created() {},
mounted() {},
}
</script>
如何支持less:style的lang属性值改为"less" 加上scoped属性。安装包:npm i less less-loader -D
组件化
组件化:一个页面可以拆分成—个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用,提升开发效率。
组件分类:普通组件(层层调用,所有组件都要最终放入根组件)、根组件(App.vue)。
形成组件树。
所有普通组件放入components中。
组件注册
普通组件的注册使用
组件注册的两种方式:
- 局部注册:只能在注册的组件内使用
- 创建.vue 文件(三个组成部分)
- 在使用的组件内script中导入并注册
<template>
<div class="app">
<MyHeader></MyHeader>
<TestMain></TestMain>
<MyFooter></MyFooter>
</div>
</template>
<script>
/**
* 局部注册组件
* 1. 导入
* 2. 注册组件 -- 注册成html标签(标签就代表组件了)
* 3. 页面中使用
*/
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import TestMain from './components/MyMain.vue'
export default {
// 注册组件
components: {
// 标签名: 导入的变量名
MyHeader: MyHeader,
TestMain: TestMain,
MyFooter: MyFooter,
},
data() {
return {}
},
}
</script>
- 全局注册:所有组件内都能使用,适用于很多很多组件都要用
- 创建.vue 文件(三个组成部分)
- main.js 中进行全局注册
// 全局注册组件
import TestButton from './components/TestButton.vue'
Vue.component('TestButton', TestButton)
scoped解决样式冲突问题
默认情况:写在组件中的样式会 全局生效 — 因此很容易造成多个组件之间的样式冲突问题。
- 全局样式:默认组件中的样式会作用到全局
- 局部样式:可以给组件加上scoped 属性,可以让样式只作用于当前组件
scoped原理?
- 当前组件内标签都被添加 data-v-hash值 的属性
- css选择器都被添加
[data-v-hash值]的属性选择器
最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到。
组件通信
组件通信,就是指组件与组件之间的数据传递。
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想用其他组件的数据,使用组件通信。
组件的关系
组件关系分为:父子关系和非父子关系。
父子组件之间的数据传递(通信)
- 父传子
<template>
<div class="App">
<!--
父传子语法:
父:通过自定义属性的方式,向子组件传递数据
子:通过props接收传递来的数据
-->
<SingleTest v-for="item in goodsList" :key="item.id" :obj="item" />
</div>
</template>
<template>
<div class="good">
<h3>{{ obj.name }}</h3>
<p>商品单价:{{ obj.price }}</p>
<p>商品描述:{{ obj.info }}</p>
</div>
</template>
<script>
export default {
props: ['obj'],
}
</script>
- 子传父
data与prop 共同点:都可以给组件提供数据。
区别:
- data 的数据是自己的 - 随便改
- prop 的数据是外部的 - 子只使用,不能直接改父数据,要遵循单向数据流。改就要子通知父,父组件自己去改数据。
单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。
谁的数据谁负责。
语法:子如何通知父
- 子使用内置方法通知:
$emit('自定义事件名', 传的数据) - 父在引用子组件的标签上自定义事件:
@自定义事件名="方法" - 父在methods中定义方法并处理数据:
方法(val){//val就是接收到的数据}
<button @click="$emit('down', obj.id)">砍一刀</button>
<SingleTest
v-for="item in goodsList"
:key="item.id"
:obj="item"
@down="subAlittle"
/>
methods: {
subAlittle(val) {
let row = this.goodsList.find((item) => item.id === val)
row.price -= 0.1
},
},
props校验
父传子,父组件中子标签自定义属性,子组件中props数组接收使用。
但如果需要对props进行校验,就需要写成对象格式。
写成对象是因为要写配置项进行校验。
required: true -------- 表示父组件必须传递这个值,没传就会报错。
type: String | Number | Object | Array | BooLean | Function
type: [String, Number]-------类型校验
default: ''-----默认值,默认值如果是对象或数组,要写成函数格式,return默认值
<script>
export default {
name: 'SonPage',
props: {
age: {
required: true,
},
list: {
default: () => {
return []
},
},
visible: {
type: Boolean,
},
say: Function, // 只校验类型
},
}
</script>
v-model原理
v-model 可以拆分成 :value + @input
<input type="text" :value="uname" @input="changeValue" />
需求:实现输入框组件的v-model
<MyInput></MyInput>
如果直接使用v-model是不会双向同步的。
v-model 必须拆分成 :value + @input
:value="uname"相当于父传子的第一步:定义属性。然后在子中props接收value,应用:value="value",完成父传子。
@input相当于子传父的最后一步。在子中添加事件@input="$emit('input', $event.target.value)",子给父的input发通知,事件名为input。父的@input="changeName"的changeName接收到$event.target.value,赋值给this.uname完成子传父。
然后再把组件中的:value + @input换回成v-model="uname"
组件的v-model需要子组件的配合。子组件需要接收value属性,并触发input事件。
<template>
<div>
<h3>需求1:不用v-model实现双向绑定</h3>
<input type="text" :value="uname" @input="changeValue" />
<hr />
<h3>需求2:实现输入框组件的v-model</h3>
<!-- <MyInput :value="uname" @input="changeName"></MyInput> -->
<MyInput v-model="uname"></MyInput>
<hr />
<h3>需求3:实现下拉框组件的v-model</h3>
<MySelect v-model="address"></MySelect>
</div>
</template>
<script>
import MyInput from './components/MyInput.vue'
import MySelect from './components/MySelect.vue'
export default {
data() {
return {
uname: 'zhangsan111',
address: '上海',
}
},
components: {
MyInput,
MySelect,
},
methods: {
changeValue(e) {
this.uname = e.target.value
// console.log(e.target.value)
},
// changeName(val) {
// this.uname = val
// },
// changeAddress(val) {
// this.address = val
// },
},
}
</script>
<style lang="less" scoped></style>
<template>
<select :value="value" @change="$emit('input', $event.target.value)">
<option value="北京">北京市</option>
<option value="上海">上海市</option>
<option value="广州">广州市</option>
<option value="深圳">深圳市</option>
</select>
</template>
<script>
export default {
name: 'MySelect',
props: ['value'],
}
</script>
<style lang="less" scoped></style>
sync修饰符
还是父传子子传父。
:属性.sync="变量"
等同于:属性="变量" + @update:属性="xxx"
<template>
<div>
<button @click="visible = true">删除</button>
<MyDialog :visible.sync="visible"></MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
data() {
return {
visible: false,
}
},
components: {
MyDialog,
},
}
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
<template>
<div class="dialog" v-show="visible">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close" @click="$emit('update:visible', false)">✖️</span>
</div>
<div class="dialog-content">我是文本内容</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: ['visible'],
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
.dialog {
width: 470px;
height: 230px;
padding: 0 25px;
background-color: #ffffff;
margin: 40px;
border-radius: 5px;
}
.dialog-header {
height: 70px;
line-height: 70px;
font-size: 20px;
border-bottom: 1px solid #ccc;
position: relative;
}
.dialog-header .close {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
}
.dialog-content {
height: 80px;
font-size: 18px;
padding: 15px 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
.dialog-footer button {
width: 65px;
height: 35px;
background-color: #ffffff;
border: 1px solid #e1e3e9;
cursor: pointer;
outline: none;
margin-left: 10px;
border-radius: 3px;
}
.dialog-footer button:last-child {
background-color: #007acc;
color: #fff;
}
</style>
组件通信-EventBus
了解。非父子组件通信。
1.作用
非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)
2.步骤
- 创建一个都能访问的事件总线 (空Vue实例)
import Vue from 'vue' const Bus = new Vue() export default Bus - A组件(接受方),监听Bus的 $on事件
created () { Bus.$on('sendMsg', (msg) => { this.msg = msg }) } - B组件(发送方),触发Bus的$emit事件
Bus.$emit('sendMsg', '这是一个消息')

3.代码示例
<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: '',
}
},
}
</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>发送消息</button>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
}
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
<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>
组件通信-依赖注入
了解。非父子间组件通信。
1.作用
跨层级共享数据
2.场景

3.语法
- 父组件 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)
}
}
4.注意
- provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
- 子/孙组件通过inject获取的数据,不能在自身组件内修改
ref与refs
找页面元素、组件。
利用ref 和 $refs可以用于 获取 dom 元素 或 组件实例
特点:查找范围 → 当前组件内(更精确稳定 qs找整个页面中的)
找到组件可以调用组件中的方法 this.$refs.test.方法()
相同的ref只找最后一个,但v-for循环出来的都会找到。
- 首先,给要获取的盒子添加ref属性
<div ref="chartRef">我是渲染图表的容器</div>
- 然后,获取时通过
$refs获取this.$refs.chartRef获取
mounted () {
console.log(this.$refs.chartRef)
}
<MyForm ref="myform"></MyForm
<button @click="$refs.myform.resetForm()">重置</button>
异步更新 & $nextTick
编辑标题, 编辑框自动聚焦
- 点击编辑,显示编辑框
- 让编辑框,立刻获取焦点
// 显示输入框
this.isShowEdit = true
// 获取焦点
this.$refs.inp.focus()
"显示之后",立刻获取焦点是不能成功的!
原因:Vue 是异步更新DOM (提升性能)
$nextTick:等 DOM更新后,才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

浙公网安备 33010602011771号