React Hooks with React Router v4 - 我如何重定向到另一个路由?

Posted

技术标签:

【中文标题】React Hooks with React Router v4 - 我如何重定向到另一个路由?【英文标题】:React Hooks with React Router v4 - how do I redirect to another route? 【发布时间】:2019-07-01 22:43:51 【问题描述】:

我有一个简单的反应钩子应用程序 - 待办事项列表 - 带有反应路由器 v4

在待办事项列表中,当点击待办事项时,我需要:

    在上下文中调度当前的待办事项 重定向到另一条路线(从 /todos 到 /todos/:id)

在之前的基于 React 类的实现中,我可以使用 this.context.history.push 重定向到另一个路由。

我将如何使用 React Hooks 结合 React Router v4 来处理这个问题(在下面的代码中,请参阅我在函数 editRow() 中的注释)?

代码如下:

=====index.js=====

import React from 'react';
import ReactDOM from 'react-dom';
import  BrowserRouter from "react-router-dom"

import App from './App';

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>, document.getElementById('root'));

=====main.js=====

import React from 'react'
import  Switch, Route  from 'react-router-dom'
import TodosList from './todoslist'
import TodosEdit from './todosedit'

const Main = () => (
  <main>
    <Switch>
      <Route exact path="/todos" component=TodosList/>
      <Route exact path="/todos/:id" component=TodosEdit />
    </Switch>
  </main>
)

export default Main

=====app.js=====

import React, useContext, useReducer from 'react';
import Main from './main'
import TodosContext from './context'
import todosReducer from './reducer'

const App = () => 
  const initialState = useContext(TodosContext);
  const [state, dispatch] = useReducer(todosReducer, initialState);
  return (
    <div>
      <TodosContext.Provider value=state, dispatch>
        <Main/>
      </TodosContext.Provider>
    </div>
  )

export default App;

=====TodosContext.js=====

import React from 'react'

const TodosContext = React.createContext(
    todos: [
        id:1, text:'Get Grocery', complete:false,
        id:2, text:'Excercise', complete:false,
        id:3, text:'Drink Water', complete:true,
    ],
    currentTodo: 
)

export default TodosContext

=====reducer.js=====

import React from 'react'

export default function reducer(state, action)
    switch(action.type)
        case "GET_TODOS":
            return 
                ...state,
                todos: action.payload
            
        case "SET_CURRENT_TODO":
                   return 
                       ...state,
                       currentTodo: action.payload
            
        default: 
            return state
    

=====Todos.js=====

import React, useState, useContext, useEffect from 'react';
import TodosContext from './context'

function Todos()   
    const [todo, setTodo] = useState("")
    const state, dispatch = useContext(TodosContext)
    useEffect(()=>
        if(state.currentTodo.text)
            setTodo(state.currentTodo.text)
         else 
            setTodo("")
        
        dispatch(
            type: "GET_TODOS",
            payload: state.todos
        )
    , [state.currentTodo.id])

    const editRow = event =>
        let destUrlEdit = `/todos/$event.id`

        let obj = 
        obj.id = event.id
        obj.text = event.text

        dispatch(type:"SET_CURRENT_TODO", payload: obj)

        //after dispatch I would like to redirect to another route to do the actual edit
        //destUrlEdit
    
    return(
        <div>
            <h1>List of ToDos</h1>
            <h4>title</h4>
            <ul>
                state.todos.map(todo => (
                    <li key=todo.id>todo.text &nbsp;
                        <button onClick=()=>
                            editRow(todo)>
                        </button>
                    </li>
                ))
            </ul>
        </div>
    )


export default Todos;

【问题讨论】:

【参考方案1】:

您的问题与使用 react-router-v4 而不是钩子以编程方式导航有关,

在 react-router-v4 中,如果 Todos 组件被渲染为子组件或 Route 或从渲染表单 Route 的祖先并将路由器 props 传递给它,您将从 props 获取历史记录。但是它没有接收到Router props,你可以使用react-router中的withRouter HOC来获取router props并调用props.history.push(destUrlEdit)

