vue基础 组件通信(二)
自定义指令#
啥是自定义指令?#
在前端开发领域,以前的通用框架是jQuery,jQuery以及基于jQuery构建的通用组件形成了一个庞大的生产系统。现在的通用框架是Angular、React和Vue,每个框架都需要基于自身构建新的组件库,自定义指令好就好在:原先的那些通用组件,无论是纯js的也好,基于jQuery的也好,都可以拿来主义直接吸收,而不需要改造或重构,自定义指令可以很方便的将大量重复的事情通过一个简短的指令来实现.
-
作用:进行DOM操作
-
使用场景:对纯 DOM 元素进行底层操作,比如:文本框获得焦点
-
两种指令:1 全局指令 2 局部指令
自定义全局指令#
- 作用:定义一个指令可以全局通用
- 关键code: Vue.directive()
- 建议:最好是单独创建一个文件然后引入到main.js文件中单独管理
// 第一个参数:指令名称
// 第二个参数:配置对象,指定指令的钩子函数
Vue.directive('directiveName', {
// bind中只能对元素自身进行DOM操作,而无法对父级元素操作
// 只调用一次 指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind( el,binding, vnode ) {
// 参数详解
// el:指令所绑定的元素,可以用来直接操作 DOM 。
// binding:一个对象,包含以下属性:
// name:指令名,不包括 v- 前缀。
// value:指令的绑定值,等号后面的值 。
// oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
// expression:字符串形式的指令表达式 等号后面的字符串 形式
// arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
// modifiers:指令修饰符。例如:v-directive.foo.bar中,修饰符对象为 { foo: true, bar: true }。
// vnode:Vue 编译生成的虚拟节点。。
// oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
},
// inserted这个钩子函数调用的时候,当前元素已经插入页面中了,也就是说可以获取到父级节点了
inserted ( el,binding, vnode ) {},
// DOM重新渲染前
update(el,binding, vnode,oldVnode) {},
// DOM重新渲染后
componentUpdated ( el,binding, vnode,oldVnode ) {},
// 只调用一次,指令与元素解绑时调用
unbind ( el ) {
// 指令所在的元素在页面中消失,触发
}
})
// 简写 如果你想在 bind 和 update 时触发相同行为,而不关心其它的钩子:
Vue.directive('自定义指令名', function( el, binding ) {})
// 例:
Vue.directive('color', function(el, binding) {
el.style.color = binging.value
})
// 使用 注意直接些会被i成data中的数据“red” 需要字符串则嵌套引号"'red'"
<p v-color="'red'"></p>
※建议用如下方法来组织全局组件
-
1.首先创建一个
directive.js
文件然后编写全局的自定义组件.例如我想定义一个直接修改Dom颜色和文字大小的自定义组件export default (Vue) => { Vue.directive('dColor', { inserted: function (el, binding) { el.style.color = binding.value; } }); Vue.directive('dFont', { inserted: function (el, binding) { el.style.fontSize = binding.value + "px"; } }); }
-
2.在main.js文件中引入
directive.js
文件,并使用Vue.use(directive)调用她import Vue from 'vue'; import App from './App.vue'; import directive from './directive'; Vue.config.productionTip = false; Vue.use(directive); //全局使用directive文件 new Vue({ render: h => h(App), }).$mount('#app')
-
3.在你想使用的地方直接调用你定义好的组件名,一定要加v-哦!
<template> <div class="hello"> <p v-dColor="'red'"> 我是全局定义的组件修改颜色值</p> <p v-dFont="'50'"> 我是全局定义的组件可以修改大小</p> </div> </template>
自定义局部指令#
- 作用:定义一个指令,只能局部组件使用
- 使用场景:组件中经常重用的某些操作Dom的方法,仅在在这一个组件中使用
- 关键code钩子: directives: 写一个局部指令,定义一个input自动焦点的指令
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
directives: {
// 自定义指令的名字
autoFocus: {
// 钩子函数,被绑定元素插入父节点时调用 (父节点存在即可调用,不必存在于 document 中)。
inserted (el) {
el.focus();
console.log('inserted');
},
// 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
bind () {
console.log('bind');
},
// 所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。
// 指令的值可能发生了改变也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
updata () {
console.log('updata');
},
// 所在组件的 VNode 及其孩子的 VNode 全部更新时调用。
componentUpdated () {
console.log('componentUpdated');
},
// 只调用一次,指令与元素解绑时调用。
unbind () {
console.log('unbind');
}
}
}
}
</script>
4.在需要使用的地方调用v-autoFocus直接使用
<template>
<div class="hello">
<p v-dColor="'red'"> 我是全局定义的组件修改颜色值</p>
<p v-dFont="'50'"> 我是全局定义的组件可以修改大小</p>
<input type="text"
placeholder="请输入文字"
v-autoFocus>
</div>
</template>
组件#
啥是组件?#
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树,举个栗子,就像小时候玩的积木一样,想要一个好玩的玩具那么就动手一个组件一个组件的拼接吧.
- 创建组件的两种方式:1.全局组件 2.局部组件
全局组件#
- 说明:全局组件在所有的vue实例中都可以使用
- 注意:先注册组件,再初始化根实例
// 1 注册全局组件
Vue.component('my-component', {
// template 只能有一个根元素
template: '<p>A custom component!</p>',
// 组件中的 `data` 必须是函数 并且函数的返回值必须是对象
data() {
return {
msg: '注意:组件的data必须是一个函数!!!'
}
}
})
// 2 使用:以自定义元素的方式
<div id="example">
<my-component></my-component>
</div>
// =====> 渲染结果
<div id="example">
<p>A custom component!</p>
</div>
// 3 template属性的值可以是:
- 1 模板字符串
- 2 模板id template: '#tpl'
<script type="text/x-template" id="tpl">
<p>A custom component!</p>
</script>
- extend:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })
var Home = Vue.extend({
template: '',
data() {}
})
Vue.component('home', Home)
局部组件#
- 说明:局部组件,是在某一个具体的vue实例中定义的,只能在这个vue实例中使用
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// 注意:此处为 components
components: {
// <my-component> 将只在当前vue实例中使用
// my-component 为组件名 值为配置对象
'my-component': {
template: ``,
data () { return { } },
props : []
}
}
})
组件通信#
组件之间通信是Vue中最常用,最基础的部分,通常组件之前的通信分为以下几种:
- 1.父组件给子组件传递值,传递事件
- 2.子组件给父组件传递值,传递事件
- 3.兄弟组件之间传递值,传递事件
方法一、props $emit v-on
#
1.父组件给子组件传值
父组件A通过props向子组件B通信
接下来我们通过一个例子来加深以上理解:
自定义一个Son.vue子组件,引入到父组件中 在父组件中定义一个数据
<template>
<div id="app">
<!-- 前者自定义名称便于子组件调用,后者要传递数据名 -->
<son v-bind:user="user"></son>
</div>
</template>
<script>
import Son from './components/Son'
export default {
name: 'app',
components: {
Son,
},
data () {
return { // 父组件定义数据,传递给子组件
user: ["james", "alice", "joho"]
}
},
}
</script>
子组件中通过prop来定义父组件传值的值类型,是否是必须以及默认值
<script>
export default {
components: {
},
data () {
return {
};
},
computed: {
},
methods: {
},
props: {
user: { //这个就是父组件中子标签自定义名字
type: Array,
required: true,
default: []
}
},
}
</script>
然后在适当的位置将父组件传递过来的数据进行渲染
<template>
<div>
<ul>
<li v-for="(item,index) in user"
:key="index">姓名:{{item}}</li>
</ul>
</div>
</template>
浏览器打开会显示user里面的值.
总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed
2.子组件向父组件传值(通过自定义事件形式)
核心点
- 1.子组件使用
this.$emit('fn',param)
向父组件传值 - 2.父组件通过
v-on:fn="fn"
来接受子组件传递过来的值
子组件中:
子组件上包含一个点击事件
// 定义一个点击事件
<son v-bind:user="user"
@titleChange="changeTitle">
</son>
在点击事件中通过this.$emit()来传递事件
<template>
<div>
<button @click="btnClick">点击</button>
</div>
</template>
<script>
//import x from ''
export default {
components: {
},
data () {
return {
title: '我是子组件',
toParentData: '我是子组件传递的数据'
};
},
computed: {
},
methods: {
btnClick () {
this.$emit('titleChange', this.toParentData);
}
},
}
</script>
**父组件中: **
通过v-on来接受子组件发出的事件名称:titleChange并且和自己的changeTitle事件绑定
<template>
<div id="app">
<!-- 前者自定义名称便于子组件调用,后者要传递数据名 -->
<son v-bind:user="user"
v-on:titleChange="changeTitle"></son>
<p>{{msg}}</p>
</div>
</template>
实现changeTitle方法,改变data中的msg数据
<script>
import Son from './components/Son'
export default {
name: 'app',
components: {
Son,
},
data () {
return {
user: ["james", "alice", "joho"],
msg: '我是父组件显示的内容'
}
},
methods: {
changeTitle (title) {
this.msg = title;
}
}
}
</script>
总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。
方法二 中央事件总线 Bus 进行通信#
发现了一个问题,如果孙子组件给爷爷组件传值,通过props
或者$emit
方式是一件很痛苦的事情,需要通过中间的爸爸来做衔接,这样通信显然会使得组件耦合,同时兄弟组件间的通信props
,$emit
也实现不了?那么问题来了,如何解决呢?
这里介绍中央事件总线的方式,名字高大上其实就是抽一个公共Vue实例媒介来管理,需要通信的组件都引入Bus,之后通过分别触发和监听 Bus 事件,进而实现组件之间的通信和参数传递.
-
1.首先建 Vue 实例作为总线:
// Bus.js import Vue from 'vue' export default new Vue;
-
2.需要通信的组件都引入 Bus.js,使用 $emit发送信息:
// ComponentA.vue <template> <div> <b>组件A:</b><button @click="handleBus">传递数值给需要的组件</button> </div> </template> <script> import Bus from './bus.js' export default { methods: { handleBus () { Bus.$emit('someBusMessage','来自ComponentA的数据') } } } </script>
-
3.需要组件A信息的就使用$on来监听:
// ComponentB.vue <template> <div> <b>组件B:</b><button>接收组件A的信息</button> <p>{{message}}</p> </div> </template> <script> import Bus from './bus.js' export default { data() { return { message: '' } }, created () { let that = this // 保存当前对象的作用域this Bus.$on('someBusMessage',function (data) { that.message = data }) }, beforeDestroy () { // 手动销毁 $on 事件,防止多次触发 Bus.$off('someBusMessage', this.someBusMessage) } } </script>
中央总线优点:可以解耦组件,方便兄弟间通信
方法三 $parent
/ $children
& $refs
#
$parent
/$children
:指定已创建的实例,在两者之间建立父子关系。子实例可以用this.$parent
访问父实例,子实例被推入父实例的$children
中。$refs
:一个对象,持有注册过ref
特性的所有 DOM 元素和组件实例。ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件。
父组件
<template>
<div id="app">
<!-- 前者自定义名称便于子组件调用,后者要传递数据名 -->
<p>{{msg}}</p>
<child-one ref="childOne"></child-one>
<child-two ref="childTwo"></child-two>
<button @click="oneContent">显示child-one传过来的数据</button>
<button @click="twoOneContent">显示hild-two传过来的数据</button>
</div>
</template>
<script>
// 引入子组件
import ChildOne from './components/ChildOne'
import ChildTwo from './components/ChildTwo'
export default {
name: 'app',
components: {
ChildOne,
ChildTwo
},
data () {
return {
msg: '我是父组件',
content: 'James' //子组件要用的数据
}
},
methods: {
// button 的事件响应
oneContent () {
const childOne = this.$refs.childOne;
this.msg = childOne.msg;
},
twoOneContent () {
// 通过$refs取到childTwo
const childTwo = this.$refs.childTwo;
this.msg = childTwo.msg;
}
},
}
</script>
子组件1
<template>
<div>
<p>{{title}} 我的父组件是{{content}}</p>
</div>
</template>
<script>
//import x from ''
export default {
components: {
},
data () {
return {
content: '',
title: '我是子组件 childOne',
msg: '我只子组件childOne hello ereryOne'
};
},
mounted () {
// 从父组件里取contet的数据
this.content = this.$parent.content;
},
}
</script>
子组件2:
<template>
<div>
<p>{{title}} 我的父组件是{{content}}</p>
</div>
</template>
<script>
//import x from ''
export default {
data () {
return {
content: '',
title: '我是子组件 childTwo',
msg: '我是子组件childTwo hello ereryOne'
};
},
mounted () {
// 加载父组件content的内容
this.content = this.$parent.content;
},
}
</script>
方法四: 发布订阅模式#
- 1.通过
npm install pubsub-js --save
的方式引入pubsub库 - 2.创建
event-type.js
定义Symbol类型的数据并导出
// event-types.js
export const MY_TOPIC = Symbol('MY_TOPIC')
- 3.在需要发布(传递事件)的地方引入
pubsub
和event=type.js
,并且通过以下方式发布事件
import PubSub from 'pubsub-js'
import { MY_TOPIC } from './event-types.js'
PubSub.publish(MY_TOPIC, 'world');
- 4.在需要订阅(接受事件)的地方引入
pubsub
和event=type.js
,并且通过以下方式接受事件
import PubSub from 'pubsub-js'
import { MY_TOPIC } from './event-types.js'
PubSub.subscribe(MY_TOPIC, function (msg, data) {
console.log(data)
});
- 5.记得在组件销毁之前移除通知哦~
beforeDestroy() {
// 销毁通知
PubSub.subscribe(MY_TOPIC)
console.log("通知被销毁了!")
},
方法五 Vuex#
Vuex 是Vue官方推荐的一种解决组件中通信的完美解决方案,也是在项目开发中必用的方案。
认真看完本篇后对照思维导图可以在脑海里回顾一遍哦,对此块的知识点你会有更深的认识和理解.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?