需要使用Redux和Router进行单独输入

时间:2016-08-03 20:59:54

标签: reactjs redux react-router-redux

我正在使用ReactJS,Redux(带有服务器端呈现)和react-router-redux as set up here,并且由于路由如何与其余的redux状态和操作一起工作而受到一点抛出。

例如,我有一个包含路径/members的成员组件:

class Members extends Component {

  static need = [
    fetchMembers
  ]

  render() {
    ...

static need数组指定一个操作,该操作在状态上填充数组,然后映射到组件props。那很有用。

但后来我有了一个路由为members/:memberId的个人成员组件。如何以客户端和服务器端的方式加载该单个成员。

我现在所做的是相同的:

class Member extends Component {

  static need = [
    fetchMembers
  ]

  render() {
    ...

然后只映射单个成员

function mapStateToProps(state, ownProps) {
  return {
    member: state.member.members.find(member => member.id == ownProps.params.memberId),
  };
}

这有效,但显然是错误的。所以问题是双重的:

  1. 当用户单击具有查询参数(:memberId)的路由器Link时,如何使用该路由器参数查询特定文档(假设为mongo数据库)。我是否会以某种方式触发一个单独的操作来填充redux状态的active成员字段?在路径组件componentDidMount中,这发生了什么?

  2. 这如何与服务器端呈现一起使用?

2 个答案:

答案 0 :(得分:1)

我有同样的问题,似乎找到了一种适合我的设置的方法。我使用Node,Express,React,React Router,Redux和Redux Thunk

1)这实际上取决于数据的位置。如果/member/:memberId所需的数据已处于状态(例如,来自之前的调用),理论上您可以过滤componentDidMount被触发时已有的数据。

然而,我宁愿将事情分开,以避免头痛。在整个应用程序中开始使用一个数据源用于多个目的地/目的可能会让您在漫长的一天(例如,组件A 需要更多/更少的关于该成员的属性而不是组件B 组件A 需要的格式与组件B 等格式不同。)

这个决定当然应该基于你的用例,但由于现在API调用的成本,当有人导航到/member/:memberId时,我不会害怕(根本)做出一个。

2)我将使用我的典型设置的简化版本来回答:

每当请求通过时,我都会让这个家伙处理它。

// Imports and other jazz up here

app.use((req, res) => {

  const store = configureStore({});
  const routes = createRoutes(store);

  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {

    if (error) {

      res.status(500).send(error.message);

    } else if (redirectLocation) {

      res.redirect(302, redirectLocation.pathname + redirectLocation.search);

    } else if (renderProps) {

      const fetchedData = renderProps.components
        .filter(component => component.fetchData)
        .map(component => component.fetchData(store, renderProps.params));

      Promise.all(fetchedData).then(() => {

        const body = renderToString(
          <Provider store={store}>
            <RouterContext {...renderProps} />
          </Provider>
        );

        res.status(200).send(`<!doctype html>${renderToStaticMarkup(
          <Html
            body={body}
            state={store.getState()}
          />)
        }`);

      });

    } else {

      res.status(404).send('Not found');

    }

  });

});

它将在即将呈现的组件上查找fetchData,并确保在向客户端发送任何内容之前获取数据。

在每条路线上,我都有ContainerContainer的唯一目的是收集该路线所需的数据。正如您所提到的,这可能发生在服务器端(在我的情况下为fetchData)或客户端(在我的情况下为componentDidMount)。我的典型Container看起来像这样:

// Imports up here

class Container extends Component {

  static fetchData(store, params) {
    const categories = store.dispatch(getCategories());
    return Promise.all([categories]);
  }

  componentDidMount() {
    this.props.dispatch(getCategoriesIfNeeded());
  }

  render() {
    return this.props.categories.length ? (
      // Render categories
    ) : null;
  }

}

Container.propTypes = {
  categories: PropTypes.array.isRequired,
  dispatch: PropTypes.func.isRequired,
  params: PropTypes.object.isRequired,
};

function mapStateToProps(state) {
  return {
    categories: state.categories,
  };
}

export default connect(mapStateToProps)(Container);

在上面的Container我使用getCategoriesgetCategoriesIfNeeded来确保我拥有该路线所需的数据。 getCategories仅称为服务器端,getCategoriesIfNeeded仅称为客户端。

请注意,我paramsfetchData(从componentDidMount传递)connect()可用,我可能会用它来提取:memberId之类的内容

以下列出了用于获取上述数据的两个函数:

// Using this for structure of reducers etc.:
// https://github.com/erikras/ducks-modular-redux
//
// actionTypes object and reducer up here

export function getCategories() {

  return (dispatch, getState) => {

    dispatch({
      type: actionTypes.GET_REQUEST,
    });

    return fetch('/api/categories').then(res => {

      return !res.error ? dispatch({
        error: null,
        payload: res.body,
        type: actionTypes.GET_COMPLETE,
      }) : dispatch({
        error: res.error,
        payload: null,
        type: actionTypes.GET_ERROR,
      });

    });

  };

}

export function getCategoriesIfNeeded() {

  return (dispatch, getState) => {

    return getState().categories.length ? dispatch(getCategories()) : Promise.resolve();

  };

}

如上所示,我dispatchgetState都可以使用Redux Thunk - 这也可以处理我的承诺 - 这让我可以自由使用我已有的数据,请求新数据和做我的减速机的多次更新。

我希望这足以让你感动。如果不是,请不要犹豫,要求进一步解释:)

答案 1 :(得分:0)

事实证明,答案非常简单。从Isomorphic Redux App实现的实现通过将路由查询参数传递给动作创建者,将组件上的need静态属性绑定回路由器。

所以对于路线:

items/:id

您使用

之类的组件
class Item extends Component {

  static need = [
    fetchItem
  ]

  render() {

指定它需​​要fetchItem操作。该操作通过了路径的查询参数,您可以像

一样使用它
export function fetchItem({id}) {
  let req = ... 
  return {
    type: types.GET_ITEM,
    promise: req
  };
}

有关此工作原因的更详细解释,请阅读marcfalk的答案,其中描述了一种非常类似的方法。