2016-09-29 71 views
17

目标:当加载反应路由器路由时,派遣一个Redux动作请求异步工作者为该路由的基础无状态组件获取数据。加载路由时如何调度从无状态组件Redux的行动?

问题:无状态部件仅仅是功能,没有生命周期的方法,如componentDidMount,所以我不能从函数内部调度终极版动作(?)。

我的问题部分与Converting stateful React component to stateless functional component: How to implement "componentDidMount" kind of functionality?有关,但我的目标是仅派发一个请求将数据异步填充到商店的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); 

然而,这看起来有点脏,不正确的方法。

回答

1

如果你希望它是完全无状态使用的OnEnter事件进入的路线时,你可以分派事件。

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

现在你可以写你在这里发挥作用提供你在这个文件中任一进口调度或以某种方式把它作为参数。

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

但我觉得这个解决方案更加肮脏。

,我可能是非常有效的另一种解决方案是使用的容器,并呼吁在该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

我开始有一种感觉,路由器方式不是正确的方式,因为它将数据处理和路由混合在一起,感觉就像一个反模式。 第二种方法是一个有趣的方法,虽然它似乎带来了不必要的复杂性(如果无论如何有状态组件是必要的,为什么不只是使无状态组件有状态而不是引入额外的容器?)。 – Kitanotori

+0

我已经用粗体提到过那是更加肮脏的做法:)。他明确提到他想保持无国籍状态,所以我给出了一个解决方案。 –

11

我不知道为什么你是绝对需要一个无状态的组件,同时与componentDidMount状态组件会以简单的方式做的工作。

mapDispatchToProps调度行为是非常危险的,可能会导致只在安装,但每当ownProps或商店道具的变化并不调度。预计副作用不应该在这种应该保持纯净的方法中完成。

一个简单的方法,让您的组件无状态是把它包装成一个HOC (Higher-Order Component),你可以很容易地创建:

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

请注意,如果您使用的终极版这个HOC连接后,你可以从道具轻松接入调度直接就好像你不使用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) 

如果您使用connect围绕Hello组件,他们可以注入dispatch作为道具并使用它来代替警告消息。

JsFiddle

+0

什么是HOC,以及LifecycleDispatch从哪里来? – Kitanotori

+0

@Kitanotori高阶组件。 https://github.com/JamieDixon/react-lifecycle-component – Waclock

+0

请记住,你不能在箭头函数中使用'this'。在这里使用普通函数'componentDidMount:'而不是箭头之一。对于像我这样的js noob,花了这么长时间才意识到为什么它不起作用。我将编辑答案来修复它。 – prostynick

3

我想我找到最清洁的溶液,而不必使用有状态的组件:

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 lifecycycle方法的高阶函数。 发现解决方案从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) 
相关问题