我已经设置了react路由器和一个交换机
组件来呈现一些页面组件。我的所有组件都呈现相同的布局(目前,稍后将是参数化的)。我的页面组件将内容“注入”到布局中,因此我不能让布局组件包装我的外部开关组件。它必须用作页面组件中的子级。
// 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呈现与布局框架相同(它仅在布局的插槽部分内不同),react也会卸载并重新装载整个布局树。
我能理解重新渲染整棵树的反应。React会看到不同的顶部组件(每个位置的页面组件都不同)。因此react呈现新组件并创建一个新的虚拟dom。
我不明白的是,由于顶级html元素没有改变,所以它们的卸载和重新装载是不可能的。头号人物没有改变。“我的标题”菜单中的“语言选择器”下拉列表没有更改。左边的应用程序菜单没有改变。是的,它们从不同的组件渲染,但它们是相同的输出。
如果react比较连续渲染的输出,只更改所需内容,那么为什么要卸载和重新装载组件?
此行为会破坏组件的动画和其他行为(例如,我希望公共布局空间中的下拉列表即使在页面更改后仍保持打开状态,但由于它已卸载并重新装载,因此会丢失状态)。
如何指示react不重新安装相同的组件?
或
如何实现让页面在布局中插入内容的目标?
使用门户是解决方案的候选方案吗?
我使用的解决方案如下。这是一个黑客解决方案,使嵌套页面能够为上面级别的布局提供内容。我会努力改进它。
我所做的是创建一个上下文(LayoutContext
),它将按插槽名称保存每个插槽的渲染元素。Layout
组件保存上下文的状态,并通过上下文将节设置器传递给后代。
我创建了helper组件(LayoutSectionPlaceHolder
和LayoutSection
)来封装钩子和备忘录的使用。
在Layout
组件中,我们在每个我们希望页面注入内容的地方呈现一个LayoutSectionPlaceHolder
元素。
每个页面组件应返回一个或多个LayoutSection
元素,并且仅返回该元素。我们不希望页面本身呈现任何内容。我们只希望它在内容更改时将内容推(注入)到布局中<代码>版面部分组件负责将作为子道具提供给它的内容注入版面
。
这个解决方案是一个黑客,因为在页面的第一次渲染中,所有的插槽都是空的(还没有设置内容)。这是因为当页面元素第一次被html" target="_blank">安装时,内容被提供,使用useLayout效应
钩子,然后每当内容改变时。
这就是说,如果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;
将常用布局组件移到交换机外部。然后使用位置来确定您的动态道具。
<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]);
}
这段代码只是一个例子,如果它将工作完全一样,但应该给你一个想法
我们能看看你的布局组件吗?你确定它卸载/重新安装或重新渲染?
如果组件卸载-应该有某种条件渲染,检查那个地方。如果组件重新呈现,然后检查道具,以确保它们不会改变,当它们不能改变时。
我有一个React应用程序,它声明了一些路由: (是检查会话的哑组件,调用
我有一个关于Vuejs和Vue路由器的项目。当用户登录系统时,服务器返回一个带有令牌、用户名和名字的Json文件,该文件存储在localstorage中,以便在以下请求中使用。第一个名称在布局中用于在导航栏中显示欢迎消息。注销后,localstorage被清除,问题就在这里,当其他用户在消息welcome not reload中使用新的名字付费时。
如何使用钩子(或任何其他钩子)来复制? 在传统的类组件中,我将执行以下操作: 使用hook: (完整示例:https://codesandbox.io/s/2oo7zqzx1n) 这不起作用,因为在中返回的“cleanup”函数捕获装载期间的道具,而不是卸载期间的道具状态。 如何在不运行函数体(或清除)的情况下对每个道具更改进行清理? 一个类似的问题并没有涉及获得最新道具的部分。 文件状态为: 如
我有几个路线,每个路线装载3个组件。所有管线上的两个组件都相同。当我在这些路由之间移动时,我希望传递新数据,在组件的某个初始化事件上,我希望填充该组件的数据,以便它反映在UI上。我还想重新触发正在加载的组件的引导动画。我该怎么做呢。因为现在,我不知道在组件的生命周期中,我将在哪里获取数据,并使用这些新数据重新提交组件。具体来说,在myapps/1和/newapp/I中有一个主视图组件和一个侧栏组件
在我的情况下,当使用从react-redux时,反应路由器不会将我的反应组件视为有效组件。并且有一个警告:失败proType:无效prop提供给 Main.js
问题: 有异步路由,从中获取参数并返回(组件)。问题是当我从一个到另一个(只是相同的路由,但不同。参数)我的路由器卸载并重新渲染组件。但它应该只传递新的道具,不要触及我的组件生命周期。怎么解决呢?我做错了什么? 代码: 路由器 组件将装入电影组件中 更新:好的,做了一些实验。 替换