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

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

myapplication.com/project/unique-project-id

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

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

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

77710 次浏览

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

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

如果在路由更改时确实需要重新挂载组件,可以向组件的 key 属性传递一个惟一的键(该键与路径/路由相关联)。因此,每次路由改变时,键也会改变触发 React 组件卸载/重新挂载的触发器。我从 这个答案得到的灵感

react-router中断是因为它必须在任何位置更改时重新安装组件。

I managed to find a fix for this bug though:

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

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

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

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

此方法从 API 获取单个项:

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

我从 Component entDidMount 调用这个方法,当我第一次加载这个路由时,这个方法只会被调用一次:

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

然后从 Component WillReceiveProps 调用它,因为第二次我们加载相同的路由但是 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 更改上执行。

The idea is to change components key property when the URL changes and this forces React to re-mount the component.

你可以用它作为 <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
}

这已经用反应路由器4.3进行了测试。

如果你有:

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

在反应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调用。

如果使用类组件,则可以使用 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])

当组件保证在路由更改后重新呈现时,可以通过 作为依赖的道具

不是最好的方式,但足以处理你正在寻找的东西 根据 肯特 · C · 多兹: Consider more what you want to have happened.

这里有一个非常简单的解决方案: 在 Component entDidUpdate 中执行位置检查,并使用 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 路由器 ^ 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/>)
}


因此,每当参数发生变化时(本例中为 userid) ,这个 useEffect()都会自动挂载

因此,当你用相同的参数点击 URL 时,结果就是(这不等于重新加载页面)。我的意思是导航到相同的 URL)组件不会重新挂载自己,但会有重新挂载,每当有一个变化的参数,我认为解决了你的问题。

反应路由器 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} />;
}

And your <Route> becomes simply:

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