ts-chain-core
TypeScript icon, indicating that this package has built-in type declarations

0.0.3-alpha2 • Public • Published

English | 简体中文

ts-chain-core

这是一个typescript链式编程的基础库,从随手编写的小工具到称手的工具集合都可以用它来完成

特性

  • 用最小的代码就能实现链式编程
  • 节省代码量:在一些场合中,使用本库编写的工具可以使实际代码量减少一半甚至更多
  • 💥💥扩展接口自动补全 无需事先定义你撰写的接口,即可在书写过程中获得代码补全!下面是实际效果展示(环境:vscode,无特殊插件) image
  • 🌈上手简单 你只要掌握三个核心的api就可以编写出满足大部分需求的链式接口。
  • 🚀小巧 程序本体压缩后仅3kb大小。

快速开始

安装

npm i ts-chain-core

####基本用法

import { ChainCore } from "ts-chain-core";

ChainCore(null)
    .setFunction((name: string) => {
        console.log(`hello,${name}`);
    })
    ('🍉')
    ('🍍')
    ('🥭')

将类方法直接转换成链式方法

class Example {
    toChain(){
        return ChainCore(this).extendFunctionsFromObject(Example)
    }
    add(a:number,b:number){
        console.log(a+b)
    }
    addOne(a:number){
        return (b:number)=>{
            console.log(a+b)
        }
    }
    addEnd(a:number,b:number){
        return a+b;
    }
}

new Example().toChain()
    .add(3,5)   //8
    .add(4,7)   //11
    .addOne(4)  
        (5)     //9
        (6)     //10
        (7)     //11
    .addEnd(11,10)  //chain ended here
    .add(42,1)  //❌error, chain function break

扩展时,根据根据方法返回类型不同,会生成不同的链式方法

返回值类型 undefined/void function 其他类型
修改()操作 function设置为()操作
终止链式反应

但是()操作的返回不论是什么类型都不会再次修改()操作

扩展实例方法

在扩展方法里面,你也许需要调用ChainCore已经扩展的方法或者ChainCore自有的方法,在这种方法中,你需要在入口参数中增加一个ChainCore的引用。 ChainCore提供了一个方法来扩展这种方法,并在该方法被调用时自动传入ChainCore实例,我们把这种方法称为实例方法

扩展方法举例

ChainCore(null).extendFunctionsFromObject({
    sayHello: (name: string) => {
        console.log(`hello,${name}`);
    },
}).extendInstanceFunctions({
    /**
     * play a scenario
     * @param ch passed by `ChainCore` and reference to itself
     * @param time 
     * @param place 
     * @param to 
     */
    play(ch,time:string,place:string,to:string){
        console.log(`This is ${time},${place}`);
        ch.sayHello(to)
    }
}).play('morning','garden','🍓')

扩展实例方法里的第一个入口参数,要预留给ts-chain-core,它不用标注类型,在调用时不用传入

扩展方法中的回调

你也许需要在扩展方法中的回调中访问ChainCore的方法,以下是上一个例子的不同的实现方式

ChainCore(null).extendFunctionsFromObject({
    sayHello: (name: string) => {
        console.log(`hello,${name}`);
    },
}).extendInstanceFunctions({
    /**
     * play a scenario
     * @param ch passed by `ChainCore` and reference to itself
     * @param fn play anything you wanted in this function
     */
    play(ch,fn:(ch:CurrentStateChainRef)=>void){
        fn.apply(null,[ch]);
    }
}).play((ch)=>{
    console.log(`This is morning,garden`);
    ch.sayHello('🍓')
})        

回调函数中的第一个参数标注为CurrentStateChainRef类型,在调用时,传入的ChainCore将自动获得此前之前扩展方法的代码提示。

实例方法同样可以设置()操作,()操作中同样也可以使用回调访问ChainCore实例

ChainCore(null).extendFunctionsFromObject({
    sayHello: (name: string) => {
        console.log(`hello,${name}`);
    },
}).extendInstanceFunctions({
    play(ch){
        return (fn:(ch:CurrentStateChainRef,time:string,place:string,to:string)=>void,time:string,place:string,to:string)=>{
            fn.apply(null,[ch,time,place,to]);
        }
    }
}).play()((ch,time,place,to)=>{
    console.log(`This is ${time},${place}`);
    ch.sayHello(to)
},'morning','garden','🍓');

