加载路由时如何从无状态组件分派 Redux 操作?

问题描述:

目标:加载 react-router 路由时,分派 Redux 操作请求异步 Saga worker 为该路由的底层无状态组件获取数据.

Goal: when loading a react-router route, dispatch a Redux action requesting asynchronic Saga worker to fetch data for the underlying stateless component of that route.

问题:无状态组件只是函数,没有生命周期方法,例如 componentDidMount,所以我不能(?)从函数内部调度 Redux 操作.

Problem: stateless components are mere functions and don't have lifecycle methods, such as componentDidMount, so I can't(?) dispatch Redux action from inside the function.

我的问题部分与 转换有关有状态的 React 组件到无状态的功能组件:如何实现componentDidMount"什么样的功能? ,但我的目标只是调度单个 Redux 操作,请求将数据异步填充到存储中(我使用 Saga,但我认为这与问题无关,因为我的目标只是调度一个普通的 Redux 操作),之后无状态组件将由于更改的数据属性而重新渲染.

My question is partly related to Converting stateful React component to stateless functional component: How to implement "componentDidMount" kind of functionality? , but my goal is to merely dispatch a single Redux action requesting data to be populated to the store asynchronously (I use Saga, but I think that's irrelevant to the problem, as my goal is to merely dispatch an ordinary Redux action), after which the stateless component would re-render due to the changed data prop.

我正在考虑两种方法:要么使用 react-router,或 Redux 的 connect 方法.是否有所谓的反应方式"来实现我的目标?

I am thinking of two approaches: either use some feature of react-router, or Redux's connect method. Is there a so-called "React-way" to accomplish my goal?

到目前为止,我想出的唯一解决方案是在 mapDispatchToProps 中分派动作,如下所示:

the only solution I have come up with so far, is dispatching the action inside mapDispatchToProps, this way:

const mapStateToProps = (state, ownProps) => ({
    data: state.myReducer.data // data rendered by the stateless component
});

const mapDispatchToProps = (dispatch) => {
    // catched by a Saga watcher, and further delivered to a Saga worker that asynchronically fetches data to the store
    dispatch({ type: myActionTypes.DATA_GET_REQUEST });
    return {};
};

export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent);

然而,这似乎有些肮脏,而不是正确的方法.

However, this seems somehow dirty and not the correct way.

我不知道你为什么绝对想要一个无状态组件,而带有 componentDidMount 的有状态组件会以简单的方式完成这项工作.

I don't know why you absolutly want a stateless component, while a stateful component with componentDidMount would do the job in a simple way.

mapDispatchToProps 中的调度操作是非常危险的,不仅在 mount 上而且在 ownProps 或 store props 发生变化时都可能导致调度.这种方法应该不会有副作用,应该保持纯净.

Dispatching actions in mapDispatchToProps is very dangerous and may lead to dispatching not only on mount but whenever ownProps or store props changes. Side effects are not expected to be done in this method that should remains pure.

保持组件无状态的一种简单方法是将其包装到 您可以轻松创建的 HOC(高阶组件):

One easy way to keep your component stateless is to wrap it into an HOC (Higher-Order Component) that you could easily create:

MyStatelessComponent = withLifecycleDispatch(dispatch => ({
   componentDidMount: function() { dispatch({ type: myActionTypes.DATA_GET_REQUEST })};
}))(MyStatelessComponent)

注意,如果你在这个 HOC 之后使用 Redux connect,你可以很容易地直接从 props 访问 dispatch,就像你不使用 mapDispatchToProps 一样,dispatch 被注入了.

Note that if you use Redux connect after this HOC, you can easily access dispatch from props directly as if you don't use mapDispatchToProps, dispatch is injected.

然后你可以做一些非常简单的事情,比如:

You can then do something very simple like:

let MyStatelessComponent = ...

MyStatelessComponent = withLifecycle({
   componentDidMount: () => this.props.dispatch({ type: myActionTypes.DATA_GET_REQUEST });
})(MyStatelessComponent)

export default connect(state => ({
   date: state.myReducer.data
}))(MyStatelessComponent);

HOC 定义:

import { createClass } from 'react';

const withLifeCycle = (spec) => (BaseComponent) => {
  return createClass({
    ...spec,
    render() {
      return BaseComponent();
    }
  })
}

这是您可以执行的操作的简单实现:


Here is a simple implementation of what you could do:

const onMount = (onMountFn) => (Component) => React.createClass({
   componentDidMount() {
     onMountFn(this.props);
   },
   render() { 
      return <Component {...this.props} />
   }  
});

let Hello = (props) => (
   <div>Hello {props.name}</div>
)

Hello = onMount((mountProps) => {
   alert("mounting, and props are accessible: name=" + mountProps.name)
})(Hello)

如果你在 Hello 组件周围使用 connect,你可以将 dispatch 作为 props 注入并使用它而不是警报消息.

If you use connect around Hello component, they you can inject dispatch as props and use it instead of an alert message.

JsFiddle