如何在React Router V4中收听路由更改?


Answers:


170

withRouter用来拿location道具。当组件由于新路线而更新时,我检查值是否更改:

@withRouter
class App extends React.Component {

  static propTypes = {
    location: React.PropTypes.object.isRequired
  }

  // ...

  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      this.onRouteChanged();
    }
  }

  onRouteChanged() {
    console.log("ROUTE CHANGED");
  }

  // ...
  render(){
    return <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/checkout" component={CheckoutPage} />
        <Route path="/success" component={SuccessPage} />
        // ...
        <Route component={NotFound} />
      </Switch>
  }
}

希望能帮助到你


21
在React Router v4中使用'this.props.location.pathname'。
ptorsson

4
@ledfusion我正在做相同的事情并使用withRouter,但是出现错误You should not use <Route> or withRouter() outside a <Router>。我没有<Router/>在上面的代码中看到任何组件。那么它是如何工作的呢?
特立独行'18

1
嗨@maverick。我不确定您的代码是什么样子,但是在上面的示例中,该<Switch>组件充当了事实上的路由器。只有第一个<Route>具有匹配路径的条目才会被渲染。<Router/>在这种情况下,不需要任何组件
brickpop '18

1
要使用@withRouter,您需要安装npm install --save-dev transform-decorators-legacy
Sigex

68

为了扩展上述内容,您将需要获取历史对象。如果使用BrowserRouter,则可以使用高阶组件(HoC)导入withRouter和包装组件,以便通过prop访问历史对象的属性和功能。

import { withRouter } from 'react-router-dom';

const myComponent = ({ history }) => {

    history.listen((location, action) => {
        // location is an object like window.location
        console.log(action, location.pathname, location.state)
    });

    return <div>...</div>;
};

export default withRouter(myComponent);

唯一需要注意的是,使用Router和其他大多数访问方法history似乎会在道具分解对象时污染道具。


不管有什么问题,答案都帮助我理解了一些东西:)。但修复withRouteswithRouter
谢尔盖·罗伊茨基

是的,很抱歉,感谢您指出这一点。我已经修改了帖子。我将正确的导入放在问题的顶部,然后在代码示例中将其拼写错误。
山姆·帕门特

5
我认为withRouter当前版本可以传递,history而不是变量listen
mikebridge

5
修改该帖子以显示不听是很好;此代码中有内存泄漏。
AndrewSouthpaw

34

您应该使用历史记录v4 lib。

那里的例子

history.listen((location, action) => {
  console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
  console.log(`The last navigation action was ${action}`)
})

1
history.pushState()和history.replaceState()调用不会触发popstate事件,因此仅此一项将无法涵盖所有​​路线更改。
瑞安

1
@Ryan似乎history.push确实触发了history.listen。请参阅历史记录v4文档中的“ 使用基本URL”示例。因为那实际上是浏览器本机对象的包装,所以它的行为与本机对象并不完全相同。historyhistory
Rockallite

这似乎是一个更好的解决方案,因为通常您需要侦听路由更改以进行事件推送,这与响应组件生命周期事件无关。
丹尼尔·杜波夫斯基

12
潜在的内存泄漏!您一定要这样做!“当您使用history.listen附加侦听器时,它将返回一个可用于删除该侦听器的函数,然后可以在清除逻辑中调用该函数:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos

转到此处以获取有关历史记录包的文档。github.com/ReactTraining/history/blob/master/docs/...
贾森·金

25

withRouterhistory.listenuseEffect(React Hooks)配合得很好:

import React, { useEffect } from 'react'
import { withRouter } from 'react-router-dom'

const Component = ({ history }) => {
    useEffect(() => history.listen(() => {
        // do something on route change
        // for my example, close a drawer
    }), [])

    //...
}

export default withRouter(Component)

每当更改路线时,都会触发监听器回调,并且的返回值history.listen是与配合良好的关机处理程序useEffect


13

v5.1引入了有用的钩子 useLocation

https://reacttraining.com/blog/react-router-v5-1/#uselocation

import { Switch, useLocation } from 'react-router-dom'

