持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
本节我们会来实现一下事件注册的功能,也就是平时我们在vue
的template
中会用到的v-on
编译成渲染函数后的结果,比如v-on:click="xxx"
则会被编译成vnode
的一个prop
-- onClick
,而我们要实现的就是这个onXxx
的功能,当xxx
事件发生的时候,就会执行onXxx
回调
1. 案例场景
根据vue3
的官方文档对h
函数的介绍,当props
中有onXxx
的方法时,会作为事件监听器,为相应的DOM
添加监听事件,我们现在就是要去实现这个功能
首先修改一下hello world
的案例:
export const App = {
// 由于还没有实现模板编译的功能 因此先用 render 函数来替代
render() {
window.self = this;
return h(
'div',
{
class: ['cyan', 'success'],
onClick() {
console.log('click...');
},
},
[
h('p', { class: 'cyan' }, 'hi '),
h('p', { class: 'darkcyan' }, 'plasticine '),
h('p', { class: 'darkviolet' }, 'mini-vue!'),
h('p', { class: 'darkcyan' }, `setupState msg: ${this.msg}`),
]
);
},
setup() {
// Composition API
return {
msg: 'plasticine-mini-vue',
};
},
};
2. 处理props时对事件监听属性做特殊处理
给根组件的DOM
结点添加了一个onClick
方法,作为click
事件的回调,那么我们就应该到具体操作DOM
的地方添加事件监听器,也就是要到mountElement
函数中操作
// src/runtime-core/renderer.ts
function mountElement(vnode: any, container: any) {
// 将 DOM 对象挂载到 vnode 上 从而让组件实例能够访问到
const el = (vnode.el = document.createElement(vnode.type));
const { children, shapeFlag } = vnode;
// children
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
el.textContent = children;
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
}
// props
const { props } = vnode;
for (const [key, value] of Object.entries(props)) {
- el.setAttribute(key, value);
+ // 处理事件监听
+ if (key === 'onClick') {
+ el.addEventListener('click', value);
+ } else {
+ el.setAttribute(key, value);
+ }
}
container.append(el);
}
这样就实现对点击事件的事件监听器注册功能啦!
3. 让事件处理更加通用
目前虽然能够处理click
事件,但是还不够通用,我们应当让它能够处理任意事件,只要用户遵循在props
设置onXxx
命名格式的方法这一约定,就能够监听到相应的事件
既然有命名规范了,那就好办了,只需要遍历到符合事件监听器命名规范的属性时,就给它添加相应的事件监听器就可以了
function mountElement(vnode: any, container: any) {
// 将 DOM 对象挂载到 vnode 上 从而让组件实例能够访问到
const el = (vnode.el = document.createElement(vnode.type));
const { children, shapeFlag } = vnode;
// children
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
el.textContent = children;
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
}
// props
const { props } = vnode;
for (const [key, value] of Object.entries(props)) {
+ const isOn = (key: string) => /^on[A-Z]/.test(key);
// 处理事件监听
- if (key === 'onClick') {
- el.addEventListener('click', value);
- } else {
- el.setAttribute(key, value);
- }
+ if (isOn(key)) {
+ el.addEventListener(key.slice(2).toLowerCase(), value);
+ } else {
+ el.setAttribute(key, value);
+ }
}
container.append(el);
}
现在就能够处理任何原生DOM
事件了
4. 测试其他事件的监听
我们可以再添加一个事件监听回调测试一下
export const App = {
// 由于还没有实现模板编译的功能 因此先用 render 函数来替代
render() {
window.self = this;
return h(
'div',
{
class: ['cyan', 'success'],
onClick() {
console.log('click...');
},
onMouseMove() {
console.log('mouse moving...');
},
},
[
h('p', { class: 'cyan' }, 'hi '),
h('p', { class: 'darkcyan' }, 'plasticine '),
h('p', { class: 'darkviolet' }, 'mini-vue!'),
h('p', { class: 'darkcyan' }, `setupState msg: ${this.msg}`),
]
);
},
setup() {
// Composition API
return {
msg: 'plasticine-mini-vue',
};
},
};