首页 文章

如何在加载路由时从无状态组件调度Redux操作?

提问于
浏览
23

Goal :加载react-router路由时,调度Redux操作,请求asynchronic Saga worker获取该路由的基础无状态组件的数据 .

Problem :无状态组件仅仅是函数,并且从函数内部调度Redux动作 .

我的问题部分与Converting stateful React component to stateless functional component: How to implement "componentDidMount" kind of functionality?有关,但我的目标是仅发送一个Redux动作,要求将数据异步填充到商店(我使用Saga,但我认为这与问题无关,因为我的目标是仅发送一个普通的Redux动作),之后由于改变的数据道具,无状态组件将重新渲染 .

我正在考虑两种方法:要么使用react-router的某些功能,要么使用Redux的connect方法 . 是否有一个所谓的"React-way"来实现我的目标?

编辑:到目前为止我唯一提出的解决方案是在mapDispatchToProps中调度动作,这样:

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);

然而,这似乎有点脏,而不是正确的方式 .

4 回答

  • 11

    我想我找到了最干净的解决方案,而不必使用有状态组件:

    const onEnterAction = (store, dispatchAction) => {
        return (nextState, replace) => {
            store.dispatch(dispatchAction());
        };
    };
    
    const myDataFetchAction = () => ({ type: DATA_GET_REQUEST });
    
    export const Routes = (store) => (
        <Route path='/' component={MyStatelessComponent} onEnter={onEnterAction(store, myDataFetchAction)}/>
    );
    

    该解决方案将商店传递给更高阶的函数,该函数传递给onEnter生命周期方法 . 从https://github.com/reactjs/react-router-redux/issues/319找到解决方案

  • 1

    一般情况下,如果没有某种触发操作,我认为这是不可能的,当第一次安装/渲染组件时会调度该操作 . 你已经通过使mapDispatchToProps不纯而实现了这一点 . 我100%同意塞巴斯蒂安这是一个坏主意 . 您还可以将杂质移动到渲染功能,这更糟糕 . 组件生命周期方法就是为了这个!如果您不想写出组件类,那么他的HOC解决方案是有意义的 .

    我没有太多要补充,但是如果你只是想看到实际的传奇代码,这里有一些伪代码,给出了这样的触发动作(未经测试):

    // takes the request, *just a single time*, fetch data, and sets it in state
    function* loadDataSaga() {
        yield take(myActionTypes.DATA_GET_REQUEST)
        const data = yield call(fetchData)
        yield put({type: myActionTypes.SET_DATA, data})
    }
    
    function* mainSaga() {
        yield fork(loadDataSaga);
        ... do all your other stuff
    }
    
    function myReducer(state, action) {
        if (action.type === myActionTypes.SET_DATA) {
             const newState = _.cloneDeep(state)
             newState.whatever.data = action.data
             newState.whatever.loading = false
             return newState
        } else if ( ... ) {
             ... blah blah
        }
        return state
    }
    
    const MyStatelessComponent = (props) => {
      if (props.loading) {
        return <Spinner/>
      }
      return <some stuff here {...props.data} />
    }
    
    const mapStateToProps = (state) => state.whatever;
    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 {};
    };
    

    加上样板:

    const sagaMiddleware = createSagaMiddleware();
    
    export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent);
    
    const store = createStore(
      myReducer,
      { whatever: {loading: true, data: null} },
      applyMiddleware(sagaMiddleware)
    );
    sagaMiddleware.run(mainSaga)
    
  • 1

    我不知道为什么你绝对想要一个无状态组件,而有一个有组件的动态组件可以用一种简单的方式完成工作 .

    mapDispatchToProps 中的调度操作非常危险,并且可能导致不仅在mount时调度,而且每当ownProps或store props更改时都会调度 . 预计这种方法不会产生副作用,应保持纯净 .

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

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

    请注意,如果您在此HOC之后使用Redux连接,则可以直接从props直接访问调度,就像您不使用 mapDispatchToProps 一样,注入调度 .

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

    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();
        }
      })
    }
    

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

    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注入为道具并使用它代替警报消息 .

    JsFiddle

  • 4

    如果您希望它完全无状态,您可以在使用onEnter事件输入路径时调度事件 .

    <Route to='/app' Component={App} onEnter={dispatchAction} />
    

    现在您可以在此处编写函数,前提是您在此文件中导入调度或以某种方式将其作为参数传递 .

    function dispatchAction(nexState,replace){
       //dispatch 
    }
    

    But this solution I feel is even more dirty.

    另一个我真正高效的解决方案是使用容器并调用componentDidMount .

    import React,{Component,PropTypes} from 'react'
    import {connect} from 'react-redux'
    
    const propTypes = {
     //
    }
    
    function mapStateToProps(state){
    //
    }
    
    class ComponentContainer extends Component {
    
      componentDidMount(){
        //dispatch action
      }
      render(){
        return(
          <Component {...this.props}/> //your dumb/stateless component . Pass data as props
        )
      }
    } 
    
    export default connect(mapStateToProps)(ComponentContainer)
    

相关问题