我已经设置了react-router和一个呈现某些Page组件的Switch
组件。我所有的组件都呈现相同的布局(此刻,以后将是参数化的)。我的页面组件将内容“注入”到布局中,因此我无法让布局组件包装我的外部开关组件。它必须用作页面组件中的子代。
// Sample code:
// app.js
const App = () => (
<LanguageProvider defaultLanguage="el" languages={languages}>
<PathProvider>
<MenuProvider menu={appMenu}>
<TemplateProvider injectedComponents={uiComponentOverrides} toolBar={toolBar}>
<Switch>
<Redirect exact from="/" to="dashboard" />
<Route exact path="/dashboard" component={DashboardIndex} />
<Route exact path="/overview" component={OverviewIndex} />
<Route exact path="/federation" component={FederationIndex} />
<Route exact path="/clubs" component={ClubsIndex} />
<Route exact path="/athletes" component={AthletesIndex} />
<Route exact path="/athletes/:id" component={AthleteDetails} />
<Route exact path="/officials" component={OfficialsIndex} />
<Route component={EmptyPage} />
</Switch>
</TemplateProvider>
</MenuProvider>
</PathProvider>
</LanguageProvider>
);
// Sample of a page component
// dashboard-page.js
export const DashboardIndex = () => {
const { t } = useTranslation();
return (
<AppFullPage key="page-component" title={t('Home.Index.WelcomeTitle')}>
<p>Dashboard content</p>
</AppFullPage>
);
};
// The AppFullPage component is the layout component. Content is injected to it through the title, subtitle, toolbar, menu and children props.
即使最终的html渲染与布局骨架相同(仅在布局的 slot 部分内部不同),也要进行卸载并重新安装整个布局树。
我可以理解重新渲染整个树的反应。 React看到了一个不同的顶部组件(页面组件在每个位置都不同)。因此react渲染了新组件并创建了一个新的虚拟dom。
我无法理解的是卸载和重新安装顶部html元素,因为它们没有发生变化。最高div不变。我的标题菜单中的语言选择器下拉菜单没有更改。左侧的应用程序菜单未更改。是的,它们是从不同的组件渲染的,但是它们是相同的输出。
如果react比较连续渲染的输出并且仅更改需要的内容,那么为什么要卸载并重新安装组件?
此行为会破坏动画和组件的其他行为(例如,即使页面更改后,我希望公用布局空间中的下拉列表也保持打开状态,但是由于已卸载并重新安装它,因此会丢失状态)。
如何指示我不要重新安装相同的组件?
或
我如何实现让页面在布局中注入内容的目标?
使用门户网站是解决方案的候选人吗?
答案 0 :(得分:0)
我们可以看到您的布局组件吗?您确定要卸载/重新安装或重新渲染吗?
如果要卸载组件-应该有某种条件的渲染,请检查该位置。如果组件重新渲染,请检查道具,以确保它们在必要时不会改变。
答案 1 :(得分:0)
将常用的布局组件移到开关之外。然后使用该位置确定您的动态道具。
<AppFullPage>
<Switch>
{all routes}
</Switch>
</AppFullPage>
const AppFullPage = () => {
const location = useLocation();
const [title, setTitle] = useState('');
const { t } = useTranslation();
useEffect(() => {
switch(location.pathname) {
case 'dashboard': {
setTitle(t('Home.Index.WelcomeTitle'));
}
... the rest of your cases
}
}, [location.pathname]);
}
此代码只是一个示例,如果它可以完全按原样运行但应该给您一个想法
,则为idk答案 2 :(得分:0)
我要使用的解决方案如下。 这是一个黑客解决方法,可让嵌套页面在上述级别为布局提供内容。我会尝试改善它。
我正在做的是创建一个上下文(LayoutContext
),该上下文将按插槽名称保存每个插槽的渲染元素。 Layout
组件保留上下文的状态,并将节设置器传递到上下文的后代。
我创建了助手组件(LayoutSectionPlaceHolder
和LayoutSection
),以在需要的地方封装钩子和备注的使用。
在Layout
组件中,我们在每个我们希望页面注入内容的地方渲染一个LayoutSectionPlaceHolder
元素。
每个页面组件应返回一个或多个LayoutSection
元素,并且仅返回一个。我们不希望页面本身呈现任何内容。我们只希望它在内容更改时将内容推送(注入)到布局。 LayoutSection
组件负责将作为子项道具提供的内容注入Layout
。
此解决方案是一种破解方法,因为在页面的第一个呈现中,所有插槽都是空的(尚未设置任何内容)。这是因为第一次安装内容是在安装页面元素时使用useLayoutEffect
钩子,然后在每次内容更改时提供。
话虽这么说,如果React提供一种反向上下文(从子级到父级)的内置机制作为控制模式的反转,那就太好了
import React, { createContext, useCallback, useContext, useLayoutEffect, useReducer, useState } from 'react';
import { Route, Switch } from 'react-router';
import { BrowserRouter, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path='/page1' render={() => <Layout><Page1 /></Layout>} />
<Route exact path='/page2' render={() => <Layout><Page2 /></Layout>} />
</Switch>
</BrowserRouter>
);
}
const LayoutSlotSetterContext = createContext(() => {});
const useLayoutSlotSetter = () => {
const { setSection } = useContext(LayoutSlotSetterContext);
return setSection;
};
const useLayoutSectionUpdater = (slotName, content, slotSetter) => useLayoutEffect(() => slotSetter(slotName, content), [content, slotName, slotSetter]);
const useLayoutSectionContent = (sectionName) => {
const { sections: { [sectionName]: section } = {} } = useContext(LayoutSlotSetterContext);
return section;
};
const LayoutSectionPlaceHolder = ({ section }) => {
const content = useLayoutSectionContent(section);
return content === undefined ? null : content;
};
const LayoutSection = ({ section, children }) => {
const slotSetter = useLayoutSlotSetter();
useLayoutSectionUpdater(section, children, slotSetter);
return null;
};
const Layout = ({ children }) => {
useLayoutEffect(() => () => console.log('unmounted: [Layout]'), []);
const reducer = useCallback((sections, { section, content }) => {
if (!section) return sections;
return {
...sections,
[section]: content,
};
}, []);
const [sections, dispatch] = useReducer(reducer, {});
const setSection = useCallback((section, content) => dispatch({ section, content }), [dispatch]);
return (
<LayoutSlotSetterContext.Provider value={{ sections, setSection }}>
{children}
<Menu /> {/* <-- this element is the same for every page. It should never unmounted */}
<Header /> {/* <-- this element is the same for every page. It should never be unmounted */}
<div>
<LayoutSectionPlaceHolder section='content' />
</div>
<div>
<LayoutSectionPlaceHolder section='secondsMounted' /> seconds mounted [<LayoutSectionPlaceHolder section='page' />]
</div>
</LayoutSlotSetterContext.Provider>
);
};
const Menu = () => {
useLayoutEffect(() => () => console.log('unmounted: [Menu]'), []);
return (
<ul>
<li>
<Link to='/page1'>Page 1</Link>
</li>
<li>
<Link to='/page2'>Page 2</Link>
</li>
</ul>
);
};
const Header = () => {
useLayoutEffect(() => () => console.log('unmounted: [Header]'), []);
const counter = useSecondsMounted();
return <h2>{counter} seconds mounted [header]</h2>;
};
const Page1 = () => {
const counter = useSecondsMounted();
return (
<>
<LayoutSection section='content'>
<p>Page 1 content</p>
</LayoutSection>
<LayoutSection section='page'>Page 1</LayoutSection>
<LayoutSection section='secondsMounted'>{counter}</LayoutSection>
</>
);
};
const Page2 = () => {
const counter = useSecondsMounted();
return (
<>
<LayoutSection section='content'>
<p>Page 2 content</p>
</LayoutSection>
<LayoutSection section='page'>Page 2</LayoutSection>
<LayoutSection section='secondsMounted'>{counter}</LayoutSection>
</>
);
};
/** Counts the seconds a component stays mounted */
const useSecondsMounted = () => {
const [counter, setCounter] = useState(0); // used for illustration purposes. The counter increases +1/sec. It should do that as long as it is visible in the page
useLayoutEffect(() => {
const interval = setInterval(() => setCounter((c) => c + 1), [1000]);
return () => clearInterval(interval);
}, []);
return counter;
};
export default App;