是否可以thunk
在特定路由上调用称为 a 的异步 redux 操作,并且在响应成功或失败之前不执行转换?
用例
我们需要从服务器加载数据并用初始值填充表单。在从服务器获取数据之前,这些初始值不存在。
像这样的一些语法会很棒:
<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
是否可以thunk
在特定路由上调用称为 a 的异步 redux 操作,并且在响应成功或失败之前不执行转换?
用例
我们需要从服务器加载数据并用初始值填充表单。在从服务器获取数据之前,这些初始值不存在。
像这样的一些语法会很棒:
<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
要回答在响应成功或失败之前阻止转换到新路由的原始问题:
因为您使用的是 redux thunk,所以动作创建者的成功或失败可能会触发重定向。我不知道你的具体动作/动作创建者是什么样的,但这样的事情可以工作:
import { browserHistory } from 'react-router'
export function loadInitialFormValues(formId) {
return function(dispatch) {
// hit the API with some function and return a promise:
loadInitialValuesReturnPromise(formId)
.then(response => {
// If request is good update state with fetched data
dispatch({ type: UPDATE_FORM_STATE, payload: response });
// - redirect to the your form
browserHistory.push('/myForm');
})
.catch(() => {
// If request is bad...
// do whatever you want here, or redirect
browserHistory.push('/myForm')
});
}
}
跟进。在进入路由/在组件的 componentWillMount 上加载数据并显示微调器的常见模式:
来自关于异步操作的 redux 文档http://redux.js.org/docs/advanced/AsyncActions.html
- 通知减速器请求开始的动作。
减速器可以通过切换状态中的 isFetching 标志来处理此操作。这样 UI 就知道是时候显示微调器了。
- 通知减速器请求成功完成的动作。
reducer 可以通过将新数据合并到它们管理的状态并重置 isFetching 来处理此操作。UI 将隐藏微调器,并显示获取的数据。
- 通知减速器请求失败的操作。
减速器可以通过重置 isFetching 来处理这个动作。此外,一些 reducer 可能想要存储错误消息,以便 UI 可以显示它。
我按照下面的一般模式使用您的情况作为粗略的指导方针。你不必使用Promise
// action creator:
export function fetchFormData(formId) {
return dispatch => {
// an action to signal the beginning of your request
// this is what eventually triggers the displaying of the spinner
dispatch({ type: FETCH_FORM_DATA_REQUEST })
// (axios is just a promise based HTTP library)
axios.get(`/formdata/${formId}`)
.then(formData => {
// on successful fetch, update your state with the new form data
// you can also turn these into their own action creators and dispatch the invoked function instead
dispatch({ type: actions.FETCH_FORM_DATA_SUCCESS, payload: formData })
})
.catch(error => {
// on error, do whatever is best for your use case
dispatch({ type: actions.FETCH_FORM_DATA_ERROR, payload: error })
})
}
}
// reducer
const INITIAL_STATE = {
formData: {},
error: {},
fetching: false
}
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case FETCH_FORM_DATA_REQUEST:
// when dispatch the 'request' action, toggle fetching to true
return Object.assign({}, state, { fetching: true })
case FETCH_FORM_DATA_SUCCESS:
return Object.assign({}, state, {
fetching: false,
formData: action.payload
})
case FETCH_FORM_DATA_ERROR:
return Object.assign({}, state, {
fetching: false,
error: action.payload
})
}
}
// route can look something like this to access the formId in the URL if you want
// I use this URL param in the component below but you can access this ID anyway you want:
<Route path="/myForm/:formId" component={SomeForm} />
// form component
class SomeForm extends Component {
componentWillMount() {
// get formId from route params
const formId = this.props.params.formId
this.props.fetchFormData(formId)
}
// in render just check if the fetching process is happening to know when to display the spinner
// this could also be abstracted out into another method and run like so: {this.showFormOrSpinner.call(this)}
render() {
return (
<div className="some-form">
{this.props.fetching ?
<img src="./assets/spinner.gif" alt="loading spinner" /> :
<FormComponent formData={this.props.formData} />
}
</div>
)
}
}
function mapStateToProps(state) {
return {
fetching: state.form.fetching,
formData: state.form.formData,
error: state.form.error
}
}
export default connect(mapStateToProps, { fetchFormData })(SomeForm)
首先,我想说的是,关于使用 react-router 的onEnter
钩子获取数据是否是一种好的做法存在争论,不过这就是这样的事情:
您可以将 redux-store 传递给您的Router
. 让以下成为您的 Root 组件,Router
安装位置:
...
import routes from 'routes-location';
class Root extends React.Component {
render() {
const { store, history } = this.props;
return (
<Provider store={store}>
<Router history={history}>
{ routes(store) }
</Router>
</Provider>
);
}
}
...
您的路线将类似于:
import ...
...
const fetchData = (store) => {
return (nextState, transition, callback) => {
const { dispatch, getState } = store;
const { loaded } = getState().myCoolReduxStore;
// loaded is a key from my store that I put true when data has loaded
if (!loaded) {
// no data, dispatch action to get it
dispatch(getDataAction())
.then((data) => {
callback();
})
.catch((error) => {
// maybe it failed because of 403 forbitten, we can use tranition to redirect.
// what's in state will come as props to the component `/forbitten` will mount.
transition({
pathname: '/forbitten',
state: { error: error }
});
callback();
});
} else {
// we already have the data loaded, let router continue its transition to the route
callback();
}
}
};
export default (store) => {
return (
<Route path="/" component={App}>
<Route path="myPage" name="My Page" component={MyPage} onEnter={fetchData(store)} />
<Route path="forbitten" name="403" component={PageForbitten} />
<Route path="*" name="404" component={PageNotFound} />
</Route>
);
};
请注意,您的路由器文件正在以您的商店作为参数导出一个 thunk,如果您向上看,看看我们如何调用路由器,我们将商店对象传递给它。
可悲的是,在撰写react-router 文档时,我会返回 404,因此我无法将您指向(nextState, transition, callback)
所描述的文档。但是,关于那些,从我的记忆中:
nextState
描述路由react-router
将过渡到;
transition
执行功能可能是另一个过渡而不是 from nextState
;
callback
将触发您的路线转换完成。
另一个需要指出的想法是,使用 redux-thunk,您的调度操作可以返回一个Promise,请在此处的文档中查看。你可以在这里找到一个关于如何使用 redux-thunk 配置你的 redux store 的好例子。
为此,我制作了一个方便的钩子,适用于 react-router v5:
/*
* Return truthy if you wish to block. Empty return or false will not block
*/
export const useBlock = func => {
const { block, push, location } = useHistory()
const lastLocation = useRef()
const funcRef = useRef()
funcRef.current = func
useEffect(() => {
if (location === lastLocation.current || !funcRef.current)
return
lastLocation.current = location
const unblock = block((location, action) => {
const doBlock = async () => {
if (!(await funcRef.current(location, action))) {
unblock()
push(location)
}
}
doBlock()
return false
})
}, [location, block, push])
}
在你的组件中,像这样使用它:
const MyComponent = () => {
useBlock(async location => await fetchShouldBlock(location))
return <span>Hello</span>
}
在 async 函数返回之前不会发生导航;您可以通过返回来完全阻止导航true
。