We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
一般情况下我们在使用框架时(react、vue、angular)都是创建一个实例,然后所有的页面都写在#app一个容器内。这样可能会导致一些本改高复用,高解耦的弹窗类组件,在使用上变得麻烦/复杂。 本文尝试通过重新实例化Vue组件的方式,让脱离主视觉的弹窗类组件,大幅地降低组件和调用方的逻辑耦合。通过函数式的调用组件,极大的提高组件的可阅读性。同时满足开闭原则,对组件的二次开发也更容易
现有的弹窗组件,在组件复用、与父组件的控制耦合、父组件和弹窗组件的通信,都没有让人满意,存在更优解。
一般情况下我们实现一个弹窗组件
// 伪代码 const template = `<div> <Modal :visible="visible" :params1="params1" :params2="params2" @success="onSuccess" @close="onCloase" /> </div>`; import Modal from './Modal.vue'; import { Component, Prop, Vue } from "vue-property-decorator"; @Component export default class App extends Vue { visible = false; modalParams = {}; onOpen() { this.modalParams = {}; this.visible = true; } onSuccess() { // ... this.onClose(); }, onClose() { this.visible = false; this.modalParams = null; } }
缺点:
参考antd的Modal.info组件,其实我们完全可以在需要的使用的时候,直接创建dom元素,并实例化一个新的Vue实例
改造后使用弹窗类组件的方式
import Modal from './modal'; showModal() { const vm = Modal.instanceRender({ modalParams1, modalParams2 onCallback() { } }); }
优点:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
如何实现这样一个状态自治、方便使用的Modal组件
class InstanceRenderClass extends Vue { static instanceRender(options: ComponentOptions<Vue>) { // 创建vue实例,可组件内固定部分的参数 const instance = new this({ el: document.createElement('div'), ...options, // data: {visible: true, params1: '' }, // i18n: i18n, // store: store, }); // 把实例添加到dom document.body.appendChild(instance.$el); // *如果全局唯一,可存贮实例,通过状态控制显示隐藏,减少创建实例成本* } }
export class InstanceRenderClass extends Vue { // 根据需要隐藏元素、销毁组件、移除dom元素、调用回调 instanceClose() { this.visible = false; this.$destroy(); this.$el.remove(); } }
基于DRY原则,对于下面两点进行抽象封装还是相当有必要的。
每一个函数式调用的组件都仅是增加了两个方法:InstanceRender & instanceClose 业务参数的输入、固定参数的输入
InstanceRender & instanceClose
import Vue, { VueConstructor } from 'vue'; import { VueClass } from 'vue-class-component/lib/declarations'; import { ComponentOptions } from 'vue/types/options'; interface IInstanceRender { instanceRender: (options: ComponentOptions<Vue>) => InstanceType<VueConstructor> } /** * 基础实现 * @param Component 想要渲染的目标组件 * @returns VueClass */ export function InstanceRender<VC extends VueClass<Vue>, NVC extends VC & IInstanceRender>( Component: NVC ): NVC { Component.instanceRender = function ( options: ComponentOptions<Vue> ) { const instance = new Component({ el: document.createElement('div'), ...options, // i18n: options.i18n, // store: options.store, // route: options.route, // data: options.data, }); document.body.appendChild(instance.$el); return instance; }; // 若需要特殊逻辑,可以在Component组件中重写实现 Component.prototype.instanceClose = function () { this.$destroy(); this.$el.remove(); }; return Component as NVC; }
/* 使用装饰器的方式调用 */ @InstanceRender @Component({ mixins: [lockBodyScrollMixin], data: () => ({ ctitle: "ctitle" }), }) export default class HelloWorldWithDecorator extends Vue { // 如果通过装饰器@的方式使用InstanceRender,则对 instanceRender进行声明是必须的。原因如下 // declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; static instanceRender: any; instanceClose: any; private handleClose() { this.instanceClose(); } }
/* 使用函数的方式调用 */ @Component({ data: () => ({ ctitle: "ctitle" }) }) class HelloWorld extends Vue { // declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; instanceClose: any; private handleClose() { this.instanceClose(); } created() { console.log('create in hellow'); } } export default InstanceRender(HelloWorld);
1. 在使用Decorator @的方式调用HelloWorld.instanceRender会触发TS的报错
HelloWorld.instanceRender
原因是装饰器的实现就是原封不动的返回入参。
解决方法:
2. 关闭弹窗的实例方法instanceClose, 会触发TS报错 解决方法:在HelloWorld中给instanceClose声明。
instanceClose
3. 为什么不使用继承的方式给子类增加方法
export class InstanceRenderClass extends Vue { static instanceRender(options: ComponentOptions<Vue>) { } instanceClose() { } } @Component class HelloWorld extends InstanceRenderClass { // -- }
原因: 这是一种失败的方式,@Component 之后的组件中,不存在instanceRender方法.因为 vue-property-decorator 中的 @Component默认了直接父类就是Vue,因此他认为所有的属性都在当前的class中,实例化时就不会获取原型链上的静态属性。参考源代码可见。 如有兴趣可以尝试一下vue-class
@Component
instanceRender
vue-property-decorator
vue-class
import { Component, Vue } from "vue-property-decorator"; @Compnoent class HelloWorld extends Vue { // - }
/** * 对于用的上InstanceRender的组件,一般是fixed的全屏弹窗之类的,因此一般还需要展示之后禁止页面的滚动 * 希望InstanceRender纯粹一点就不给它增加参数加入其中了 */ export const lockBodyScrollMixin = { created() { document.body.style.overflow = "hidden"; }, beforeDestroy() { document.body.style.overflow = "initial"; }, }
最终回顾解决方案的时候会发现:原始问题的优先级并不高,而且整个过程并没有复杂度比较高的环节。但是通过一步一步解决下来,还是有触摸到自己的盲区,并且最后的成果还是相当有建设性的。
文中出现都是代码块。重在传递思路。
InstanceRender不仅仅适用于弹窗。而是任何想要高内聚,低耦合,又脱离主视觉的业务,都可以考虑使用。
InstanceRender
The text was updated successfully, but these errors were encountered:
No branches or pull requests
写在前面
一般情况下我们在使用框架时(react、vue、angular)都是创建一个实例,然后所有的页面都写在#app一个容器内。这样可能会导致一些本改高复用,高解耦的弹窗类组件,在使用上变得麻烦/复杂。
本文尝试通过重新实例化Vue组件的方式,让脱离主视觉的弹窗类组件,大幅地降低组件和调用方的逻辑耦合。通过函数式的调用组件,极大的提高组件的可阅读性。同时满足开闭原则,对组件的二次开发也更容易
现状
现有的弹窗组件,在组件复用、与父组件的控制耦合、父组件和弹窗组件的通信,都没有让人满意,存在更优解。
一般情况下我们实现一个弹窗组件
缺点:
无法同时render多个modal实例优化
参考antd的Modal.info组件,其实我们完全可以在需要的使用的时候,直接创建dom元素,并实例化一个新的Vue实例
改造后使用弹窗类组件的方式
优点:
缺点:
实现
如何实现这样一个状态自治、方便使用的Modal组件
抽象封装
基于DRY原则,对于下面两点进行抽象封装还是相当有必要的。
抽象的方法
问题整理
1. 在使用Decorator @的方式调用
HelloWorld.instanceRender
会触发TS的报错解决方法:
2. 关闭弹窗的实例方法
instanceClose
, 会触发TS报错解决方法:在HelloWorld中给
instanceClose
声明。3. 为什么不使用继承的方式给子类增加方法
原因:
这是一种失败的方式,
@Component
之后的组件中,不存在instanceRender
方法.因为vue-property-decorator
中的@Component
默认了直接父类就是Vue,因此他认为所有的属性都在当前的class中,实例化时就不会获取原型链上的静态属性。参考源代码可见。 如有兴趣可以尝试一下vue-class
其他
instanceRender
静态方法,缓存实例等操作,可以有效提高重复渲染的效率。最后回顾一下发展历程
最终回顾解决方案的时候会发现:原始问题的优先级并不高,而且整个过程并没有复杂度比较高的环节。但是通过一步一步解决下来,还是有触摸到自己的盲区,并且最后的成果还是相当有建设性的。
PS
The text was updated successfully, but these errors were encountered: