10

これまでのところ、他の Flux 実装よりも Redux の方が好きで、これを使用してフロントエンド アプリケーションを書き直しています。

私が直面している主な苦労点:

  1. 重複したリクエストの送信を避けるために、API 呼び出しのステータスを維持します。
  2. レコード間の関係を維持します。

最初の問題は、各タイプのデータのサブステートにステータス フィールドを保持することで解決できます。例えば:

function postsReducer(state, action) {
  switch(action.type) {
    case "FETCH_POSTS":
      return {
        ...state,
        status: "loading",
      };
    case "LOADED_POSTS":
      return {
        status: "complete",
        posts: action.posts,
      };
  }
}

function commentsReducer(state, action) {
  const { type, postId } = action;
  switch(type) {
    case "FETCH_COMMENTS_OF_POST":
      return {
        ...state,
        status: { ...state.status, [postId]: "loading" },
      };
    case "LOADED_COMMENTS_OF_POST":
      return {
        status: { ...state.status, [postId]: "complete" },
        posts: { ...state.posts, [postId]: action.posts },
      };
  }
}

これで、投稿用の Saga とコメント用の別の Saga を作成できます。各サガは、リクエストのステータスを取得する方法を知っています。しかし、それはすぐに多くの重複コードにつながるでしょう (例: 投稿、コメント、いいね、リアクション、作成者など)。

重複したコードをすべて回避する良い方法があるかどうか疑問に思っています。

2 番目の問題は、redux ストアから ID でコメントを取得する必要があるときに発生します。データ間の関係を処理するためのベスト プラクティスはありますか?

ありがとう!

4

3 に答える 3

2

まず、投稿をフェッチするための一般的なアクション クリエーターを作成できます。

function fetchPost(id) {
  return {
   type: 'FETCH_POST_REQUEST',
   payload: id,
  };
}

function fetchPostSuccess(post, likes, comments) {
  return {
    type: 'FETCH_POST_SUCCESS',
    payload: {
      post,
      likes,
      comments,
    },
  };
}

この fetch post アクションを呼び出すと、onFetchPost sagaがトリガーされます。

function* watchFetchPost() {
  yield* takeLatest('FETCH_POST_REQUEST', onFetchPost);
}

function* onFetchPost(action) {
  const id = action.payload;

  try {
    // This will do the trick for you.
    const [ post, likes, comments ] = yield [
      call(Api.getPost, id),
      call(Api.getLikesOfPost, id),
      call(Api.getCommentsOfPost, id),
    ];

    // Instead of dispatching three different actions, heres just one!
    yield put(fetchPostSuccess(post, likes, comments));
  } catch(error) {
    yield put(fetchPostFailure(error))
  }
}
于 2016-09-07T19:46:19.597 に答える
2

私のプロジェクトでもまったく同じ問題がありました。redux-saga を試してみましたが、副作用の redux を使用してデータ フローを制御するのは本当に賢明なツールのようです。ただし、リクエストの重複やデータ間の関係の処理など、実際の問題に対処するのは少し複雑です。

そこで、この問題を解決するために小さなライブラリ ' redux-dataloader ' を作成しました。

アクションクリエーター

import { load } from 'redux-dataloader'
function fetchPostsRequest() {
  // Wrap the original action with load(), it returns a Promise of this action. 
  return load({
    type: 'FETCH_POSTS'
  });
}

function fetchPostsSuccess(posts) {
  return {
    type: 'LOADED_POSTS',
    posts: posts
  };
}

function fetchCommentsRequest(postId) {
  return load({
    type: 'FETCH_COMMENTS',
    postId: postId
  });
}

function fetchCommentsSuccess(postId, comments) {
  return {
    type: 'LOADED_COMMENTS_OF_POST',
    postId: postId,
    comments: comments
  }
}

リクエスト アクション用のサイド ローダーを作成する

次に、'FETCH_POSTS' と 'FETCH_COMMENTS' のデータ ローダーを作成します。

import { createLoader, fixedWait } from 'redux-dataloader';

const postsLoader = createLoader('FETCH_POSTS', {
  success: (ctx, data) => {
    // You can get dispatch(), getState() and request action from ctx basically.
    const { postId } = ctx.action;
    return fetchPostsSuccess(data);
  },
  error: (ctx, errData) => {
    // return an error action
  },
  shouldFetch: (ctx) => {
    // (optional) this method prevent fetch() 
  },
  fetch: async (ctx) => {
    // Start fetching posts, use async/await or return a Promise
    // ...
  }
});

const commentsLoader = createLoader('FETCH_COMMENTS', {
  success: (ctx, data) => {
    const { postId } = ctx.action;
    return fetchCommentsSuccess(postId, data);
  },
  error: (ctx, errData) => {
    // return an error action
  },
  shouldFetch: (ctx) => {
    const { postId } = ctx.action;
    return !!ctx.getState().comments.comments[postId];
  },
  fetch: async (ctx) => {
    const { postId } = ctx.action;
    // Start fetching comments by postId, use async/await or return a Promise
    // ...
  },
}, {
  // You can also customize ttl, and retry strategies
  ttl: 10000, // Don't fetch data with same request action within 10s
  retryTimes: 3, // Try 3 times in total when error occurs
  retryWait: fixedWait(1000), // sleeps 1s before retrying
});

export default [
  postsLoader,
  commentsLoader
];

redux-dataloader を redux ストアに適用する

import { createDataLoaderMiddleware } from 'redux-dataloader';
import loaders from './dataloaders';
import rootReducer from './reducers/index';
import { createStore, applyMiddleware } from 'redux';

function configureStore() {
  const dataLoaderMiddleware = createDataLoaderMiddleware(loaders, {
    // (optional) add some helpers to ctx that can be used in loader
  });

  return createStore(
    rootReducer,
    applyMiddleware(dataLoaderMiddleware)
  );
}

データ チェーンの処理

それでは、dispatch(requestAction) を使用してデータ間の関係を処理します。

class PostContainer extends React.Component {
  componentDidMount() {
    const dispatch = this.props.dispatch;
    const getState = this.props.getState;
    dispatch(fetchPostsRequest()).then(() => {
      // Always get data from store!
      const postPromises = getState().posts.posts.map(post => {
        return dispatch(fetchCommentsRequest(post.id));
      });
      return Promise.all(postPromises);
    }).then() => {
      // ...
    });
  }

  render() {
    // ...
  }
}

export default connect(
  state => ()
)(PostContainer);

通知リクエスト アクションは ttl 内にキャッシュされ、リクエストの重複を防ぐことが約束されています。

ところで、async/await を使用している場合は、次のように redux-dataloader を使用してデータ取得を処理できます。

async function fetchData(props, store) {
  try {
    const { dispatch, getState } = store;
    await dispatch(fetchUserRequest(props.userId));
    const userId = getState().users.user.id;
    await dispatch(fetchPostsRequest(userId));
    const posts = getState().posts.userPosts[userId];
    const commentRequests = posts.map(post => fetchCommentsRequest(post.id))
    await Promise.all(commentRequests);
  } catch (err) {
    // error handler
  }
}
于 2016-05-09T18:28:53.700 に答える