import React, {useState} from 'react';
import ReactDOM from 'react-dom';
function PageLayout({title, children}) {
return <div>
<h1>{title}</h1>
<input type="text"/>
{children}
</div>;
}
function Page2({setPage}) {
return <PageLayout title="Page2">
<button onClick={() => {setPage(() => Page1);}}>Test</button>
</PageLayout>
}
function Page1({setPage}) {
return <PageLayout title="Page1">
<button onClick={() => {setPage(() => Page2);}}>Test</button>
</PageLayout>
}
function App() {
let [Page, setPage] = useState(() => Page1);
return <Page setPage={setPage}/>;
}
ReactDOM.render(<App />, document.getElementById('app'));
https://codesandbox.io/embed/serene-browser-tehj4?fontsize=14
上述代码是最直观的构建多页面Web应用的模式。--不要在意setPage。只需关注Page组件返回的PageLayout示例。
但是React的diff算法并不适用于这种模式。如果你点击“测试”按钮,输入框中输入的文本将会丢失。
因此我提出了“软组件”的概念。在diff算法中,两个软组件将被视为相同的组件。在这个例子中,我们将Page1和Page2更改为软组件,从而解决了我上面提到的问题。
6条答案
按热度按时间55ooxyrt1#
如果组件的类型发生变化,diff算法将渲染新的组件(如果Page1被渲染,点击后React会卸载Page1并渲染Page2,因此PageLayout也会被卸载)。我认为应该改变的是渲染布局的方式。
因为React依赖于启发式方法,如果它们背后的假设不满足,性能就会受到影响。算法不会尝试匹配不同组件类型的子树。如果你发现自己在两种非常相似的输出之间交替使用两种组件类型,你可能希望将其设置为相同的类型。实际上,我们还没有发现这是一个问题。
键应该是稳定的、可预测的和唯一的。不稳定的键(如Math.random()产生的键)会导致许多组件示例和DOM节点被不必要的重新创建,这可能导致性能下降和子组件中丢失状态。
https://reactjs.org/docs/reconciliation.html#tradeoffs
cbjzeqam2#
我认为Soft Component/Component就像Lisp的宏/函数一样,两者都很有用。
如果不引入Soft Component,我们必须将其写为函数调用而不是组件示例形式。
ttp71kqs3#
你组合组件的方式并不完全符合React的方式。正如@amazzalel-habib建议的,你可能需要重新组织组件。
如果你坚持自己的方式(我并不推荐这样做),你的输入状态将成为一个全局状态,需要相应地进行管理。
sd2nnvve4#
Page1的PageLayout和Page2的PageLayout是两个不同的组件,切换一个到另一个意味着它将完成完整的生命周期,包括
unMount
。在我看来,行为是正常的。w8ntj3qf5#
问题中的代码示例非常清晰地描述了业务场景,这是描述路由组成的一种非常自然的方式。我希望我们能够让它正常工作。
我想支持一个更强烈的建议:让上述代码在没有任何作者输入的情况下正常工作。考虑到代码示例是一个容易犯的错误:上面的代码在导航过程中会长时间地重新创建整个页面DOM元素,只有在某些状态意外重置时(例如在这个例子中的输入、图片加载或其他)才会被注意到。这个建议可能不实用,但我们需要记住这个可能的错误。
上面那些批评者似乎主要关注于描述今天的协调算法是如何工作的,但这并没有告诉我们它明天应该如何工作(这类似于哲学上的is–ought problem)。
鉴于在设计协调算法的实现细节时所考虑的权衡,我相信React作者已经考虑过了这个问题。但归根结底,这是一个React用户需要记住的不自然的怪癖,或者希望不会被它困扰。
hs1ihplo6#
这是一个非常重要的特点——也许我们应该重新表述为:
现有的实现意味着更改容器将销毁子组件的状态。
例如:
在这个例子中,更改容器将失去所有React状态,重新触发动画,重置滚动等。
Kapture.2022-03-23.at.20.24.17.mp4
当然,我们可以创建一个统一的容器,但这并不总是可能的。容器可能很大且复杂,有时仅仅是一个黑盒子。
我们需要一个逃生舱口,如下所示:
这仍然会有一些限制条件——容器必须具有相同的DOM结构(更改DOM元素可能会产生副作用,如重新加载iframes)。
容器仍应像常规组件一样卸载和挂载——我们不希望它们的钩子/状态发生冲突。