import React, useState, useContext, useEffect from 'react';
import TodosContext from './context'
import  withRouter  from 'react-router-dom';

function Todos(props)   
    const [todo, setTodo] = useState("")
    const state, dispatch = useContext(TodosContext)
    useEffect(()=>
        if(state.currentTodo.text)
            setTodo(state.currentTodo.text)
         else 
            setTodo("")
        
        dispatch(
            type: "GET_TODOS",
            payload: state.todos
        )
    , [state.currentTodo.id])

    const editRow = event =>
        let destUrlEdit = `/todos/$event.id`

        let obj = 
        obj.id = event.id
        obj.text = event.text

        dispatch(type:"SET_CURRENT_TODO", payload: obj)

        //after dispatch I would like to redirect to another route to do the actual edit
        //destUrlEdit
        props.history.push(destUrlEdit);
    
    return(
        <div>
            <h1>List of ToDos</h1>
            <h4>title</h4>
            <ul>
                state.todos.map(todo => (
                    <li key=todo.id>todo.text &nbsp;
                        <button onClick=()=>
                            editRow(todo)>
                        </button>
                    </li>
                ))
            </ul>
        </div>
    )


export default withRouter(Todos);

【讨论】:

感谢您的及时回复!!这为我指明了探索在 React 类组件与 React 功能组件中访问 React Router 道具的正确方向。要添加到您的答案中的一件事是,因为 固有地提供了 history, match, location 道具,如果路由已经定义并且位于主级别,则您不需要 HOC withRouter。道具可以按原样访问。但是如果组件嵌套很深,那么 HOC withRouter 就会变得很有帮助。 我写了一篇关于此的文章:dev.to/httpjunkie/… 和一个沙盒示例:6wo1673y2r.codesandbox.io 可能会有所帮助! React 日志给人一种递归状态更新正在发生的感觉,这会导致组件被卸载然后再次安装,但最终历史会在状态保持之前卸载组件,就是这样奇怪。 @EricBishard 我一直在寻找几个小时,就像您在文章中建议的那样简单。我认为你应该在这里发布它作为答案。【参考方案2】:

使用 react-redux 和 connected-react-router...

import useDispatch  from 'react-redux';
import  push  from 'connected-react-router';

export default () => 
 const dispatch = useDispatch();

 return (
   <Button onClick=() => dispatch(push('/login'))>
     Login
   </Button>    
  );
;

【讨论】:

我收到了错误Uncaught Could not find router reducer in state tree, it must be mounted under "router",尽管在我开始使用钩子之前一切都已正确设置。使用组件类的旧方法有效。一旦我切换到这种方法,我就会得到那个错误。【参考方案3】:

它实际上比其他答案简单得多,React Router v5.1 提供了一个useHistory 钩子。

import React from 'react'
import  useHistory  from 'react-router-dom'

const MyComponent = () => 
  const history = useHistory()
  const handleButtonClick = (event) => 
    history.push(event.target.value)
  
  return (
    <button
      type="button"
      value="/my/path"
      onClick=handleButtonClick
    >
      Navigate Me!
    </button>
  )

【讨论】:

我认为实际上是 v5.1 引入了这个功能:reacttraining.com/blog/react-router-v5-1 是的,你是对的!我已经更新了答案中的版本。 history.push('/') 仍然会将用户发送到页面的旧迭代,但是,您仍然需要将其放在 useEffect 中并为 useEffect 提供一个变量检查更改,或通过 props.history.push('/') 向您的历史记录提供道具,其中道具被传递到功能组件中。

以上是关于React Hooks with React Router v4 - 我如何重定向到另一个路由?的主要内容,如果未能解决你的问题,请参考以下文章

[React] Refactor a Class Component with React hooks to a Function

[React Testing] Test your Custom Hook Module with react-hooks-testing-library

如何在 React with Typescript 中使用和指定通用 Hooks?

React Typescript with hooks:最大更新深度超出错误

[React + GraphQL] Use useLazyQuery to manually execute a query with Apollo React Hooks

React 使用 React Hooks 从路由器获取道具