196

我有几个按钮用作路线。每次更改路线时,我都想确保处于活动状态的按钮发生更改。

有没有办法在反应路由器 v4 中监听路由变化?

4

13 回答 13

198

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>
  }
}

希望能帮助到你

于 2017-06-07T10:36:35.680 回答
96

要扩展上述内容,您需要获取历史记录对象。如果您正在使用,您可以使用高阶组件 (HoC)BrowserRouter导入withRouter和包装您的组件,以便通过 props 访问历史对象的属性和函数。

    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);

唯一需要注意的是 withRouter 和大多数其他访问方式history似乎会污染道具,因为它们将对象解构到其中。

正如其他人所说,这已被反应路由器暴露的钩子所取代,并且存在内存泄漏。如果您在功能组件中注册侦听器,您应该通过 useEffect 这样做,并在该函数的返回中取消注册它们。

于 2017-02-09T10:29:57.257 回答
82

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>
}
于 2020-01-29T15:40:59.950 回答
37

您应该使用history 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}`)
})
于 2017-02-06T09:49:53.873 回答
37

withRouter, history.listen, 和useEffect(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.

于 2019-08-05T10:15:27.140 回答
20
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]);

}

带挂钩

于 2020-02-11T16:48:04.100 回答
13

带钩子:

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>组件

于 2019-06-23T23:14:46.800 回答
8
import { useHistory } from 'react-router-dom';

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

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

  return null;
}
于 2019-12-05T02:27:57.790 回答
4

使用 React Hooks,我正在使用useEffect

import React from 'react'
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])

在这个例子中,当路线改变时我向上滚动:

import React from 'react'
import { useLocation } from 'react-router-dom'

const ScrollToTop = () => {
  const location = useLocation()

  React.useEffect(() => {
    window.scrollTo(0, 0)
  }, [location.key])

  return null
}

export default ScrollToTop
于 2020-03-31T02:38:45.337 回答
2

在某些情况下,您可能会使用render属性而不是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方法中的状态,这可能会导致“超出最大更新深度”错误。

于 2019-02-14T08:51:18.233 回答
2

对于功能组件,请尝试使用带有 props.location 的 useEffect。

import React, {useEffect} from 'react';

const SampleComponent = (props) => {

      useEffect(() => {
        console.log(props.location);
      }, [props.location]);

}

export default SampleComponent;
于 2020-09-26T19:14:03.073 回答
1

使用该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);

于 2020-01-05T17:10:16.220 回答
0

我刚刚处理了这个问题,所以我将添加我的解决方案作为其他答案的补充。

这里的问题是它useEffect并没有真正像您希望的那样工作,因为调用仅在第一次渲染后触发,因此存在不必要的延迟。
如果你使用一些像 redux 这样的状态管理器,你可能会因为 store 中的延迟状态而在屏幕上闪烁。

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

所以我写了一个小实用函数,我把它放在和我的路由器相同的目录中:

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

我从组件 HOC 中调用它,如下所示:

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

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

我真的不赞成手动听/不听历史。那只是海事组织。

于 2020-07-12T09:53:28.883 回答