限制注意📢📢 回调函数中的ChainCore实例是由扩展方法自身传入的,你可以不必在第一位传入ChainCore实例,但是我们约定只有在第一位的参数类型标注为CurrentStateChainRef,才会转换成当前的ChainCore类型

📑类型自动转换

ts-chain-core通过类型映射技术(remapping)来实现类型自动转换,以提供方便的自动补全功能,CurrentStateChainRef是一个例子。在后面还会看到更多例子。

然而该技术存在一定局限性。首先,当前typescript存在的限制,让所有的参数都实现自动转换是不切实际的,在保证使用和运算量之间做了权衡,该功能目前只能提供给函数返回类型和入参的前四个。在绝大多数情况下,这是够用的。

另外remapping也不支持泛型映射,但ts-chain-core提供了一套解决方案,后面会提到。

🔍ChainCore类型声明

由于ChainCore是一个比较复杂的类型,但有时候的你的代码中需要声明扩展了某些方法的ChainCore类型,在绝大多数情况下,直接书写ChainCore类型是不被推荐的,因为过于复杂。这里推荐用类型推导的方式来进行声明

//声明示例
function chainFactory(){
    return ChainCore(null).extendFunctionsFromObject({
                sayHello: (name: string) => {
                    console.log(`hello,${name}`);
                },
                //some other functions
            })
}

type MyChainType = ReturnType<typeof chainFactory>;

function myFunction(ch:MyChainType){
    ch.sayHello('')
}

🔍thisArg

thisArgChainCore的内置变量,用于设置运行方法中的this

它可以通过ChainCore()传入,也可以通过getThis()setThis()在运行中修改和访问

arguments cache

为了避免重复的输入,ts-chain-core的运行时缓存了上一次调用的参数,参数不传入或传入undefined时,ChainCore会传入上一次传入的值作为替代

UIKit(this)
    .show()
        ('myUsernameEditor',props.mode==='edit')  //show these controls when user need edit
        ('myAddressEditor')
        ('myEmailEditor')
        ('myPhotoEditBtn')
        ('myUsernameLabel',props.mode==='view')  //show these controls when just read
        ('myAddressLabel')
        ('myEmailLabel')

在大多数场合下,这个特性确实很有帮助,但是在有些场合,它可能会产生一些令人困惑的结果,并让人误认为是程序的bug

ChainCore(null)
    .extendInstanceFunctions({
        log(ch,...args:any){
        console.log(...args)
        }
    })
    //confused output
    .log('a','b','c')                 // stdout: a b c
    .log('d')                         // stdout: d b c  
    .log(undefined,'e',undefined)     // stdout: d e c

ts-chain-core提供了clearArgCache()方法用于清空上一次的缓存

//proper version
ChainCore(null)
    .extendInstanceFunctions({
        log(ch,...args:any){
        ch.clearArgCache();
        console.log(...args)
        }
    })
    .log('a','b','c')                 // stdout: a b c
    .log('d')                         // stdout: d 
    .log(undefined,'e',undefined)     // stdout:  e 

你也可以在调用它来清除潜在的内存泄露

const persistChainInstance =  ChainCore(null)
    .extendInstanceFunctions({
        on(ch,obj:any,eventName:string,fun:Function){
            ch.clearArgCache(); //prevent fun be cached
            obj.addEventListener(eventName,fun)
        }
    })
访问cache arguments
ChainCore(null)
    .setFunction((...args:any[])=>console.log(...args))
        ('a really really really really long expression') //a really really really really long expression
        (CacheValue(x=>'🎄'+x+'🎄')) //🎄a really really really really long expression🎄
        (CacheValue(x=>'✨'+x+'✨'))  //✨🎄a really really really really long expression🎄✨

批量调用方法

ts-chain-core提供了两个批量调用()操作的方法,请看示例

ChainCore(null)
    .setFunction((m: number, n?: number) => {
        console.log(Math.pow(m, n || 0));
    })
    .batch([3, 4], [1, 2], [5, 6], [1], [2], [3]);

ChainCore(null)
    .setFunction((m: number, n?: number) => {
        console.log(Math.pow(m, n || 0));
    })
    .zipBatch([1, 2, 3, 4, 5, 6], [2]);
    //equal to 
    .batch([1,2],[2],[3],[4],[5],[6]);

