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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@aloudata/recoil-normalize-orm

v0.1.0-beta.1

Published

# 介绍

Downloads

18

Readme

normalize-orm

介绍

基于recoil实现的管理数据实体的工具,能够保证前端应用中数据的统一性。

能够保证同一个数据实例,不论在页面中展示多少处,在内存中只存在同一份数据,对它产生的变更能作用到所有引用它的地方。借鉴了服务端应用数据库的思想,即数据实例只存在一份,并按id存储。

案例

假设有图书的数据如下

[{
    id: 1,
    name: 'book1',
    author: {
        userId: '1',
        name: 'user1',
        age: 10
    },
    comments: [{
        cid: 'c1',
        text: 'good book',
        user: {
            userId: '2',
            name: 'user2',
            age: 20
        },
    }, {
        cid: 'c2',
        text: 'very good',
        user: {
            userId: '3',
            name: 'user3',
            age: 30,
        },
    }],
    summary: 'hello world',
}, {
    id: 2,
    name: 'book2',
    author: {
        userId: '2',
        name: 'user2',
        age: 20
    },
    comments: [{
        cid: 'c3',
        text: 'hehe',
        user: {
            userId: '1',
            name: 'user1',
            age: 10
        },
    }, {
        cid: 'c4',
        text: 'haha',
        user: {
            userId: '3',
            name: 'user3',
            age: 30
        },
    }],
    summary: 'new start',
}]

以上数据的业务模型,有3个实体:图书(Book)、评论(Comment)、用户(User)。

图书实体有名称、作者、评论这些字段,评论实体又有内容、用户信息这些字段。其中作者和用户信息,对应的都是用户实体。

入库以后,数据会以如下格式进行存储,且在整个前端应用内只有唯一的一份。

// Book表
{
    '1': {
        id: 1,
        name: 'book1',
        author: '1',
        comments: ['c1', 'c2'],
        summary: 'hello world',
    },
    '2': {
        id: 2,
        name: 'book2',
        author: '2',
        comments: ['c3', 'c4'],
        summary: 'new start',
    }
}

// User
{
    '1': {
        userId: '1',
        name: 'user1',
        age: 10
    },
    '2': {
        userId: '2',
        name: 'user2',
        age: 20
    },
    '3': {
        userId: '3',
        name: 'user3',
        age: 30,
    },
}

// Comment
{
    'c1': {
        cid: 'c1',
        text: 'good book',
        user: '2',
    },
    'c2': {
        cid: 'c2',
        text: 'very good',
        user: '3',
    },
    'c3': {
        cid: 'c3',
        text: 'hehe',
        user: '1',
    },
    'c4': {
        cid: 'c4',
        text: 'haha',
        user: '3',
    }
}

示例

初始化

示例数据如下:

export const booksData = [{
    id: 1,
    name: 'book1',
    author: { userId: '1', name: 'user1', age: 10 },
    comments: [{
        cid: 'c1',
        text: 'good book',
        user: { userId: '2', name: 'user2', age: 20 },
        tags: [{ tid: 't01', name: '标签1', }, { tid: 't02', name: '标签2', }]
    }, {
        cid: 'c2',
        text: 'very good',
        user: { userId: '3', name: 'user3', age: 30, },
        tags: [{ tid: 't01', name: '标签1', }, { tid: 't03', name: '标签3', }]
    }],
    summary: 'hello world',
}, {
    id: 2,
    name: 'book2',
    author: { userId: '2', name: 'user2', age: 20 },
    comments: [{
        cid: 'c3',
        text: 'hehe',
        user: { userId: '1', name: 'user1', age: 10 },
        tags: [{ tid: 't04', name: '标签4', }]
    }, {
        cid: 'c4',
        text: 'haha',
        user: { userId: '3', name: 'user3', age: 30 },
        tags: [{ tid: 't02', name: '标签2', }, { tid: 't05', name: '标签5', }]
    }],
    summary: 'new start',
}];

先定义模型:

// 图书模型
export const Book = {
    // 模型名称
    name: 'Book',
    // 主键字段名称
    idAttr: 'id',
    // 关联模型的字段名,及关联模型名
    fields: {
        author: 'User',
        comments: 'Comment'
    }
};

export const User = {
    name: 'User',
    idAttr: 'userId',
};

export const Comment = {
    name: 'Comment',
    idAttr: 'cid',
    fields: {
        user: 'User',
        tags: 'Tag',
    },
};

export const Tag = {
    name: 'Tag',
    idAttr: 'tid',
};

初始化整个库

const { createModel, } = initModel();

