vue-typescript项目 工作笔记

1. Input组件的封装, 实现v-model功能

<!-- 组件HTML -->
<input type="text" 
        :placeholder="placeholder? placeholder : '请输入...'"
        @input='handleInput'>
export class Input extends Vue {
    @Prop() placeholder: string;   
    handleInput (event) {
        let value = event.target.value;
        this.$emit('input', value);
    }
}
//父组件
<Input  v-model='value':placeholder="'嘟嘟嘟'"></Input>{{value}}
value = '111';
  @Watch('value')
  onValueChange(val: string, oldVal: string) {
    console.log(val)  // 至此 页面和打印日志  都在和输入框里面的值同步变化
  }

主要就是触发vue的input事件, 不仅仅是input才可以用这个事件做到双向绑定。其他的还有(step步骤组件的currStepIndex、selector下拉框的当前选中值 等)只要在这个值或者事件触发的时候,手动触发input的事件即可

2. Vue.set

  • vue不像angular一样, 一个新属性或者新变量的加入或变动, 脏检查机制都能检测得到。 对一个已经有了的对象(如后台返回的数据)进行新添加key value处理的时候,vue是检测不到的。表现是属性变化,页面不跟着刷新。这个时候就用到了Vue.set
vue.set(object, objectKey, objectValue);
  • 所以这里也注意,由于vue自身机制问题, 组件内的变量最好都要有初始值

3. Vue中,用方法动态打开一个组件实现

export class UDynamicService {
    // 动态的打开一个组件
    open<T, K>(option: UDynamicOpenOptionObject<T, K>) {
        option = Object.assign({
            data: {},
            events: {}
        },option)
        let component = Vue.extend(option.component);
        let instance = new component();
        for(let i in option.data) {
            instance.$props[i] = option.data[i];
        }
        for(let i in option.events) {
            instance.$on(i, option.events[i]);
        }
        let temp = instance.$mount();
        if(option.selector) {
            (option.selector as any).appendChild(temp.$el);
        } else {
            document.body.appendChild(temp.$el);
        }
        return instance;
    }
    // 手动的关闭一个组件
    destroy(instance: object & Record<never, any> & Vue) {
        let dom = new DomHelper(instance.$el);
        dom.remove();
    }
    // 清空所有的组件
    destroyAll() {

    }
}
export interface UDynamicOpenOptionObject<T, K> {
    component: T; // 组件
    selector?: Element | string;
    data?: K; // 传入的数据
    events?: any;
}

4. 粗略模仿vue emit(eventName, param);on(eventName, handler)

  • 大致原理:

创建一个单例, 单例中有一个变量是用来保存事件名和事件名对应的处理函数

单例中 on 函数存储 eventName 和 evenName对应handler处理函数

单例中的 emit 函数是触发 eventName 对应的handler处理函数 并往handler中传递参数

  • 实现
// 注意: 使用的时候一定要是一个 "单例"
// 公共的emitter 处理类
export class UEmitter {
  constructor() {}
  // 用于存储events信息 , 由于组件使用的时候可能注册多个事件,这里是个数组
  private events: {
        [prop: string]: {
            handler: Function,
            once: boolean
        }[];
    } = {};
   // 只注册一次, 处理完之后就delete
    once(eventname: string, handler: Function = () => {}) {
       this.addEvent(eventname, handler, true);
       return this;  
    }
    on(eventname: string, handler: Function = () => {}) {
        this.addEvent(eventname, handler, false);
        return this;
    }
    /*
    ** @Param eventname 事件名
    ** @Parma handler 绑定的事件
    ** @Parma once 是否只绑定一次
   */
    private addEvent(eventname: string, handler: Function = () => {}, once: boolean = false) {
        if(typeof eventname === 'function') {
            once = handler as any;
            handler = eventname;
            eventname = 'default';
        }
        if(!this.events[eventname]) {
            this.events[eventname] = [];
        }
        this.events[eventname].push({
            handler: handler,
            once: once
        });
    }
    destroy() {
        for(let i in this.events) {
            let events = this.events[i];
            events.forEach((event) => {
                delete event.handler;
                delete event.once;
            });
            delete this.events[i];
        }
    }
    emit(eventname: string = 'default', ...data) {
        if(this.events[eventname]) {
            // 存在这个处理
            for(let i in this.events[eventname]) {
                let item = this.events[eventname][i];
                item.handler.apply(null, data);
            }
            this.events[eventname] = this.events[eventname].filter((item, index) => {
                let isOnce = item.once;
                if(isOnce) {
                    delete item.handler;
                    delete item.once;
                }
                return !isOnce;
            })
        }
        return this;
    }
}

