路由参数更改时组件不会重新挂载

Component does not remount when route parameters change

我正在使用 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所以数据没有刷新。我应该使用另一个生命周期事件来触发我的数据请求或解决此问题的不同策略吗?

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

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

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

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

虽然我设法找到了这个错误的修复程序:

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

简而言之(有关完整信息,请参阅上面的 link)

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

这将使它在任何 URL 更改时重新挂载

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

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

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

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

效果很好,但在某些情况下,我发现最好不要设置内部组件的键,而是路由自身。此外,如果组件的路径是静态的,但您只想在每次用户导航到组件时重新挂载组件(可能在 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)

我是这样解决问题的:

此方法从 API:

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

我从 c​​omponentDidMount 调用这个方法,这个方法只会在我第一次加载这个路由时被调用一次:

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

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

想法是在 URL 更改时更改组件 key 属性,这会迫使 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 测试。

如果你有:

<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 调用。

如果你正在使用Class组件,你可以使用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
      }
    }
 }

您可以使用提供的方法:

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

当组件在路由更改后保证 re-render 时,您可以通过 道具作为依赖项

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

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

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


getData = () => {
    CallSomeAsyncronousServiceToFetchData
        .then(
            response => {
                this.setState({whatever: response.data})
            }
        )
}

我会尽量给出更新后的答案。

所以基本上你想要的是只要我们发现参数发生变化就重新安装组件。

现在 react-router>6.0 已经发布,这个答案将用于 React Router ^V6

所以假设我有一条路

/user/<userId>

这里的<userId>会被替换成实际的userId

所以这个路径的初始化会是这样

<BrowserRouter>
    <Routes>
        <Route
           path="/user/:userid"
              element={<YourComponent/>}
         />
     </Routes>
</BrowserRouter>

现在在<YourComponent/>接收端

import { useParams } from "react-router-dom";
import { useEffect } from "react";

export default function YourComponent(){
    const {userid} = useParams();

    useEffect(() => {
        // Some Logic Here
    },[userid])

   return (<Component/>)
}

所以这个 useEffect() 会在每次参数发生变化时自行挂载(userid 在这种情况下)

因此,每当您使用相同的参数点击 URL 时(这不等于重新加载页面。我的意思是导航到相同的 URL),组件将不会自行重新挂载但是只要我认为可以解决您的问题的参数发生变化,就会重新安装。

React Router v6+

您需要指定一个密钥。在 React Router 的早期版本中,您可以执行以下操作:

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

由于 render 不能再与 <Route> 一起使用(从 React Router v6 开始),最简单的解决方案之一是创建一个辅助组件:

const PageProxy = (props) =>
{
  const { pid } = useParams();
  return <Page key={pid} {...props} />;
}

你的 <Route> 变得简单:

<Route path="/project/:pid" element={<PageProxy />} />