// 初始化各模型表
// createModel接受2个泛型,第一个是完整数据类型如IBookItem,第二个是处理后的数据类型如INormalizedBookItem
// INormalizedBookItem 中关联其他模型的字段都已被处理成id或id数组。第二个泛型可选,如果像IUserItem这样字段不再关联其他模型的情况,则不必传入第二个泛型
const bookStore = createModel<IBookItem, INormalizedBookItem>(Book);
const userStore = createModel<IUserItem>(User);
const commentStore = createModel<ICommentItem, INormalizedCommentItem>(Comment);
const tagStore = createModel<ITagItem>(Tag);

interface IBookItem {
  id: number;
  name: string;
  author: IUserItem;
  comments: ICommentItem[];
}

interface INormalizedBookItem {
  id: number;
  name: string;
  author: string;
  comments: string[];
}

interface IUserItem {
  userId: string;
  name: string;
  age: number;
}

interface ICommentItem {
  cid: string;
  text: string;
  user: IUserItem;
  tags: ITagItem[];
}

interface INormalizedCommentItem {
  cid: string;
  text: string;
  user: string[];
}

interface ITagItem {
  tid: string;
  name: string;
}

插入数据

// 在自定义hook中使用
function useCustomHook() {
    // 可以直接获取book的set方法
    const set = bookStore.useSetState();

    // 也可以和shallow数据一起获取set方法,类似于react的useState
    const [shallowData, set] = bookStore.useShallowState();
    // shallow顾名思义,是浅层的意思。名字有shallow的方法,获取到的数据中,与其他模型关联的字段的值都会被替换为对应的id
    // 如 Book 模型中单条数据的 shallow 数据:{ id: 1, name: 'book1', author: '1', comments: ['c1', 'c2'], summary: 'hello world', }
    // 其中关联 User 和 Comment 模型的字段 author 和 comments,值已被替换为对应的id

    // 也可以和完整数据一起获取set方法,类似于react的useState
    const [data, set] = bookStore.useState();

    useEffect(() => {
        const ids = set(booksData);
    }, []);
}

set方法,会将数据存储成以主键值为键的Map,并根据模型定义中定义的模型关联关系,将数据进行拆分。并返回当前模型新插入数据的id值。

set方法,支持插入批量数据,也支持插入单个数据,也支持修改现有数据的部分字段,有以下几种调用方式:

set(list: Partial<T>[]): IModelId[]; // 数据中必须包含id字段

set(item: Partial<T>): IModelId; // 数据中必须包含id字段

set(id: IModelId, item: Partial<T>): IModelId; // 由于指定了id,因此数据中可以不包含id字段

type IModelId = string | number;

读取数据

// 在hook函数中
function useCustomHook() {
    const idList = useMemo(() => [1, 2], []);

    // 入参 idList 需要是个immutable且多次渲染中保持不变的数据,如atom里取出的值。这样可以命中useGetValue中的缓存,保证books在多次渲染中返回同一个对象。方便在下游的useCallback中使用books数据。
    // 下面的 useShallowValue,useState,useShallowState 也是如此

    // 批量获取完整数据,books的数据和初始化章节中booksData数据一样
    const books = bookStore.useGetValue(idList);

    // 批量获取本模型下数据,shallowBooks获取到的是Book模型下只包含其他模型id的数据
    // [{ id: 1, name: 'book1', author: '1', comments: ['c1', 'c2'], summary: 'hello world', }, { id: 2, name: 'book2', author: '2', comments: ['c3', 'c4'], summary: 'new start', }]
    const shallowBooks = bookStore.useShallowValue(idList);

    const singleId = useMemo(() => 1, []);
    // 获取单条完整数据
    const singleBook = bookStore.useGetValue(singleId);
    // 获取单条本模型下的数据
    const singleShallowBook = bookStore.useGetValue(singleId);

    // useState 返回的内容等同于 [bookStore.useGetValue(...), bookStore.useSetState()], useSetState将在下文介绍。
    const [bookValue, setBookState] = bookStore.useState(singleIdOrIdList);
}

删除数据

删除整个库所有的数据

const { useChangeData, } = initModel();

// 在hook函数中
function useCustomHook() {
    // 调用reset方法可以清空整个库中的所有数据
    const { reset, } = useChangeData();
}

删除单个模型中的部分数据

// 在hook函数中
function useCustomHook() {
    const remove = bookStore.useRemoveState();
    useEffect(() => {
        // 删除id值为1和2的数据
        remove([1, 2]);
    }, []);
}

remove方法支持删除单条或批量的数据,方法定义如下:

remove(id: IModelId | IModelId[]) => void

type IModelId = string | number;

但只能删除本模型下的数据,不会删除关联的其他模型下的数据,因为删除其他模型下的数据可能产生其他影响,不太安全。