Skip to content

Instantly share code, notes, and snippets.

@DaiwenZh5
Last active July 23, 2019 18:07
Show Gist options
  • Save DaiwenZh5/b81de8014da8cb638a9efee4d95c7ed6 to your computer and use it in GitHub Desktop.
Save DaiwenZh5/b81de8014da8cb638a9efee4d95c7ed6 to your computer and use it in GitHub Desktop.
#md #test
# 目的
在子组件渲染完成之后,父组件可以及时获取 dom 进行操作。
# 描述
这里是因为我在获取 markdown 渲染之后的文章目录时,无法及时更新视图,
因为其执行过程是在组件渲染完成之后,将 markdown 渲染的文本通过
`innerHTML` 置入 dom 中,因此需要在组件渲染完成,且将文件放置到 dom 之后触发事件。
# 尝试
## 组件生命周期钩子
1. 在父组件中订阅子组件生命周期:
`@hook:mounted="rendered"`,该方法等同与子组件的`mounted`方法中发布事件,提供外部访问子组件`mounted`生命周期,但向比较而言,`@hook`提供了无入侵式方法。
2. 在父组件`mounted`方法中操作,因为子组件是先于父组件渲染完成的,为了确保无误,可以使用`this.$nextTick`方法来明确操作执行与 DOM 更新之后。
```vue.js
async mounted() {
// DOM 更新前
await this.$nextTick();
// DOM 更新后
}
```
## 结果
只能确保视图渲染完成,DOM 存在,但是不能确保此时已经完成`innerHTML`操作。
# 解决
`innerHTML`插入完成之后使用`$emit`方法发布事件,在订阅事件中进行 DOM 操作。确保两点:
1. `innerHTML`之后插入,确保了 DOM 操作的执行时机
2. `$emit`方法虽然执行于父组件,但是完成的依旧是子组件事件,该方法封装一套逻辑执行于子组件发布的位置,从而保证了执行顺序的无误。
# 方法:修改子组件
需要完成两点:
1. 获取 DOM:
- `ref`属性
- 自定义指令
2. 发布自定义事件
## `ref`属性
在组件上定义该属性,即可使用`this.$refs`来获取 DOM
```vue.js
//- 在子组件上指定 ref 值
ref="markdown-it-vue-container"
// 在 innerHTML 执行之前插入
this.init(this.$refs["markdown-it-vue-container"])
```
## 自定义指令
在组件上绑定自定义指令
```vue,js
//- 子组件 render 指令绑定 init 函数
v-render="init"
// render 指令此处用了方法简写,其意味着 bind、update 函数一致
directives: {
render(el, binding) {
// value 的值为函数,用于暴露 dom,方便组件操作
binding.value(el);
}
},
```
## 发布自定义事件
```vue.js
methods: {
init(el) {
// 子组件自身需要执行的操作
el.innerHTML = this.md.render(this.content);
// 暴露给父组件的方法
this.$emit("rendered", el);
}
},
```
## 订阅自定义事件
父组件需要进行的内容:
```vue.js
//- 父组件订阅事件,并指定方法名为 rendered
//- 需要注意的是,这里的发布的事件名与订阅事件名一致或可能出现冲突
@rendered="rendered"
methods: {
// el 是在自定义指令中作为参数传递的
// init 方法 -> rendered 事件
rendered(ref) {
// do anything you want
// - 下面是我的应用
// getCatalog 是用于获取 dom 内的文章目录
// 因此需要子组件渲染之后执行
 let { levels, noLevels } = getCatalog(ref);
this.catalog = {
levels,
noLevels
};
}
},
```
# 问题
尽管完成效果,但是由于通过在子组件内部发布自定义事件来实现的,因此对于一些封装好的组件无能为力,无法做到无侵入式切入。
但是通过 AOP 的思想,可以在组件被调用前,对其进行切面。
# 方法:面向切面编程(AOP)
## 组件改装,加入切面
在网上粘过来的 `after`方法
```js
Function.prototype.after = function(action) {
//保留当前函数环境
var func = this;
// return 被包装过的函数,这里就可以执行其他功能了。
// 并且该方法挂在Function.prototype上,
// 被返回的函数依然具有after属性,可以链式调用
return async function() {
// 原函数执行
// 这是里用了异步,是因为切片所在的函数也是异步的
var result = await func.apply(this, arguments);
// 执行之后的操作,after 方法是否异步不重要,其必然在原方法之后执行
await action.apply(this, arguments);
// 将执行结果返回
return result;
};
};
```
对组件进行分析,跑一边调试、打印,定位到组件的 watch,该组件是用于渲染 markdown 文档的,其对 content 字符串进行监听,一旦改动则使用 markdown-it 对其渲染成 html 字符串,然后将其插入 dom 中。
```vue.js
// 切面,这里只装载了 after 方法
// 需要在引入组件之前进行
MarkdownItVue.watch.content.handler = MarkdownItVue.watch.content.handler.after(
async function() {
console.log("1 - aop");
// console.log(this.$el)
setTimeout(() => {
// 同样需要提供父组件调用,因此要发布事件
this.$emit("aopRendered");
}, 20);
}
);
```
## 订阅事件
本质上还是在子组件中发布了自定义事件,父组件中同样需要订阅。
```vue.js
methods: {
rendered() {
console.log("2 - rendered");
const el = this.$refs["markdown"].$el;
console.log(el);
let { levels, noLevels } = getCatalog(el);
this.catalog = {
levels,
noLevels
};
}
},
```
## 结果
执行结果符合预期:订阅事件生效,且在 after 方法之后执行
![](https://daiwenzh5.github.io/post-images/1563736529159.png)
# 方法:钩子函数
实际上也是 AOP 思想,同样需要在组件使用前进行改写。
```vue.js
// 要使用该方法,执行将此处的方法名,即等式左边替换掉即可
MarkdownItVue.watch.content.handler = (function(fn) {
// 返回包装的函数,运行时将包装函数this传入原函数
return async function() {
console.log("1 - 钩子函数");
console.log("执行之前");
const result = await fn.apply(this, arguments);
console.log("执行之后");
this.$emit("aopRendered");
return result;
};
// 等式从右边执行,因此可以直接使用原函数,无需缓存
})(MarkdownItVue.watch.content.handler);
```
其余代码和 AOP 中一致。
## 结果
执行结果也是符合预期的,当然执行之中的由于没有改动子组件自然是没法配合了,不过订阅的事件也同样生效了,即拓展成功。
![](https://daiwenzh5.github.io/post-images/1563736700356.png)
# 总结
1. 1. 子组件通过`$emit`发布事件能够与父组件进行通信
2. AOP 编程可以无侵入式的对组件进行拓展,上面之所以区分,是因为 aop 方法提供了 before、after、around 三种切面,更规范一些。这里将 aop 拓展在了 `Function`方法上,而事实上,对于单个函数拓展,自然是使用钩子函数来的更轻松一些,只作用于目标函数,用完即走,只进入身体、不进入生活,潇洒地惹人喜爱。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment