当将React与React Router一起使用时,我遇到了一些安装问题。 这甚至可能不是React Router本身的问题。 我想传递一些其他数据以及子路线。 这似乎是可行的,但是每次状态更改时,主页上的更改都会触发重新安装孙代。
这是为什么?为什么这只会发生在子孙身上而不仅仅是孩子?
代码示例:
import React, { useEffect, useState } from 'react';
import { Route, Switch, BrowserRouter as Router, Redirect } from 'react-router-dom';
const MainPage = ({ ChildRoutes }) => {
const [foo, setFoo] = useState(0);
const [data, setData] = useState(0);
const incrementFoo = () => setFoo(prev => prev + 1);
useEffect(() =>{
console.log("mount main")
},[]);
useEffect(() =>{
setData(foo * 2)
},[foo]);
return (
<div>
<h1>Main Page</h1>
<p>data: {data}</p>
<button onClick={incrementFoo}>Increment foo {foo}</button>
<ChildRoutes foo={foo} />
</div>
);
};
const SecondPage = ({ ChildRoutes, foo }) => {
const [bar, setBar] = useState(0);
const incrementBar = () => setBar(prev => prev + 1);
useEffect(() =>{
console.log("mount second")
},[]);
return (
<div>
<h2>Second Page</h2>
<button onClick={incrementBar}>Increment bar</button>
<ChildRoutes foo={foo} bar={bar} />
</div>
);
};
const ThirdPage = ({ foo, bar }) => {
useEffect(() =>{
console.log("mount third")
},[]);
return (
<div>
<h3>Third Page</h3>
<p>foo: {foo}</p>
<p>bar: {bar}</p>
</div>
);
};
const routingConfig = [{
path: '/main',
component: MainPage,
routes: [
{
path: '/main/second',
component: SecondPage,
routes: [
{
path: '/main/second/third',
component: ThirdPage
},
]
}
]
}];
const Routing = ({ routes: passedRoutes, ...rest }) => {
if (!passedRoutes) return null;
return (
<Switch>
{passedRoutes.map(({ routes, component: Component, ...route }) => {
return (
<Route key={route.path} {...route}>
<Component {...rest} ChildRoutes={props => <Routing routes={routes} {...props}/>}/>
</Route>
);
})}
</Switch>
);
};
export const App = () => {
return(
<Router>
<Routing routes={routingConfig}/>
<Route exact path="/">
<Redirect to="/main/second/third" />
</Route>
</Router>
)
};
export default App;
MainPage中的每个单独状态更改都会导致ThirdPage重新安装。
由于React Router,我无法使用StackOverflow创建代码段。因此,这是一个与代码完全相同的codeandbox:https://codesandbox.io/s/summer-mountain-unpvr?file=/src/App.js
预期的行为是每个页面仅触发一次安装。 我知道我可能可以通过使用Redux或React.Context来解决此问题,但是现在我想知道是什么原因导致了这种现象,以及是否可以避免。
=========================
更新: 使用React.Context可以正常工作,但是我想知道如果没有它可以做到吗?
工作片:
const ChildRouteContext = React.createContext();
const ChildRoutesWrapper = props => {
return (
<ChildRouteContext.Consumer>
{ routes => <Routing routes={routes} {...props} /> }
</ChildRouteContext.Consumer>
);
}
const Routing = ({ routes: passedRoutes, ...rest }) => {
if (!passedRoutes) return null;
return (
<Switch>
{passedRoutes.map(({ routes, component: Component, ...route }) => {
return (
<Route key={route.path} {...route}>
<ChildRouteContext.Provider value={routes}>
<Component {...rest} ChildRoutes={ChildRoutesWrapper}/>
</ChildRouteContext.Provider>
</Route>
);
})}
</Switch>
);
};
答案 0 :(得分:5)
要了解此问题,我认为您可能需要了解React组件和React元素之间的区别以及React对帐的工作原理。
反应组件是基于类或功能的组件。您可以将其视为将接受一些道具和 最终返回一个React元素。而且您应该只创建一次React组件。
另一方面,反应元素是描述组件实例或DOM节点及其所需属性的对象。 JSX提供 通过其React组件创建React元素的语法:
<Component someProps={...} />
在单个时间点,您的React应用程序是一棵React元素树。最终,该树将转换为显示在屏幕上的实际DOM节点。
每次状态改变时,React都会构建另一棵新树。之后,React需要根据新树和最后一棵树之间的差异,找到一种有效更新DOM节点的方法。此过程称为对帐。此过程的差异算法是比较两个根元素,如果这两个是:
// this means re-mount that element (unmount and mount again)
来构建新树。// this means keep the instance (React element) and update the props
这只是理论的简要介绍,让我们开始实践吧。
我将进行一个类比:React组件是特定工厂的工厂,而React元素是特定工厂的产品。工厂应该创建一次。
这行代码ChildRoutes
是一个工厂,每次Component
的父级重新渲染时(由于Javascript函数的创建方式),您都在创建一个新工厂:
<Component {...rest} ChildRoutes={props => <Routing routes={routes} {...props}/>}/>
MainPage
基于routingConfig,创建了一个工厂来创建SecondPage
。 SecondPage
创建了一个工厂来创建ThirdPage
。在MainPage
中,当有状态更新时(例如:foo
递增):
MainPage
重新呈现。它使用其SecondPage
工厂创建一个SecondPage
产品。由于工厂不变,因此稍后将根据“相同类型的组件元素”规则对创建的SecondPage
产品进行差异化。SecondPage
重新呈现(由于foo
道具变更)。再次创建其ThirdPage
工厂。因此,新创建的ThirdPage
产品与以前的ThirdPage
产品不同,并且后来根据“不同类型的元素”进行了区分。这就是导致ThirdPage
元素重新挂载的原因。要解决此问题,我正在使用渲染道具作为使用“一次创建”工厂的方式,以便稍后通过“相同类型的组件元素”来区分其创建的产品规则。
<Component
{...rest}
renderChildRoutes={(props) => (<Routing routes={routes} {...props} />)}
/>
这是工作示例:https://codesandbox.io/s/sad-microservice-k5ny0
参考:
答案 1 :(得分:3)
罪魁祸首是这条线:
<Component {...rest} ChildRoutes={props => <Routing routes={routes} {...props}/>}/>
更具体地说,是ChildRoutes
道具。在每个渲染上,您正在为其提供一个全新的功能组件,因为给定了:
let a = props => <Routing routes={routes} {...props}/>
let b = props => <Routing routes={routes} {...props}/>
a === b
总是以false结尾,因为它是2个不同的函数对象。由于您在每个渲染器上都为其提供了一个新的功能对象(一个新的功能组件),因此它只能从此Node重新安装组件子树,因为它每次都是一个新组件。
解决方案是提前在渲染方法外部 中创建一次此功能组件,如下所示:
const ChildRoutesWrapper = props => <Routing routes={routes} {...props} />
...然后传递此单个功能组件:
<Component {...rest} ChildRoutes={ChildRoutesWrapper} />
答案 2 :(得分:1)