web-dev-qa-db-ja.com

ルートがロードされたときにステートレスコンポーネントからReduxアクションをディスパッチする方法

目標:反応ルータールートをロードするとき、非同期を要求するReduxアクションをディスパッチします Saga 基礎となるステートレスのデータを取得するワーカーそのルートのコンポーネント。

問題:ステートレスコンポーネントは単なる関数であり、componentDidMountなどのライフサイクルメソッドがないため、内部からReduxアクションをディスパッチできません(?)関数。

私の質問は ステートフルReactコンポーネントをステートレス機能コンポーネントに変換する:「componentDidMount」機能の実装方法? に関連していますが、私の目標は単にディスパッチすることです非同期的にストアにデータを取り込むことを要求する単一のReduxアクション(私は佐賀を使用しますが、目標は通常のReduxアクションを単にディスパッチすることなので、これは問題とは無関係だと思います)変更されたデータの小道具。

私は2つのアプローチを考えています: 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);

ただし、これは何らかの方法で汚れているようで、正しい方法ではありません。

28
Kitanotori

なぜステートレスコンポーネントが絶対に必要なのかはわかりませんが、componentDidMountを備えたステートフルコンポーネントは簡単な方法でジョブを実行します。

mapDispatchToPropsでのアクションのディスパッチは非常に危険であり、マウント時だけでなく、ownPropsまたはstore propsが変更されるたびにディスパッチする可能性があります。このメソッドでは、純粋なままであるはずの副作用は想定されていません。

コンポーネントをステートレスに保つ簡単な方法の1つは、簡単に作成できる HOC(高次コンポーネント) にラップすることです。

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

このHOCの後にRedux接続を使用すると、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を使用する場合は、小道具としてディスパッチを注入し、アラートメッセージの代わりにそれを使用できます。

JsFiddle

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 から解決策を見つけました

4
Kitanotori

完全にステートレスにしたい場合は、onEnterイベントを使用してルートに入るときにイベントをディスパッチできます。

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

このファイルにディスパッチをインポートするか、何らかの方法でパラメーターとして渡すことを条件に、ここで関数を記述できます。

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

ただし、このソリューションはさらに汚いと感じています。

私が本当に効率的にできるもう1つのソリューションは、コンテナーを使用し、その中で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)
1
Harkirat Saluja

一般に、コンポーネントが初めてマウント/レンダリングされるときにディスパッチされる何らかのトリガーアクションがなければ、これは不可能だと思います。これを実現するには、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
WuTheFWasThat