React-Router:如何在路由转换之前等待异步操作

And*_*gan 9 state reactjs react-router redux react-router-redux

是否可以thunk在特定路由上调用异步redux操作,并且在响应成功或失败之前不执行转换?

用例

我们需要从服务器加载数据并使用初始值填充表单.在从服务器获取数据之前,这些初始值不存在.

像这样的一些语法会很棒:

<Route path="/myForm" component={App} async={dispatch(loadInitialFormValues(formId))}>
Run Code Online (Sandbox Code Playgroud)

jma*_*rje 11

回答在响应成功或失败之前阻止转换到新路由的原始问题:

因为你正在使用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')
      });
  }
}
Run Code Online (Sandbox Code Playgroud)

跟进.在输入路径/组件的componentWillMount并显示微调器时加载数据的常见模式:

来自异步操作的redux文档http://redux.js.org/docs/advanced/AsyncActions.html

  • 通知减压器请求开始的动作.

reducers可以通过在状态中切换isFetching标志来处理此操作.这样UI就知道是时候展示一个微调器了.

  • 通知减压器请求成功完成的操作.

reducers可以通过将新数据合并到他们管理的状态并重置isFetching来处理此操作.UI将隐藏微调器,并显示获取的数据.

  • 通知减压器请求失败的操作.

reducers可以通过重置isFetching来处理此操作.此外,某些Reducer可能希望存储错误消息,以便UI可以显示它.

我使用您的情况作为粗略的指导方针,遵循下面的这种一般模式.您不必使用承诺

// 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)
Run Code Online (Sandbox Code Playgroud)

  • 你也可以有航线上的OnEnter事件处理程序,或在componentWillMount取回获取数据,并有一个微调,而表单数据,而不是获取冻结导航.这对终极版咚一个非常普遍的使用 (2认同)
  • React Router v4 的任何解决方案? (2认同)

Mor*_*hai 6

我为此目的制作了一个方便的钩子,与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])
}
Run Code Online (Sandbox Code Playgroud)

在你的组件中,像这样使用它:

const MyComponent = () => {
    useBlock(async location => await fetchShouldBlock(location))

    return <span>Hello</span>
}
Run Code Online (Sandbox Code Playgroud)

在异步函数返回之前不会发生导航;您可以通过返回来完全阻止导航true