iresine、クライアント側のデータの正規化

正規化。私たちはそれに苦しむか、共通のリポジトリにエンティティが存在するかどうかを多くチェックして独自のソリューションを作成します。それを理解してこの問題を解決してみましょう!





問題の説明

次のシーケンスを想像してみましょう。





  1. クライアントアプリケーションは、/ usersへのリクエストでユーザーのリストをリクエストし、IDが1から10のユーザーを取得します





  2. ID3のユーザーが名前を変更します





  3. クライアントアプリケーションは、/ user / 3へのリクエストを使用して、ID3のユーザーをリクエストします。





質問:  ID 3のユーザー名はアプリケーションに含まれますか?

回答: データを要求したコンポーネントによって異なります。/ユーザーへのリクエストからのデータを使用するコンポーネントは古い名前を表示します。/ user / 3のリクエストからのデータを使用するコンポーネントは、新しい名前を表示します。





結論:この場合、システム内に異なるデータセットを持つ同じ意味のエンティティがいくつかあります。





質問: なぜそれが悪いのですか?

回答: 最良の場合、ユーザーはサイトのさまざまなセクションに1人の名前が表示され、最悪の場合、古い銀行の詳細に送金されます。





ソリューションオプション

現在、この問題には次の解決策があります。













  • graphql (apollo relay)









. . , ? , ?









mobx:





class Store {
  users = new Map();

  async getUsers() {
    const users = await fetch(`/users`);
    users.forEach((user) => this.users.set(user.id, user));
  }

  async getUser(id) {
    const user = await fetch(`/user/${id}`);
    this.users.set(user.id, user);
  }
}
      
      



mobx , redux  .



graphql (apollo relay)



Apollo relay , . graphql apollo, , , .





graphql ? apollo! apollo :





...normalizes query response objects before it saves them to its internal data store.





 normalize?





Normalization involves the following steps:





1. The cache generates a unique ID for every identifiable object included in the response.

2. The cache stores the objects by ID in a flat lookup table.





apollo , . Apollo . :





const store = new Map();

const user = {
  id: '0',
  type: 'user',
  name: 'alex',
  age: 24,
};

const id = `${user.type}:${user.id}`;

store.set(id, user);
      
      



id - . , id, .





Apollo , __typename, graphql?





, . :





  • id





















:





const store = new Map();

const user = {
  id: '0',
};

const comment = {
  id: '1',
};

store.set(user.id, user);
store.set(comment.id, comment);

// ...

store.get('0'); // user
store.get('1'); // comment
      
      



, id . , id / ( - ). , id , .









:





const store = new Map();

const user = {
  id: '0',
  type: 'user', // <-- new field
};

const comment = {
  id: '1',
  type: 'comment', // <-- new field
};

function getStoreId(entity) {
  return `${entity.type}:${entity.id}`;
}

store.set(getStoreId(user), user);
store.set(getStoreId(comment), comment);

// ...

store.get('user:0'); // user
store.get('comment:1'); // comment
      
      



- , . . .





?

. - . .





  • , :





app.get('/users', (req, res) => {
  const users = db.get('users');
  const typedUsers = users.map((user) => ({
    ...user,
    type: 'user',
  }));
  res.json(typedUsers);
});
      
      



  • , :





function getUsers() {
  const users = fetch('/users');
  const typedUsers = users.map((user) => ({
    ...user,
    type: 'user',
  }));
  return typedUsers;
}
      
      



. Api, , . , .





.





iresine

iresine  .





iresine :





  • @iresine/core





  • @iresine/react-query





iresine react-query:





@iresine/core

, , .





const iresine = new Iresine();
const oldRequest = {
  users: [oldUser],
  comments: {
    0: oldComment,
  },
};
// new request data have new structure, but it is OK to iresine
const newRequest = {
  users: {
    0: newUser,
  },
  comments: [newComment],
};

iresine.parse(oldRequest);
iresine.parse(newRequest);

iresine.get('user:0' /*identifier for old and new user*/) === newRequest.users['0']; // true
iresine.get('comment:0' /*identifier for old and new comment*/) === newRequest.comments['0']; // true
      
      



, , @iresine/core :





entityType + ':' + entityId;
      
      



@iresine/core  type



, id  id



. , . apollo:





const iresine = new Iresine({
  getId: (entity) => {
    if (!entity) {
      return null;
    }
    if (!entity.id) {
      return null;
    }
    if (!entity.__typename) {
      return null;
    }
    return `${entity.__typename}:${entity.id}`;
  },
});
      
      



id:





const iresine = new Iresine({
  getId: (entity) => {
    if (!entity) {
      return null;
    }
    if (!entity.id) {
      return null;
    }
    return entity.id;
  },
});
      
      



@iresine/core , ? :





const user = {
  id: '0',
  type: 'user',
  jobs: [
    {
      name: 'milkman',
      salary: '1$',
    },
    {
      name: 'woodcutter',
      salary: '2$',
    },
  ],
};
      
      



user , jobs? type id! @iresine/core : , .





@iresine/core , . ! .





@iresine/react-query

react-query , . , iresine.





@iresine/react-query react-query. @iresine/core react-query. react-query , iresine.





import Iresine from '@iresine/core';
import IresineReactQuery from '@iresone/react-query';
import {QueryClient} from 'react-query';

const iresineStore = new IresineStore();
const queryClient = new QueryClient();
new IresineReactQueryWrapper(iresineStore, queryClient);
// now any updates in react-query store will be consumbed by @iresine/core
      
      



( ):





. . . ,   ,  iresine








All Articles