【问题标题】:How to listen to route changes in react router v4?如何在反应路由器 v4 中监听路由变化?
【发布时间】:2017-06-14 03:48:04
【问题描述】:

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

有没有办法在 react router v4 中监听路由变化?

【问题讨论】:

标签: react-router-v4


【解决方案1】:

我使用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>
  }
}

希望对你有帮助

【讨论】:

  • 在反应路由器 v4 中使用 'this.props.location.pathname'。
  • @ledfusion 我也在做同样的事情并使用withRouter,但我收到错误You should not use &lt;Route&gt; or withRouter() outside a &lt;Router&gt;。我在上面的代码中没有看到任何 &lt;Router/&gt; 组件。那么它是如何工作的呢?
  • 嗨@maverick。我不确定您的代码是什么样子,但在上面的示例中,&lt;Switch&gt; 组件充当了事实上的路由器。只有第一个具有匹配路径的 &lt;Route&gt; 条目将被呈现。在这种情况下不需要任何&lt;Router/&gt; 组件
  • 要使用@withRouter 你需要安装 npm install --save-dev transform-decorators-legacy
  • withRouter 到底怎么样。
【解决方案2】:

要扩展上述内容,您需要获取历史对象。如果您使用BrowserRouter,您可以导入withRouter 并用higher-order component (HoC) 包装您的组件,以便通过道具访问历史对象的属性和函数。

    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 的方法似乎会在将对象解构到其中时污染道具。

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

【讨论】:

  • 无论问题如何,答案都帮助我理解了一些东西:)。但是将withRoutes 修复为withRouter
  • 是的,抱歉,感谢您指出这一点。我已经修改了帖子。我将正确的导入放在问题的顶部,然后在代码示例中拼写错误。
  • 我认为current version of withRouter 传递history 而不是变量listen
  • 最好修改帖子以证明不听;这段代码有内存泄漏。
  • 您应该只订阅一次!现在您将订阅重新渲染的每个组件。
【解决方案3】:

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

【讨论】:

  • 请注意,因为我遇到了错误:Cannot read property 'location' of undefined at useLocation。您需要确保 useLocation() 调用不在将路由器放入树的同一组件中:see here
  • 不幸的是,这不会触发嵌套路由上的操作
  • 感谢您的精彩回答。
  • 这是最简洁的解决方案。
【解决方案4】:

你应该使用history v4 lib。

来自there的示例

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

【讨论】:

  • history.pushState() 和 history.replaceState() 调用不会触发 popstate 事件,因此仅此一项不会涵盖所有路由更改。
  • @Ryan 似乎history.push 确实触发了history.listen。请参阅history v4 docs 中的使用基本 URL 示例。因为 history 实际上是浏览器原生 history 对象的包装,所以它的行为与原生对象不完全相同。
  • 这感觉是一个更好的解决方案,因为很多时候你需要监听事件推送的路由变化,这与 react 组件生命周期事件无关。
  • 潜在的内存泄漏!你这样做非常重要! “当您使用 history.listen 附加侦听器时,它会返回一个可用于删除侦听器的函数,然后可以在清理逻辑中调用该函数:const unlisten = history.listen(myListener); unlisten();
  • 转到此处获取有关历史包的文档。 github.com/ReactTraining/history/blob/master/docs/…
【解决方案5】:

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 配合使用。

【讨论】:

    【解决方案6】:
    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]);
    
    }
    

    带钩子

    【讨论】:

    • 冬青杰西斯!它是如何工作的?你的回答很酷!但是我将调试器点放在 useEffect 中,但是任何时候我更改了路径名,位置都保持 undefined?你能分享任何好文章吗?因为很难找到任何明确的信息
    • 使用react-router-dom 而不是react-router
    【解决方案7】:

    带钩子:

    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)
    

    导入并渲染为&lt;DebugHistory&gt;组件

    【讨论】:

      【解决方案8】:
      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
      • 它也必须在&lt;Router&gt; 元素内,否则你会得到TypeError: history is undefined
      【解决方案9】:

      使用 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
      

      【讨论】:

        【解决方案10】:

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

        import React, {useEffect} from 'react';
        
        const SampleComponent = (props) => {
        
              useEffect(() => {
                console.log(props.location);
              }, [props.location]);
        
        }
        
        export default SampleComponent;
        

        【讨论】:

          【解决方案11】:

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

          【讨论】:

            【解决方案12】:

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

            【讨论】:

              【解决方案13】:

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

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

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

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

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

              我在组件 HOC 中这样调用:

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

              path 是使用withRouter 时在match 对象中传递的道具。

              我不太赞成手动听/不听历史。 这只是imo。

              【讨论】:

                猜你喜欢
                • 2017-12-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-09-16
                • 2017-10-21
                • 2018-01-26
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多