5. @type -- TypeScript中有些模块需要从@types下 再引入@type

  • 简单来讲,@type是es5到typescript的桥梁, 有些包,直接从npm或者yarn下载过来,包里面是没有export 这种模块导出的变量的,@types提供的只是定义文件,真正的逻辑在对应的包里。它把原来框架的内容通过@types进行模块化声明和export,然后我们就可以在ts中直接引用了

6. vue变量命名问题 (遇到的小坑)

  • vue中 如果变量是以_ $等(可能还有其他,这里我就测了这两种) 例如_a, $a, 这个时候vue是检测不到这个变量的变化的,好像vue自己规避了这些特殊符号的检测

7. extends 和 implements

  • angular2中常使用OnInit
 export class OneComponent implements OnInit {
   ngOnInit() {} 
}

extends 是继承了父类 它可以直接用父类中的方法, 也可以重写父类中的方法(重新定义和父类中名字一样的变量或方法)

implements 是实现多个接口, 接口的方法一般都是空的, 只有重写才能使用,但是子类不能重新定义和接口父类中名字一样的变量或者函数,即使一样,也会被接口父类取代

extends每次继承的时候只能继承一个, 但是implements可以实现多个接口 用逗号隔开就行了

8. 项目中的组件都是动态打开的 , 动态打开的组件如果需要外界的一些真实dom做一些初始化工作的话, 就会出现一些问题。(动态组件的实例$el,是虚拟的dom,虚拟dom还未转变成真实dom, 就已经去拿虚拟dom的一些样式, 是拿不到的

  • 项目中 封装的codemirror的组件u-code 动态打开它的时候 在页面加载完成 发现没有自动获得获得光标 等一些样式问题

  • 问题解决

    保证dom加载完成之后,再去渲染u-code组件, 这个时候 保证了dom和组件的加载顺序是先加载当前组件,再去加载u-code动态打开的组件, 而不是动态组件还未真正从虚拟dom变成真是dom就去初始化 u-code的一些东西

    用一个flag=false, 当mounted之后 让flag-=true, <u-code v-if='flag'></u-code> 来解决 这样就保证了 dom完全变成真实dom了之后 再去初始化u-code组件

9. 下载文件的封装优化

  • 项目中下载文件存在如果后台找不到文件会给前端发一个json的数据
{
  "succ": false,
  "msg": "下载异常",
  "data": "下载异常"
}
  • 这个时候由于form表单的提交返回这样的数据而非文件的格式,浏览器会直接把json的数据展示到页面(如果存在这个文件,浏览器会直接下载这个文件),用户看到的是后台返回数据,再点击浏览器的返回键看到原网页,这个时候浏览器已经刷新了,用户在此之前的所有操作不复存在,体验很不好。
  • 解决
    • 再真正用form表单提交下载请求地址之前,先用正常ajax请求一次,如果返回文件异常的错误json提示,提示用户下载出错,相反没有错误再去提交form
async download(url: string, params: any = {}, method: string = 'post') {
        try{
            Axios.get(url, params).then((data) => {
                // 错误拦截 不让浏览器触发默认行为
                if(data.data.succ == false) {
                    let uM: UMessageService = Ioc(UMessageService);
                    uM.error(data.data.msg);
                }else {
                    // 这是项目封装的有关dom的一些操作
                    let form = new DomHelper(document.createElement('form'));
                    let net: UNetService = Ioc(UNetService);
                    document.body.appendChild(form.getDom());
                    form.getDom().setAttribute('target', '');
                    form.getDom().setAttribute('method', method);
                    form.getDom().setAttribute('action', net.getUrl(url));
                    for(let i in params) {
                        let input = new DomHelper(document.createElement('input'));
                        input.getDom().setAttribute('type', 'hidden');
                        input.getDom().setAttribute('name', i);
                        input.getDom().setAttribute('value', ((typeof params[i]) === 'object' ) ? JSON.stringify(params[i]) : params[i]);
                        form.getDom().appendChild(input.getDom());
                    }
                    let opt = document.createElement("input");
                    opt.type = "submit";  
                    form.getDom().appendChild(opt); 
                    (form.getDom() as HTMLFormElement).submit();
                    Vue.nextTick(() => {
                        form.getDom().remove();
                    })
                }
            })
        }catch(e) {
            console.log(e);
        }

    }

10. 把静态数据转换成动态的事件(简单的模仿 rxjs的发布订阅值的机制)

export class UObserver<T> extends UEmitter {
    constructor(

    ) {
        super();
    }
     // 因为一个被订阅的值, 伴随其变化有可能有多个操作 每个操作都让其唯一存在于handlers数组里面保存 
    index: number = 0;
    private val: T = null;
    // 用于保存操作
    handlers: {
        [prop: number]: ((val: T) => void)
    } = [];
    get value() {
        return this.val;
    }
    set value(v: T) {
        this.next(v);
    }
    setValue(v: T) {
        this.val = v;
    }
    // 简单的发布值
    next(val: T) {
        this.val = val;
        for(let i in this.handlers) {
            this.handlers[i](val);
        }
    }
    // 有可能是有次序的执行,这里可以使用sync
    async nextSync(val: T) {
        this.val = val;
        for(let i in this.handlers) {
            await this.handlers[i](val);
        }
    }
    map<K>(handler: (val: T, index: number) => K) {
        let subject = new UObserver<K>();
        let count = 0;
        this.subscribe((val: T) => {
            subject.next(handler(val, count++));
        })
        return subject;
    }
    merge<K>(subject: UObserver<K>) {
        let newsubject = new UObserver<T | K>();
        setTimeout(() => {
            let self_res = this.subscribe((v: T) => {
                newsubject.next(v);
            });
            let subject_res = subject.subscribe((v: K) => {
                newsubject.next(v);
            });
            // 这里别忘了取消订阅
            newsubject.once('unsubscribe', () => {
                subject_res();
                self_res();
            })
        })
        return newsubject;
    }
    // 订阅值
    subscribe(handler: (val: T) => void) {
        let index = this.index;
        this.handlers[this.index++] = handler;
        return async () => {
            delete this.handlers[index];
            this.emit('unsubscribe');
        }
    }
    // 取消所有的订阅
    unsubscribe() {
        for(let i in this.handlers) {
            delete this.handlers[i];
        }
        this.index = 0;
        this.emit('unsubscribe-all');
    }
}

11. 关于tsconfig

tsconfig 全部配置项列表 https://www.tslang.cn/docs/handbook/compiler-options.html

{
    "compilerOptions": {
        "emitDecoratorMetadata": true,   // 开启反射
        "experimentalDecorators": true,  //  开启装饰器
        "importHelpers": true,
        "lib": [
            "es2017",
            "dom",
            "es5"
        ],
        "module": "commonjs",
        "moduleResolution": "node",
        "noEmitHelpers": true,
        "noImplicitThis": false,
        "noUnusedLocals": false,
        "removeComments": true,
        "sourceMap": true,
        "strict": false,
        "target": "es5",  // 编译为es5
        "declaration": false, 
         // TypeScript具有三种JSX模式:`preserve`,`react`和`react-native`。
         // 这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 
         // 在`preserve`模式下生成代码中会保留JSX以供后续的转换操作使用(比如:[Babel])
        "jsx": "preserve", 
         //  如果工厂定义为React.createElement(默认值),编译器将在检查全局JSX之前检查React.JSX。
         // 如果工厂定义为h,它将在全局JSX之前检查h.JSX
        "jsxFactory": "h"
    },
    "compileOnSave": false,
    "buildOnSave": false
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容