2016-09-29 1 views
17

Цель: при загрузке маршрута реактивного маршрутизатора отправьте действие Redux с запросом asynchronic Saga для получения данных для базового компонента без гражданства этого маршрута.Как отправить действие Redux из компонента без гражданства при загрузке маршрута?

Проблема: компоненты без гражданства являются простыми функциями и не имеют методов жизненного цикла, таких как componentDidMount, поэтому я не могу (?) Отправлять Redux-действие изнутри функции.

Вопрос частично связан с Converting stateful React component to stateless functional component: How to implement "componentDidMount" kind of functionality?, но моя цель - просто отправить одно действие Redux, запрашивающее данные, которые будут заноситься в хранилище асинхронно (я использую Saga, но я думаю, что это не имеет отношения к проблеме, поскольку моя цель - просто отправить обычное действие Redux), после чего компонент без гражданства будет повторно отображать из-за измененной информации.

Я думаю о двух подходах: используйте либо функцию react-router, либо метод connect Redux. Существует ли так называемый «Реакт-путь» для достижения моей цели?

EDIT: единственное решение, которое я придумал до сих пор, является диспетчерская действие внутри 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

Я не знаю, почему вы хотите Absolutly без гражданства компонент, в то время как компонент с сохранением состояния componentDidMount будет делать работу простым способом.

Действия по отправке в mapDispatchToProps очень опасны и могут привести к диспетчеризации не только на монтировку, но и при изменении собственных настроек или сохранения реквизита. Побочных эффектов не ожидается в этом методе, который должен оставаться чистым.

Один простой способ сохранить компонент без гражданства, чтобы обернуть его в HOC (Higher-Order Component), что вы можете легко создать:

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

Обратите внимание, что если вы используете Redux подключения после этого 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, вы можете вводить отправку в качестве реквизита и использовать его вместо предупреждающего сообщения.

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) 
Смежные вопросы