Redux:API呼び出し中に考える必要性をなくそうとする、パート2

APIによって取得された各モデルの同じレデューサーとアクションクリエーターの絶え間ない作成をなくすことができるパッケージを作成したいと考えています。







最初の部分はこの記事です。 その中で、将来のパッケージの構成を作成し、アクションクリエーター、ミドルウェア、およびレデューサーを含める必要があることがわかりました。 開発を始めましょう!







アクションクリエーター



最も簡単なことから始めます-アクション作成者。 ここでの貢献は最小限です-構成を考慮してredux-api-middleware



従来のアクションクリエーターを作成するだけです。







ユーザーを取得するには、次のようになります。







  import {CALL_API} from 'redux-api-middleware'; const get = () => ({ [CALL_API]: { endpoint: 'mysite.com/api/users', method: 'GET', types: ['USERS_GET', 'USERS_SUCCESS', 'USERS_FAILURE'] } });
      
      





実際には、ヘッダー、資格情報をさらに追加できます。 リクエストが成功した場合、USERS_SUCCESSを取得し、action.payloadのAPIが受信したデータを取得します。 エラーが発生すると、actions.errorsにエラーがあるUSERS_FAILUREが返されます。 これはすべて、 ドキュメントで詳細に説明されています







将来、推論を簡単にするために、ペイロードのデータはすでに正規化されていると想定しています。 クリエイターを近代化してすべてのエンティティを取得する方法に興味があります。 すべてが非常に簡単です。必要なエンティティを返すために、このエンティティの名前を作成者に渡します。







  import {CALL_API} from 'redux-api-middleware'; const initGet = (api) => (entity) => ({ [CALL_API]: { endpoint: api[entity].endpoint, // endpoint      method: 'GET', types: api[entity].types //  actions      } });
      
      





また、GETパラメーターによるサーバー応答のフィルターを追加して、必要なデータのみを取得し、余分なものをドラッグしないようにする必要があります。 GETパラメーターを辞書として渡し、それらを個別のobjectToQueryメソッドでシリアル化することを好みます。







  import {CALL_API} from 'redux-api-middleware'; const initGet = (api) => (entity, params) => ({ [CALL_API]: { endpoint: `${api[entity].endpoint}${objectToQuery(params)}`, method: 'GET', types: api[entity].types } });
      
      





作成者自身を初期化します:







 const get = initGet(config.api);
      
      





ここで、必要な引数を使用してgetメソッドを呼び出し、必要なデータの要求を送信します。 次に、受信したデータを保存する方法に注意する必要があります-レデューサーを作成します。







減速機



より正確には、2。 1つはエンティティをストアに配置し、もう1つはストアに到着します。 それらを1つの場所に保持するのは悪い考えです。その場合、クリーンなデータをクライアント上のアプリケーションのローカル状態と混合するからです(結局、各クライアントには独自のデータ到着時間があります)。







ここでは、同じsuccessActionTypesとreact-addons-update



addons react-addons-update



が必要です。これにより、ストアの耐性が確保されます。 ここでは、エンティティから各エンティティを調べて、個別に$マージする必要があります。つまり、defaultStoreとreceivedDataのキーを結合します。







  const entitiesReducer = (entities = defaultStore, action) => { if (action.type in successActionTypes) { const processedData = {}; const receivedData = action.payload.entities || {}; for (let entity in receivedData) { processedData[entity] = { $merge: receivedData[entity] }; } return update(entities, processedData); } else { return entities; } };
      
      





timestampReducerについても同様ですが、ストア内のデータの現在の到着時間を設定します。







  const now = Date.now(); for (let id in receivedData[entity]) { entityData[id] = now; } processedData[entity] = { $merge: entityData };
      
      





初期化中にスキーマまたはライフタイム、successActionTypesが必要です-アクション作成者に同じコードを記述しました。







defaultStateを取得するには、次を実行します。







 const defaultStore = {}; for (let key in schema) { //  lifetime,   reducer' defaultStore[key] = {}; }
      
      





successActionTypesは、API構成から取得できます。







  const getSuccessActionTypes = (api) => { let actionTypes = {}; for (let key in api) { actionTypes[api[key].types[1]] = key; } return actionTypes; };
      
      





もちろん、これは単純なタスクですが、そのような単純なレデューサーを使用すると、各データ型に対して独自のレデューサーを作成する時間を大幅に節約できます。







ルーチン作業が完了しました-パッケージのメインコンポーネントに進みます。これは、本当に必要なデータのみを処理し、同時に考えさせないようにします。







ミドルウェア



正規化されたデータがすぐに届くと信じていることを思い出させてください。 次に、ミドルウェアでは、エンティティで取得したすべてのデータを調べて、欠落している関連エンティティのIDのリストを収集し、このデータのAPIリクエストを行う必要があります。







  const middleware = store => next => action => { if (action.type in successActionTypes) { //   action,     const entity = successActionTypes[action.type]; //    const receivedEntities = action.payload.entities || {};; //    const absentEntities = resolve(entity, store.getState(), receivedEntities); //   for (let key in absentEntities) { const ids = absentEntities[key]; //   id  if (ids instanceof Array && ids.length > 0) { //     store.dispatch(get(key, {id: ids})); //  action,         } } } return next(action); }
      
      





successActionTypes、解決し、初期化時にミドルウェアを渡す必要性を取得します。







resolveメソッドを実装するだけで、どのデータが欠落しているかを判断できます。 これはおそらく最も興味深い重要な部分です。







簡単にするために、ストアデータはstore.entitiesに格納されていると仮定します。 構成内の別のポイントとして取り出して、そこにレデューサーをアタッチできますが、この段階では重要ではありません。







この種の辞書であるabcentEntitiesを返す必要があります。







 const absentEntities = {users: [1, 2, 3], posts: [107, 6, 54]};
      
      





リスト内の欠落データのIDが保存されている場所。 欠落しているデータを判別するには、構成からのスキーマとライフタイムが役立ちます。







一般に、外部リストには、1つのIDだけでなくIDリストが含まれる場合があります。多対多および1対多の関係をキャンセルした人はいません。 外部キーのデータ型を確認することでこれを考慮する必要があり、もしあれば、リストの全員を対象にします。







 const resolve = (type, state, receivedEntities) => { let absentEntities = {}; for (let key in schema[type]) { //    foreign key  const keyType = schema[typeName][key]; //   foreign key absentEntities[keyType] = []; //     for (let id in receivedEntities[type]) { //      //    let keyIdList = receivedEntities[type][id][key]; if (!(keyIdList instanceof Array)) { keyIdList = [keyIdList]; } for (let keyId of keyIdList) { // ,   id  store const present = state.entities.hasOwnProperty(keyType) && state.entities[keyType].hasOwnProperty(keyId); // ,     receivedEntities const received = receivedEntities.hasOwnProperty(keyType) && receivedEntities[keyType].hasOwnProperty(keyId); // ,    ? const relevant = present && !!lifetime ? state.timestamp[keyType][keyId] + lifetime[keyType] > Date.now() : true; //      action,    store  ,    absent   if (!(received || (present && relevant))) { absentEntities[keyType].push(keyId); } } } };
      
      





それだけです-少し不可解なロジックとすべてのケースの検討、そして私たちの機能は準備ができています。 初期化中に、設定からスキーマとライフタイムを忘れずに渡す必要があります。







おわりに



一般に、次のことを前提にすれば、すべてがすでに機能します。







  1. テストせずにできます。
  2. 誰も設定を間違えることはありません。
  3. データはすでに正規化されたミドルウェアに送られます。


これらのすべてのポイント(特に最初のポイント!)は慎重に解決する必要があります。これは3番目のパートで行います。 しかし、これはそれほど面白くありません。目標を達成するコードのほとんどすべてが既に書かれているため、自分で見たりテストしたい人のために、リンクを提供しています。










All Articles