function usePageViews() {
  let location = useLocation()

  useEffect(
    () => {
      ga.send(['pageview', location.pathname])
    },
    [location]
  )
}

function App() {
  usePageViews()
  return <Switch>{/* your routes here */}</Switch>
}

4
请注意,我在遇到错误时遇到了麻烦:Cannot read property 'location' of undefined at useLocation。您需要确保useLocation()调用不在将路由器放入树的同一组件中:请参见此处
toddg

11

带挂钩:

import { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { history as historyShape } from 'react-router-prop-types'

const DebugHistory = ({ history }) => {
  useEffect(() => {
    console.log('> Router', history.action, history.location])
  }, [history.location.key])

  return null
}

DebugHistory.propTypes = { history: historyShape }

export default withRouter(DebugHistory)

导入并渲染为<DebugHistory>组件


11
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';

function MyApp() {

  const location = useLocation();

  useEffect(() => {
      console.log('route has been changed');
      ...your code
  },[location.pathname]);

}

带钩


冬青Jesys!它是如何工作的?你的答案很酷!但是我将调试器点放在useEffect中,但是无论何时我更改路径名,位置都保持未定义状态?你可以分享任何好文章吗?因为很难找到任何清晰的信息
AlexNikonov

7
import { useHistory } from 'react-router-dom';

const Scroll = () => {
  const history = useHistory();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [history.location.pathname]);

  return null;
}

它也可以观察哈希变化吗?路线/ a#1->路线/ a#2
Naren

1

随着反应挂钩,我正在使用 useEffect

  const history = useHistory()
  const queryString = require('query-string')
  const parsed = queryString.parse(location.search)
  const [search, setSearch] = useState(parsed.search ? parsed.search : '')

  useEffect(() => {
    const parsedSearch = parsed.search ? parsed.search : ''
    if (parsedSearch !== search) {
      // do some action! The route Changed!
    }
  }, [location.search])

0

在某些情况下,您可以通过以下方式使用renderattribute而不是component

class App extends React.Component {

    constructor (props) {
        super(props);
    }

    onRouteChange (pageId) {
        console.log(pageId);
    }

    render () {
        return  <Switch>
                    <Route path="/" exact render={(props) => { 
                        this.onRouteChange('home');
                        return <HomePage {...props} />;
                    }} />
                    <Route path="/checkout" exact render={(props) => { 
                        this.onRouteChange('checkout');
                        return <CheckoutPage {...props} />;
                    }} />
                </Switch>
    }
}

请注意,如果您在onRouteChange方法中更改状态,则可能导致“超出最大更新深度”错误。


0

使用该useEffect挂钩,可以在不添加侦听器的情况下检测路由更改。

import React, { useEffect }           from 'react';
import { Switch, Route, withRouter }  from 'react-router-dom';
import Main                           from './Main';
import Blog                           from './Blog';


const App  = ({history}) => {

    useEffect( () => {

        // When route changes, history.location.pathname changes as well
        // And the code will execute after this line

    }, [history.location.pathname]);

    return (<Switch>
              <Route exact path = '/'     component = {Main}/>
              <Route exact path = '/blog' component = {Blog}/>
            </Switch>);

}

export default withRouter(App);

0

我只是处理了这个问题,所以我将在其他给出的答案的基础上增加解决方案。

这里的问题是,useEffect它实际上并没有按您希望的那样工作,因为调用仅在第一次渲染后才触发,因此会出现不必要的延迟。
如果您使用诸如redux之类的状态管理器,由于商店中的状态持续存在,您很可能会在屏幕上闪烁。

您真正想要的是使用它,useLayoutEffect因为它会立即触发。

因此,我编写了一个小的实用程序函数,并将其放在与路由器相同的目录中:

export const callApis = (fn, path) => {
    useLayoutEffect(() => {
      fn();
    }, [path]);
};

我从组件HOC内这样调用:

callApis(() => getTopicById({topicId}), path);

pathmatch使用时会在对象中传递的道具withRouter

我不太赞成手动收听/取消收听历史记录。那只是imo。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.