批量调用仍然遵循参数缓存原则

扩展运行时

ChainCore的运行时除了thisArgarguments cache之外,还运行使用者进行扩展,扩展的属性通过runtime接口进行访问

const ch = ChainCore(null)
    .extendRuntime<{value:string}>()  //extend a field names `value`
    .extendInstanceFunctions({
        setValue(ch,value:string){
            ch.runtime.value = value;   //write to value
        },
        getValue(ch){
            return ch.runtime.value;   //access value
        }
    });
const result = ch.setValue('hello').getValue();
console.log(result) //hello

🚀扩展运行时类型动态修改和指向

在一些应用中,无法事先确认数据的具体类型,比如,你要做一个对数组进行操作的函数库, 在定义方法的时候,无法指明需要操作的类型 (这要求泛型参数能够动态调整,这一点对于目前的typescript来说是做不到的)*。ts-chain-core提供了引用扩展运行时类型的方法ReferTo<>和动态修改运行时类型的方法ChangeChainRuntime<,>来达成目的,请看例子

const ch = ChainCore(null)
    .extendRuntime<{group:any[]}>()
    .extendInstanceFunctions({
        /**
         * 
         * @param ch 
         * @param value ReferTo<'<0>'> which means use the first generic type as input type
         * @returns ChangeChainRuntime<'group','<0>'> means change runtime `group' type to first generic type
         */
        group(ch,value:ReferTo<'<0>'>):ChangeChainRuntime<'group','<0>'>{
            ch.runtime.group = value;
            return ChangeRuntimeReturn();  //call this function to actually change runtime type
        },
        setToIndex(ch,value:ReferTo<'group[number]'>,index:number){
            ch.runtime.group[index] = value;
        },
        each(ch,fn:(item:ReferTo<'group[number]'>,index?:number)=>void){
            ch.runtime.group.forEach(fn);
        }
    });
ch.group([0,1,2,3]) //group<number[]> will change  setToIndex and each's function parameter type to number
    .setToIndex(42,0)
    //.setToIndex('42',0)  ❌error,'42' doesn't match type number
    .each((item,index)=>{
        console.log(index,item)
    })  //output : 42,1,2,3

你可能注意到,其实每个ts-chain-core扩展的方法都是泛型方法,泛型参数有四个,可以通过ReferTo<'<0>'>,ReferTo<'<1>'>,ReferTo<'<2>'>,ReferTo<'<3>'>来实现动态修改类型的目的,返回时用到ChangeChainRuntime则是用来修改运行参数的类型的。

🚀ReferTo<>语法

ReferTo<T[]>表示它是类型T的数组类型 ReferTo<T[number]> 表示它是类型T的数组元素类型.前提T必须是一个数组类型否则会得到错误类型提示 ReferTo还可以用路径符号::来表示一个复杂类型下面的某个属性的类型 下面是举例(此例仅做展示,并不表示一定需要用ReferTo<>来指向一个类型,除非它是一个需要用泛型来表达的类型)

ChainCore(null).extendRuntime<{
        actor:{
            name:string,
            birthDate:Date,
            movie:{
                title:string,
                year:number,
                genre:[{id:number,key:string,name:string}]
            }[]
        }
    }>()
    .setFunction((input:ReferTo<'actor::movie[number]::genre[number]'>)=>{
        console.log((input as any).name)
    })
    ({id:1,key:'comedy',name:'comedy'})
🚀多个扩展类型组合

使用ReMapTo<>将多个扩展运行时的属性的类型组合成一个新的类型

const v = ChainCore(null)
    .extendRuntime<{name:string,age:number}>()
    .extendInstanceFunctions({
        setPerson(ch,values:ReMapTo<'name'|'age'>){
            ch.runtime.age = (values as any).age;
            ch.runtime.name = (values as any).name;
        },
        getPerson(ch):ReMapTo<'name'|'age'>{
            return {...ch.runtime} as any;
        }
    })
    .setPerson({name:'Amy',age:42})
    .getPerson().name;
console.log(v) //Amy

Package Sidebar

Install

npm i ts-chain-core

Weekly Downloads

3

Version

0.0.3-alpha2

License

MIT

Unpacked Size

80.5 kB

Total Files

15

Last publish

Collaborators

  • luka-fans