npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gent-data

v1.0.2

Published

> 优雅的数据管理工具

Readme

Gent-Data 优雅的数据管理工具/思想

基于RxJS@6,函数式响应式编程,集中管理数据流。

前言1:为什么要gent-data

现在的vuex,redux,mobx等工具都很优秀很成熟了,为什么还要自己写一个?

所谓工具,自己觉得好用的才是最好的。写这个工具的初衷,不是为了追赶潮流,主要是为了整理一个很适合自己使用习惯的数据管理工具,当然如果能帮到他人就更好了。

虽然业界有很多十分强大的状态管理工具,但在实际项目中我们发现,用起来也存在着一些烦恼:

  • 概念相对复杂,使用相对很繁琐,考验团队规范和开发者记忆力
  • 分层太多,逻辑分拆后不是很清晰,小项目使用的时候有大炮打蚊子的感觉
  • 规范太死,不够灵活,比如特殊情况下,我要获取一个action的执行状态,以显示一个loading状态,只能通过定义一个额外的state上的数据。
  • 还有就是,我们不得不针对不同的项目(vue,react等)使用一套完全不同的状态管理工具。

以下是在使用当下流行的状态管理工具时遇到的一些烦恼:

  • Mobx:上手难度相对较大,容易踩坑,虽然各种坑在文档上都已经列出来了,但使用的时候还是感觉容易踩。
  • Flux or Redux:使用起来比较繁琐,各种action,reducer写起来很考验记忆力,对于接盘的人就更痛苦了。虽然可以通过命名规范来解决,但是有点繁琐
  • Vuex:和vue绑定,其他项目没法用。

还有,这些各种工具都有一个共同的烦恼:

  • 我执行一个action,没办法直接获取这个执行的结果(比如我也显示隐藏loading)。单向数据流没错,但很多情况为了一个action的执行情况去额外定义一个state状态属性。

前言2:gent-data数据管理思想和原则

gent-data设计的几个目标:

  • 化繁为简,使用简单,概念简单。
  • 设计灵活,便于扩展和集成。
  • 业务逻辑清晰,使用的时候不要记很多东西,便于调试。
  • 一套数据,适用于各种场景(React,Vue,纯JS等……),很方便与各种工具集成。

gent-data里,我们发现,所有数据管理归纳起来,无外乎都可以划分为以下几部分:

  • 【数据存储】:将数据存在一个地方,供之后或者其他消费者取出使用,它们有:自定义store(比如vuex的store), LocalStorage, SessionStorage, Cookie 等。
  • 【数据流动、和变换】:一个事件激发一个数据,这个数据沿着一条或者多条路径不断改变、传输,最终到达目的地。比如,点击按钮=>参数=>服务器=>数据=>store改变=>通知页面更新
  • 【变更通知】:数据或者发起的时候通知相关的消费者。

那么,我们就针对这两部分来管理就行了:

  • gent-data(rxjs): 解决数据流动变换, RxJS有着响应式数据流管理的很强大的功能,gent-data是一个扩展工具,帮助我们在rxjs的基础上更好的管理数据流。
  • gent-store: 解决数据存储。
  • 对于变更通知的问题,gent-storegent-data都有涉及。

安装使用

安装:

npm i gent-data rxjs -S

使用:

import {
  logMiddleware,
  observePromise,
  observeValue,
  sourceToObservable,
  concatMapSource,
  mergeMapSource,
  switchMapSource,
  FlowGroup
} from 'gent-data';

store:gent-store的情况gent-store文档

开始使用之前,先理解一下两个基本的RxJS的概念。

Observable: 可观察对象。

  • 描述一个未来的,有一定发射规律的,可调用的,数据或者事件的集合。
  • 一个Observable可被订阅(subscribe)N次,每次被订阅即产生一个独立的执行(subscription),然后开始按照规律发射数据。
  • 一次执行可以发射多个数据(next),期间可能发射next,error,complete三个事件。
  • 执行可以被取消(unsubscribe)。
  • 具体参见RxJS Observable 或者 RxJs Beta Docs

Observer: 数据观察者。

  • 描述数据的接收者。

  • 一个观察者是一个有三个回调函数的对象,用来监听数据: { next(value){}, error(err){}, complete(){} }

  • 具体参见RxJS Observer 或者 RxJs Beta Docs

    • next: 接收每一个数据。
    • error: 错误时调用。
    • complete: 完成时调用。

然后,再了解下gent-data加入很少的几个概念。

Dynamic Source: 动态数据源,

  • 一个动态数据源,是一个可以根据传入条件,动态制造可观察新数据的机器。
  • 动态数据源是一个制造数据的方法,每次调用接收两个参数,resource:制造数据的资源,observer:数据的观察者。
  • 动态数据源可以被多次执行。
  • 每次执行可发送多个数据。
  • 执行可以被取消(可选)。
  • 具有Observable的特性(发送多个数据,有next,error,complete三个事件,可以取消执行...),可以很方便地转换为Observable

下面是一个动态数据源的例子:每次执行,根据传入的page,count等参数,发射一个从服务器拉取的用户列表数据。

import request from 'superagent';
import { observePromise } from 'gent-data';

// 获取用户列表
function userList(resource, observer) {
  const params = {page: 1, count: 20, ...resource};
  const req = request.post('/user')
    .send(params);

  // 将promise 和观察者绑定,根据promise的状态自动调用observer的方法
  observePromise(req, observer);

  // 可以返回一个取消执行的方法
  return function cancel() {
    req.abort();
  }
}

// 等价于:
// 获取用户列表
function userList(resource, observer) {
  const params = {page: 1, count: 20, ...resource};
  const req = request.post('/user')
    .send(params);

  req
    .then(v => {
      observer.next(v);
      observer.complete();
    })
    .catch(err => {
      observer.error(err);
      observer.complete();
    });

  // 可以返回一个取消执行的方法
  return function cancel() {
    req.abort();
  }
}

下面是将动态数据源转换为Observable的例子:

import { userList } from '../source/user';
import { sourceToObservable } from 'gent-data';

// 转换
let observable = sourceToObservable(userList, {page: 1, count: 10});

// 订阅
let subscription = observable.subscribe({
  next(data) {
    //
  },
  error(err) {
    //
  },
  complete() {
    //
  }
});

// 可以取消订阅
subscription.unsubscribe();
// 取消订阅的时候如果这个source还没有完成,会调用上面source返回的cancel方法:
// return function cancel() {
//   req.abort();
// }

Flow: 数据流。

下面pipe的操作请参考RxJS pipe文档

  • 定义一个有一定规律的,数据流动变换的轨迹,比如点击按钮获取用户列表,并加入store,更新界面的一系列数据流动,可以看作一个数据流。
  • 每个数据流是一个方法,所做的工作就是将一个Observable转换为另外一个Observable
  • 接受参数为,变换前的Observable
  • 返回结果为,变换后的Observable
  • 每个数据流可以进行多次数据变换,也可以流过多个数据源。
  • flow只是一定数据流动变换的集合,一个操作反馈中的数据变动,可以用一个flow,也可以了流经多个flow
import { Observable } from 'rxjs';
import { map, filter, scan, concatMap } from 'rxjs/operators';
import { sourceToObservable } from 'gent-data';
import { userList } from '../source/user';

// 订阅获取用户列表的数据流,just a sample
function getUserListFlow(observable) {
  return observable
    .pipe(
      map((args) => { 
        args.keywords += ' xxx';
        return args;
      })
    )
    .pipe(
      concatMap((v) => {
        return sourceToObservable(userList, v);
      })
    );
}

// 调用flow
let params = { page: 1, count: 20, keywords: 'test' };
let observable = Observable.from([params]);

// 流变换
observable = getUserListFlow(observable);

// 订阅数据流
let subscription = observable.subscribe({
  next(users) {
    console.log(users)
  },
  error(err) {
    //
  },
  complete() {
    //
  }
});

FlowGroup:数据流集合。

  • 数据流集合主要用来将一组数据流集中起来,便于管理。
  • 一个数据流集合可以为添加在上面的每条数据流flow,统一添加前置和后置中间件,以便于打印调试和统一变换。
  • 可以将动态数据源直接添加到上面成为数据流
  • 一个项目可以使用多个FlowGroup

推荐:小型项目,一个项目使用一个FlowGroup, 大型项目,一个模块一个FlowGroup

import { FlowGroup } from 'gent-data';

const testFlows = new FlowGroup({
  name: 'test', // 便于调试
  beforeMiddlewares: [],
  afterMiddlewares: []
});

Middleware: 中间件。

  • 一个中间件也是一个数据流flow,但是flow作为中间件的时候会额外接收一个参数。

下面是一个简单的打印日志的中间件:

function logFlow(observable, middlewareOptions={}) {
  let {
    // 分组名
    groupName,
    // 流的名字
    flowName,
    // 中间件类型:before / after
    middleware
  } = middlewareOptions;

  // 不是作为中间件,而是直接作为flow调用时
  if (!middleware) return observable;

  return observable
    .pipe(
      map((data) => { 
        if (middleware == 'before') {
          console.log(`${groupname}.${flowName} in:`);
        }else if (middleware == 'after') {
          console.log(`${groupname}.${flowName} out:`);
        }

        console.log(JSON.stringify(data))
      })
    );

API

  • Dynamic Source: 动态数据源,一个函数,接收两个参数, res(数据源产生的条件或者资源),observer(数据的观察者),可以选择性返回一个用于终止数据源的方法。
export function canCancelSource(res, observer) {
  let timer = null;
  let promise = new Promise((resolve, reject) => {
    timer = setTimeout(() => {
      resolve(`${res}-cancel`);
    }, 2000);
  });

  observePromise(promise, observer);

  return function cancel() {
    clearTimeout(timer);
  }
}
  • observePromise(promise, observer): 将promise实例观察者绑定,resolve或者reject的时候自动调用observer.nextobserver.error, 并且调用observer.complete
import { observePromise } from 'gent-data';

export function promiseSource(res, observer) {
  let promise = Promise.resolve(`${res}-promise`);
  observePromise(promise, observer);
}
  • observeValue(value, observer): 将一个值观察者绑定,自动调用observer.nextobserver.complete
import { observeValue } from 'gent-data';

export function syncSource(res, observer) {
  observeValue(`${res}-sync`, observer);
}
  • sourceToObservable(source, res): 将source转换为Observable,转换的时候里面的数据发射逻辑并不会会执行,只有observable被订阅(subscribe)的时候才会执行。
function userListSource(resource, observer) {
  const params = {page: 1, count: 20, ...resource};
  const req = request.post('/user')
    .send(params);

  // 将promise 和观察者绑定,根据promise的状态自动调用observer的方法
  observePromise(req, observer);

  // 可以返回一个取消执行的方法
  return function cancel() {
    req.abort();
  }
}

// 转换的为Observable的时候,请求还未发出
let observable = sourceToObservable(userListSource, { page: 1, count: 10 });

// userListSource执行一次,发出请求
let subscription1 = observable.subscribe({
  //...
});

// userListSource再执行一次,发出另一个请求
let subscription2 = observable.subscribe({
  //...
});

// 终止1发出的请求 req.abort();
subscription.unsubscribe();
  • concatMapSource(source): 将source转换为数据流flow。调用这个flow的时候,流入的observable每一个数据,会根据source生成一个新的observable,然后通过concatMap操作符将这些observable连接为一个流出的observable
import { of } from 'rxjs';
import { concatMapSource } from 'gent-data';
import { userListSource } from '../source/user';

let observable = of({page: 1, count: 10});
let userListFlow = concatMapSource(userListSource);

let newObservable = userListFlow(observable);
// 还可以应用更多flow:
// newObservable = ooxxFlow(newObservable); 

let subscription = newObservable.subscribe({
  // ...
});
  • mergeMapSource(source): 和concatMapSource一样,只不过通过mergeMap操作符连接observable。

  • switchMapSource(source): 和concatMapSource一样,只不过通过switchMap操作符连接observable。

  • logMiddleware: 日志中间件,打印日志信息。

FlowGroup

FlowGroup流程图.

  • 创建实例
import { FlowGroup, logMiddleware } from 'gent-data';

const myFlows = new FlowGroup({
  name: 'MyFlows',
  beforeMiddlewares: process.env.NODE_ENV === 'production' ? [] : [logMiddleware],
  afterMiddlewares: process.env.NODE_ENV === 'production' ? [] : [logMiddleware]
});

import { testFlow } from '../flows/test';
import * as TestSoureces from '../sources/test';

// 添加flow
myFlows.addFlow(testFlow, 'test');

// 批量添加source
myFlows.addSources(TestSoureces, 'ooxx');

export { myFlows };
  • 使用
import { of } from 'rxjs';
import { myFlows } from '../flowGroups/myFlows';

let observable = of('hello');
// 应用test flow
observable = myFlows.flow('test')(observable);
// 应用 source.test flow
observable = myFlows.flow('source.test')(observable);

observable.subscribe({
  //
});
  • group.addFlow(fn, name="Anonymous"): 注册flow到group, fn: flow方法。
  • group.addFlows(flowMap={}, context=""): 批量注册flow,以keyname,如果context不为空,则namecontext.key
  • group.addSource(source, name="Anonymous", operatorType="concatMap"):将source转换为flow,再注册到group,operatorType[concatMap,mergeMap,switchMap]中的一个,默认concatMap
  • group.addSources(sourceMap, context="", operatorType="concatMap"): 将一组source转换为flow,再注册到group,默认concatMap
  • group.setBeforeMiddlewares(middlewares=[]): 设置前置中间件。
  • group.setAfterMiddlewares(middlewares=[]): 设置后置中间件。
  • group.flow(name): 获取中间件。

推荐目录结构

---- data
  |---- sources
  |---- flows
  |---- flowGroups

辅助工具

  • gent-store
import mainStore from '../stores/main';

export function storeSource(res, observer) {
  return mainStore.subscribe(observer);
}
  • gent-vue
  • gent-react

完整实例

  • gent-demo