路由参数更改时,组件不会重新安装


97

我正在使用react-router开发一个React应用程序。我有一个项目页面,其URL如下:

myapplication.com/project/unique-project-id

当项目组件加载时,我从componentDidMount事件触发了对该项目的数据请求。我现在遇到一个问题,如果我直接在两个项目之间切换,那么只有id会这样改变...

myapplication.com/project/982378632
myapplication.com/project/782387223
myapplication.com/project/198731289

componentDidMount不会再次触发,因此不会刷新数据。我是否应该使用另一个生命周期事件来触发我的数据请求,或者使用其他策略来解决此问题?


1
您可以发布组件代码吗?
Pierre Criulanscy

Answers:


81

如果链接仅通过不同的参数指向同一条路线,则它不是重新安装,而是接收新的道具。因此,您可以使用该componentWillReceiveProps(newProps)函数并查找newProps.params.projectId

如果您要加载数据,我建议在路由器使用组件上的静态方法处理匹配之前获取数据。看看这个例子。反应路由器兆演示。这样,该组件将加载数据并在路由参数更改时自动更新,而无需依赖componentWillReceiveProps


2
谢谢。我能够使用componentWillReceiveProps使其工作。我仍然不太了解React Router Mega Demo数据流。我看到了某些组件上定义的静态fetchData。这些静态信息如何从路由器客户端调用?
星座

1
好问题。检出client.js文件和server.js文件。在路由器的运行周期中,他们调用fetchData.js文件并将其传递state给它。起初有点令人困惑,但随后变得更加清晰。
布拉德B

12
仅供参考:“ componentWillReceiveProps”被认为是遗产
Idan Dagan

4
您应该在React 16上使用ComponentDidUpdate,因为componentWillReceiveProps已被弃用
Pato Loco

1
这可行,但是存在一个问题,您需要添加componentWillReceiveProps()或componentDidUpdate()来处理对正在加载数据的每个组件的重新渲染。例如,考虑一个包含多个组件的页面,这些组件正在基于相同的path参数加载数据。
JuhaSyrjälä19年

97

如果在更改路线时确实需要重新安装组件,则可以将唯一键传递给组件的key属性(该键与您的路径/路由相关联)。因此,每次路线更改时,密钥也会更改,从而触发React组件卸载/重新安装。我从这个答案中得到了主意


2
辉煌!谢谢您提供这个简单而优雅的答案
Z_z_Z

只是问问,这会不会影响应用程序的性能?
Alpit Anand

1
@AlpitAnand这是一个广泛的问题。重新安装肯定比重新渲染要慢,因为它会触发更多的生命周期方法。在这里,我只是回答如何在更改路线时进行重新安装。

但这不是很大的影响吗?您有什么建议可以安全使用而不会产生太大影响?
Alpit Anand

@AlpitAnand您的问题过于广泛且针对特定应用。对于某些应用程序,是的,这将对性能造成影响,在这种情况下,您应该观看道具更改自己的情况,并仅更新应更新的元素。对于大多数情况而言,以上是一个很好的解决方案
克里斯(Chris)

83

这是我的答案,与上述类似,但有代码。

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />

4
此处的键起了最大的作用,因为您在个人资料页面中具有链接到的图像,单击该图像时,它仅重定向到相同的路线,但是使用不同的参数,但是该组件不会更新,因为React找不到区别,因为必须有一个比较旧结果的密钥
Georgi Peev '19

14

您必须清楚,路由更改不会导致页面刷新,您必须自己处理。

import theThingsYouNeed from './whereYouFindThem'

export default class Project extends React.Component {

    componentWillMount() {

        this.state = {
            id: this.props.router.params.id
        }

        // fire action to update redux project store
        this.props.dispatch(fetchProject(this.props.router.params.id))
    }

    componentDidUpdate(prevProps, prevState) {
         /**
         * this is the initial render
         * without a previous prop change
         */
        if(prevProps == undefined) {
            return false
        }

        /**
         * new Project in town ?
         */
        if (this.state.id != this.props.router.params.id) {
           this.props.dispatch(fetchProject(this.props.router.params.id))
           this.setState({id: this.props.router.params.id})
        }

    }

    render() { <Project .../> }
}

谢谢,这个解决方案对我有用。但是我的陪同人员抱怨这件事:github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules / ...您对此有何看法?
konekoya

是的,实际上这不是最佳实践,很抱歉,在分发了“ fetchProject”之后,减速器无论如何都会更新您的道具,我只是用这个来表达我的观点而没有将redux放到原处
chickenchilli

嗨,小鸡,我实际上还停留在此...您还有其他建议或推荐的教程吗?否则,我暂时坚持您的解决方案。谢谢你的帮助!
konekoya

12

如果你有:

<Route
   render={(props) => <Component {...props} />}
   path="/project/:projectId/"
/>

在React 16.8及更高版本中,使用hooks可以执行以下操作:

import React, { useEffect } from "react";
const Component = (props) => {
  useEffect(() => {
    props.fetchResource();
  }, [props.match.params.projectId]);

  return (<div>Layout</div>);
}
export default Component;

在这里,fetchResource只要有props.match.params.id更改,您就只会触发一个新呼叫。


4
考虑到其他大多数答案都依赖于现在已过时componentWillReceiveProps
且不

7

根据@ wei,@ Breakpoint25和@PaulusLimma的回答,我为制作了此替换组件<Route>。URL更改时,这将重新安装页面,迫使页面中的所有组件都被创建并重新安装,而不仅仅是重新呈现。所有componentDidMount()其他所有启动挂钩也都在URL更改时执行。

这个想法是key在URL更改时更改组件属性,这将迫使React重新安装组件。

您可以将其用作的替代产品<Route>,例如:

<Router>
  <Switch>
    <RemountingRoute path="/item/:id" exact={true} component={ItemPage} />
    <RemountingRoute path="/stuff/:id" exact={true} component={StuffPage} />
  </Switch>
</Router>

<RemountingRoute>组件的定义如下:

export const RemountingRoute = (props) => {
  const {component, ...other} = props
  const Component = component
  return (
    <Route {...other} render={p => <Component key={p.location.pathname + p.location.search}
                                              history={p.history}
                                              location={p.location}
                                              match={p.match} />}
    />)
}

RemountingRoute.propsType = {
  component: PropTypes.object.isRequired
}

这已经通过React-Router 4.3进行了测试。


5

@wei的答案很好用,但是在某些情况下,我发现最好不要设置内部组件的键,而是路由自己。同样,如果组件的路径是静态的,但是您只希望每次用户导航到组件时都重新安装组件(也许在componentDidMount()进行api调用),则可以方便地将location.pathname设置为route的键。有了路线,当位置更改时,所有内容都将重新安装。

const MainContent = ({location}) => (
    <Switch>
        <Route exact path='/projects' component={Tasks} key={location.pathname}/>
        <Route exact path='/tasks' component={Projects} key={location.pathname}/>
    </Switch>
);

export default withRouter(MainContent)

5

这就是我解决问题的方法:

此方法从API获取单个项:

loadConstruction( id ) {
    axios.get('/construction/' + id)
      .then( construction => {
        this.setState({ construction: construction.data })
      })
      .catch( error => {
        console.log('error: ', error);
      })
  }

我从componentDidMount调用此方法,当我第一次加载此路由时,该方法将仅被调用一次:

componentDidMount() {   
    const id = this.props.match.params.id;
    this.loadConstruction( id )
  }

从componentWillReceiveProps开始,因为第二次加载相同的路由但使用了不同的ID,因此我将调用第一个方法重新加载状态,然后component将加载新项目。

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }

这可行,但是存在一个问题,您需要添加componentWillReceiveProps()来处理对正在加载数据的每个组件的重新渲染。
JuhaSyrjälä19年

使用componentDidUpdate(prevProps)。不推荐使用componentWillUpdate()componentWillReceiveProps()。
Mirek Michalak,

0

如果您正在使用类组件,则可以使用componentDidUpdate

componentDidMount() {
    const { projectId } = this.props.match.params
    this.GetProject(id); // Get the project when Component gets Mounted
 }

componentDidUpdate(prevProps, prevState) {
    const { projectId } = this.props.match.params

    if (prevState.projetct) { //Ensuring This is not the first call to the server
      if(projectId !== prevProps.match.params.projectId ) {
        this.GetProject(projectId); // Get the new Project when project id Change on Url
      }
    }
 }

componentDidUpdate现在已被弃用,如果您也可以建议一些替代方法,那将是很好的。
萨希尔

你确定吗?ComponentDidUpdate不是,将来的版本中也不会弃用,您可以附加一些链接吗?该函数将不推荐使用,如下所示:componentWillMount — UNSAFE_componentWillMount componentWillReceiveProps — UNSAFE_componentWillReceiveProps componentWillUpdate — UNSAFE_componentWillUpdate
Angel Mas

0

您可以使用提供的方法:

useEffect(() => {
  // fetch something
}, [props.match.params.id])

当组件保证在更改路线后可以重新渲染时,您可以将prop作为依赖项传递

根据Kent C Dodds的说法,这不是最好的方法,但是足够好来满足您的需求:考虑更多您想发生的事情。


-3

react-router 损坏,因为必须在任何位置更改时重新安装组件。

我设法找到了一个修复此错误的方法:

https://github.com/ReactTraining/react-router/issues/1982#issuecomment-275346314

简而言之(有关完整信息,请参见上面的链接)

<Router createElement={ (component, props) =>
{
  const { location } = props
  const key = `${location.pathname}${location.search}`
  props = { ...props, key }
  return React.createElement(component, props)
} }/>

这将使其在任何URL更改时重新安装

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.