目標:反応ルータールートをロードするとき、非同期を要求する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);
ただし、これは何らかの方法で汚れているようで、正しい方法ではありません。
なぜステートレスコンポーネントが絶対に必要なのかはわかりませんが、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
を使用する場合は、小道具としてディスパッチを注入し、アラートメッセージの代わりにそれを使用できます。
ステートフルコンポーネントを使用せずに、最もクリーンなソリューションを見つけたと思います。
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 から解決策を見つけました
完全にステートレスにしたい場合は、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)
一般に、コンポーネントが初めてマウント/レンダリングされるときにディスパッチされる何らかのトリガーアクションがなければ、これは不可能だと思います。これを実現するには、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)