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
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
}