Created
June 1, 2020 07:13
-
-
Save kokororin/975125590a8577c891e2f2c2ed543fb2 to your computer and use it in GitHub Desktop.
TypeScript Miniapp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
createStoreBindings, | |
StoreBindingsOptions | |
} from 'mobx-miniprogram-bindings'; | |
export function getPrototypeOf(obj: any): any { | |
// eslint-disable-next-line no-proto | |
return Object.getPrototypeOf ? Object.getPrototypeOf(obj) : obj.__proto__; | |
} | |
/** | |
* 判断 something 是不是一个 JS Object (从 mora-script 中取过来的) | |
* | |
* 除了 null, 及字面量,其它一般都是 Object,包括 函数 | |
*/ | |
export function isObject(something: any) { | |
const type = typeof something; | |
return something !== null && (type === 'function' || type === 'object'); | |
} | |
export function isPlainObject(something: any) { | |
return Object.prototype.toString.call(something) === '[object Object]'; | |
} | |
/** | |
* 遍历继承关系类的 prototype | |
* | |
* @export | |
* @param {Function} callback - 回调函数,函数参数是遍历的每个实例的 prototype,函数如果返回 false,会终止遍历 | |
* @param {any} fromCtor - 要遍历的起始 class 或 prototype | |
* @param {any} toCtor - 要遍历的结束 class 或 prototype | |
* @param {boolean} [includeToCtor=true] - 是否要包含结束 toCtor 本身 | |
* | |
* @example | |
* A -> B -> C | |
* | |
* 在 C 中调用: iterateInheritedPrototype(fn, A, C, true) | |
* 则,fn 会被调用三次,分别是 fn(A.prototype) fn(B.prototype) fn(C.prototype) | |
*/ | |
export function iterateInheritedPrototype( | |
callback: (proto: object) => boolean | void, | |
fromCtor: any, | |
toCtor: any, | |
includeToCtor = true | |
) { | |
let proto = fromCtor.prototype || fromCtor; | |
const toProto = toCtor.prototype || toCtor; | |
while (proto) { | |
if (!includeToCtor && proto === toProto) { | |
break; | |
} | |
if (callback(proto) === false) { | |
break; | |
} | |
if (proto === toProto) { | |
break; | |
} | |
proto = getPrototypeOf(proto); | |
} | |
} | |
export interface ClassInstanceToObjectOptions { | |
/** | |
* 将所有的对象中的函数绑定到指定的对象上 | |
* | |
* **注意:对象中的箭头函数无法重新绑定** | |
*/ | |
bindTo?: any; | |
/** | |
* 要排除的键名 | |
* | |
* 默认: ['constructor'] | |
*/ | |
excludes?: string[]; | |
/** | |
* 递归遍历到的终点对象或class(不会遍历终点对象上的属性) | |
* | |
* 默认: Object | |
*/ | |
till?: any; | |
/** | |
* 生成的新对象的键值是否需要 enumerable, 0 表示使用原本的配置,此值默认为 true | |
*/ | |
enumerable?: 0 | boolean; | |
/** | |
* 生成的新对象的键值是否需要 configurable, 不指定或指定 0 则使用原本的 | |
*/ | |
configurable?: 0 | boolean; | |
/** | |
* 生成的新对象的键值是否需要 writable,不指定或指定 0 则使用原本的 | |
*/ | |
writable?: 0 | boolean; | |
} | |
/** | |
* | |
* 将一个可能包含原型链的对象扁平化成单个对象 | |
* | |
* 如,现有这样的类的继承关系 A -> B -> C,当创建一个实例 a = new A() 时 | |
* | |
* a 实例会存有 B、C 的原型链,使用此函数 newa = toObject(a) 之后, | |
* newa 就会变成一个 PlainObject,但它有 A、B、C 上的所有属性和方法, | |
* 当然不包括静态属性或方法 | |
* | |
* 注意1:用此方法的话,尽量避免在类中使用胖函数,胖函数的 this 死死的绑定 | |
* 在原对象中,无法重新绑定 | |
* | |
* 注意2:类继承的时候不要在函数中调用 super,toObject 之后是扁平的,没有 super 之说 | |
*/ | |
export function toObject( | |
something: any, | |
options: ClassInstanceToObjectOptions = {} | |
): { [key: string]: any } { | |
const obj = {}; | |
if (!isObject(something)) { | |
return obj; | |
} | |
const excludes = options.excludes || ['constructor']; | |
const { enumerable = true, configurable = 0, writable = 0 } = options; | |
const defaultDesc: PropertyDescriptor = {}; | |
if (enumerable !== 0) { | |
defaultDesc.enumerable = enumerable; | |
} | |
if (configurable !== 0) { | |
defaultDesc.configurable = configurable; | |
} | |
if (writable !== 0) { | |
defaultDesc.writable = writable; | |
} | |
iterateInheritedPrototype( | |
proto => { | |
Object.getOwnPropertyNames(proto).forEach(key => { | |
if (excludes.indexOf(key) >= 0) { | |
return; | |
} | |
if (obj.hasOwnProperty(key)) { | |
return; | |
} | |
const desc = Object.getOwnPropertyDescriptor( | |
proto, | |
key | |
) as PropertyDescriptor; | |
const fnKeys = ['get', 'set', 'value'] as 'get'[]; | |
fnKeys.forEach(k => { | |
if (typeof desc[k] === 'function') { | |
const oldFn = desc[k] as any; | |
desc[k] = function (...args: any[]) { | |
return oldFn.apply( | |
options.hasOwnProperty('bindTo') ? options.bindTo : this, | |
args | |
); | |
}; | |
} | |
}); | |
Object.defineProperty(obj, key, { ...desc, ...defaultDesc }); | |
}); | |
}, | |
something, | |
options.till || Object, | |
false | |
); | |
return obj; | |
} | |
declare type AnyObject = Record<string, any>; | |
export interface BasePageProps | |
extends WechatMiniprogram.Page.InstanceProperties { | |
/** | |
* 用来保存 onLoad(options) 传入的参数 | |
*/ | |
options: AnyObject; | |
/** 页面的初始数据 | |
* | |
* `data` 是页面第一次渲染使用的**初始数据**。 | |
* | |
* 页面加载时,`data` 将会以`JSON`字符串的形式由逻辑层传至渲染层,因此`data`中的数据必须是可以转成`JSON`的类型:字符串,数字,布尔值,对象,数组。 | |
* | |
* 渲染层可以通过 `WXML` 对数据进行绑定。 | |
*/ | |
data: any; | |
/** 到当前页面的路径和参数,类型为`String`。最低基础库: `1.2.0` */ | |
url?: string; | |
} | |
export interface BasePage<TPageData> | |
extends WechatMiniprogram.Page.ILifetime, | |
WechatMiniprogram.Page.InstanceMethods<TPageData>, | |
BasePageProps { | |
// 允许其他任意自定义的 class 变量 | |
data: TPageData; | |
[x: string]: any; | |
} | |
export class BasePage<TPageData> { | |
readonly app: any; | |
constructor() { | |
this.app = getApp(); | |
} | |
} | |
interface DecoratorOptions { | |
storeBindingOptions?: StoreBindingsOptions; | |
} | |
export function pagify(decoratorOptions?: DecoratorOptions) { | |
return function <TPageData>( | |
constructor: new () => BasePage<TPageData> | |
): void { | |
class WxPage extends constructor { | |
storeBindings: any; | |
// constructor(..._args: any[]) { | |
// super(); | |
// } | |
onLoad(options?: any) { | |
// this.setData({ | |
// wxPage: 1 | |
// }); | |
const { storeBindingOptions } = decoratorOptions || {}; | |
// 绑定传入的 store 到当前 Page 对象 | |
if (storeBindingOptions) { | |
const { store, fields, actions } = storeBindingOptions; | |
// @ts-ignore | |
this.storeBindings = createStoreBindings(this, { | |
store, | |
fields, | |
actions | |
}); | |
} | |
super.onLoad && super.onLoad(options); | |
} | |
// 重写生命周期函数,可以在这里前后拦截操作 | |
onReady() { | |
super.onReady && super.onReady(); | |
} | |
onShow() { | |
super.onShow && super.onShow(); | |
} | |
onUnload() { | |
this.storeBindings && this.storeBindings.destroyStoreBindings(); | |
super.onUnload && super.onUnload(); | |
} | |
} | |
const current = new WxPage(); | |
const obj = toObject(current); | |
Page(obj); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment