【问题标题】:Component does not remount when route parameters change路由参数更改时组件不会重新挂载
【发布时间】:2018-12-01 22:04:42
【问题描述】:

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

myapplication.com/project/unique-project-id

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

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

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

【问题讨论】:

标签: reactjs react-router


【解决方案1】:

如果您确实需要在路由更改时重新挂载组件,您可以将唯一键传递给组件的键属性(键与您的路径/路由相关联)。所以每次路由改变时,key也会改变,触发React组件卸载/重新安装。我的想法来自this answer

【讨论】:

  • 太棒了!谢谢你这个简单而优雅的回答
  • 只是问一下,这不会影响应用的性能吗?
  • @AlpitAn 这是一个广泛的问题。重新安装肯定比重新渲染慢,因为它会触发更多的生命周期方法。这里我只是回答当路由改变时如何重新挂载。
  • 但影响不大?您有什么建议我们可以安全使用它而不会产生太大影响?
  • @AlpitAnand 您的问题过于宽泛且针对特定应用。对于某些应用程序,是的,这会影响性能,在这种情况下,您应该观察 props 的变化并只更新应该更新的元素。不过,对于大多数人来说,以上是一个很好的解决方案
【解决方案2】:

这是我的答案,与上面的一些类似,但带有代码。

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

【讨论】:

  • 这里的关键起到了最大的作用,因为你在个人资料页面中链接到的图像,点击它只是重定向到相同的路线,但参数不同,但组件没有更新,因为React 找不到差异,因为必须有一个 KEY 来比较旧的结果
  • 这是一个很好的答案 - 将我从 componentDidUpdate 地狱中救了出来;)
  • 最佳答案,因为只有在更改 :pageid 参数时才更新 comp。
【解决方案3】:

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

如果您尝试加载数据,我建议在路由器使用组件上的静态方法处理匹配之前获取数据。看看这个例子。 React Router Mega Demo。这样,组件会在路由参数发生变化时加载数据并自动更新,而无需依赖componentWillReceiveProps。

【讨论】:

  • 谢谢。我能够使用 componentWillReceiveProps 来完成这项工作。我仍然不太了解 React Router Mega Demo 数据流。我看到在某些组件上定义了静态 fetchData。这些静态是如何从路由器客户端调用的?
  • 好问题。查看 client.js 文件和 server.js 文件。在路由器的运行周期中,他们调用 fetchData.js 文件并将state 传递给它。一开始有点混乱,但后来变得更清晰了。
  • 仅供参考:“componentWillReceiveProps”被认为是遗留的
  • 你应该在 React 16 中使用 ComponentDidUpdate,因为 componentWillReceiveProps 已被弃用
  • 这可行,但有一个问题,您需要添加 componentWillReceiveProps() 或 componentDidUpdate() 来处理对正在加载数据的每个组件的重新渲染。例如,考虑一个页面,该页面具有多个基于相同路径参数加载数据的组件。
【解决方案4】:

你要清楚,路由改变不会导致页面刷新,你要自己处理。

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

【讨论】:

  • 谢谢,这个解决方案对我有用。但是我的 eslint 设置抱怨这个:github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/…你怎么看这个?
  • 是的,这实际上不是最佳实践,对此感到抱歉,在调度“fetchProject”之后,您的减速器无论如何都会更新您的道具,我只是用它来表达我的观点,而没有将 redux 放置到位
  • 你好chickenchilli,我其实还停留在这个...你有其他建议或推荐教程吗?或者我现在会坚持你的解决方案。感谢您的帮助!
【解决方案5】:

如果你有:

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

在 React 16.8 及更高版本中,using hooks,你可以这样做:

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

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

在那里,您只会在props.match.params.id 更改时触发新的fetchResource 调用。

【讨论】:

  • 这是更好的答案,因为大多数其他答案都依赖于现在已弃用且不安全的componentWillReceiveProps
【解决方案6】:

根据@wei、@Breakpoint25 和@PaulusLimma 的回答,我为&lt;Route&gt; 制作了这个替换组件。这将在 URL 更改时重新安装页面,强制重新创建和安装页面中的所有组件,而不仅仅是重新渲染。所有componentDidMount() 和所有其他启动挂钩也会在 URL 更改时执行。

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

您可以将其用作&lt;Route&gt; 的直接替代品,例如:

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

&lt;RemountingRoute&gt; 组件的定义如下:

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 测试过。

【讨论】:

    【解决方案7】:

    @wei's answer 效果很好,但在某些情况下,我发现最好不要设置内部组件的键,而是路由本身。此外,如果组件的路径是静态的,但您只想在每次用户导航到它时重新安装组件(可能在 componentDidMount() 进行 api 调用),将 location.pathname 设置为路由键很方便。有了它的路由,它的所有内容都会在位置改变时重新安装。

    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)
    

    【讨论】:

    • 将key 直接添加到&lt;Route /&gt; 是更简单的修复,它也适用于与 Typescript 进行反应而无需额外代码(因为其他类似修复需要对类型进行一些额外的工作) .
    【解决方案8】:

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

    此方法从 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后被调用,我调用第一个方法来重新加载状态,然后组件将加载新项目。

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

    【讨论】:

    • 这可行,但有一个问题是您需要添加 componentWillReceiveProps() 以处理对正在加载数据的每个组件的重新渲染。
    • 使用 componentDidUpdate(prevProps)。 componentWillUpdate() componentWillReceiveProps() 已弃用。
    【解决方案9】:

    您可以使用提供的方法:

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

    当组件保证在路由更改后重新渲染时,您可以通过 props 作为依赖项

    不是最好的方法,但足以处理您正在寻找的东西 根据 Kent C Dodds: 多考虑一下你希望发生的事情。

    【讨论】:

      【解决方案10】:

      如果你使用的是类组件,你可以使用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 不会也不会在即将到来的版本中被弃用,你能附上一些链接吗?要弃用的函数如下: componentWillMount — UNSAFE_componentWillMount componentWillReceiveProps — UNSAFE_componentWillReceiveProps componentWillUpdate — UNSAFE_componentWillUpdate
      【解决方案11】:

      这是一个非常简单的解决方案:在 componentDidUpdate 中进行位置检查,并有一个 getData 函数,该函数具有带有 setState 的数据获取部分:

      componentDidUpdate (prevProps) {
          if (prevProps.location.key !== this.props.location.key) {
              this.getData();
          }
      }
      
      
      getData = () => {
          CallSomeAsyncronousServiceToFetchData
              .then(
                  response => {
                      this.setState({whatever: response.data})
                  }
              )
      }
      

      【讨论】:

        【解决方案12】:

        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 更改时重新挂载

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-06-16
          • 1970-01-01
          • 2017-02-23
          • 2020-01-02
          • 2020-08-19
          相关资源
          最近更新 更多