Skip to content

Instantly share code, notes, and snippets.

@kokororin
Created June 1, 2020 07:13
Show Gist options
  • Save kokororin/975125590a8577c891e2f2c2ed543fb2 to your computer and use it in GitHub Desktop.
Save kokororin/975125590a8577c891e2f2c2ed543fb2 to your computer and use it in GitHub Desktop.
TypeScript Miniapp
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