react 功能请求:软件组件

zbq4xfa0  于 5个月前  发布在  React
关注(0)|答案(6)|浏览(74)
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更改为软组件,从而解决了我上面提到的问题。

55ooxyrt

55ooxyrt1#

如果组件的类型发生变化,diff算法将渲染新的组件(如果Page1被渲染,点击后React会卸载Page1并渲染Page2,因此PageLayout也会被卸载)。我认为应该改变的是渲染布局的方式。

因为React依赖于启发式方法,如果它们背后的假设不满足,性能就会受到影响。算法不会尝试匹配不同组件类型的子树。如果你发现自己在两种非常相似的输出之间交替使用两种组件类型,你可能希望将其设置为相同的类型。实际上,我们还没有发现这是一个问题。

键应该是稳定的、可预测的和唯一的。不稳定的键(如Math.random()产生的键)会导致许多组件示例和DOM节点被不必要的重新创建,这可能导致性能下降和子组件中丢失状态。

https://reactjs.org/docs/reconciliation.html#tradeoffs

cbjzeqam

cbjzeqam2#

我认为Soft Component/Component就像Lisp的宏/函数一样,两者都很有用。
如果不引入Soft Component,我们必须将其写为函数调用而不是组件示例形式。

ttp71kqs

ttp71kqs3#

你组合组件的方式并不完全符合React的方式。正如@amazzalel-habib建议的,你可能需要重新组织组件。
如果你坚持自己的方式(我并不推荐这样做),你的输入状态将成为一个全局状态,需要相应地进行管理。

sd2nnvve

sd2nnvve4#

Page1的PageLayout和Page2的PageLayout是两个不同的组件,切换一个到另一个意味着它将完成完整的生命周期,包括unMount。在我看来,行为是正常的。

w8ntj3qf

w8ntj3qf5#

问题中的代码示例非常清晰地描述了业务场景,这是描述路由组成的一种非常自然的方式。我希望我们能够让它正常工作。
我想支持一个更强烈的建议:让上述代码在没有任何作者输入的情况下正常工作。考虑到代码示例是一个容易犯的错误:上面的代码在导航过程中会长时间地重新创建整个页面DOM元素,只有在某些状态意外重置时(例如在这个例子中的输入、图片加载或其他)才会被注意到。这个建议可能不实用,但我们需要记住这个可能的错误。
上面那些批评者似乎主要关注于描述今天的协调算法是如何工作的,但这并没有告诉我们它明天应该如何工作(这类似于哲学上的is–ought problem)。
鉴于在设计协调算法的实现细节时所考虑的权衡,我相信React作者已经考虑过了这个问题。但归根结底,这是一个React用户需要记住的不自然的怪癖,或者希望不会被它困扰。

hs1ihplo

hs1ihplo6#

这是一个非常重要的特点——也许我们应该重新表述为:

escape hatch for subtree destruction, during reconciliation

现有的实现意味着更改容器将销毁子组件的状态。
例如:

function App({someUserChoice}) {
  const Container = someUserChoice ? BlueContainer : RedContainer;

  return <Container>
    <Counter/>
  </Container>
}

function Counter() {
  const [counter, setCounter] = useState(0);
  useInterval(() => setCounter(x=> x+1), 1000);

  return <div>{counter}</div>
}

function BlueContainer({ children }: { children: ReactNode }) {
    return <div style={{ background: "blue", padding: 5 }}>{children}</div>;
}

function RedContainer({ children }: { children: ReactNode }) {
    return <div style={{ background: "red", padding: 5 }}>{children}</div>;
}

在这个例子中,更改容器将失去所有React状态,重新触发动画,重置滚动等。
Kapture.2022-03-23.at.20.24.17.mp4
当然,我们可以创建一个统一的容器,但这并不总是可能的。容器可能很大且复杂,有时仅仅是一个黑盒子。
我们需要一个逃生舱口,如下所示:

function App({someUserChoice}) {
  const Container = someUserChoice ? BlueContainer : RedContainer;

  return <Container
    // DRAFT API! ⚠️
    UNSAFE_superKey="app-conatiner" 
    // option 2:
    UNSAFE_container_with_stable_dom
  >
    <Counter/>
  </Container>
}

这仍然会有一些限制条件——容器必须具有相同的DOM结构(更改DOM元素可能会产生副作用,如重新加载iframes)。
容器仍应像常规组件一样卸载和挂载——我们不希望它们的钩子/状态发生冲突。

相关问题