React hooks:我们可以将组件作为函数来调用吗?

问题描述 投票:0回答:1

考虑我们有这个示例组件:

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <div> {count} </div>

      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Increase
      </button>
    </div>
  );
}

我有时会遇到一些代码,其中有人在渲染中将反应组件调用为函数

 const App = () => (
     <div> {Counter()} </div>
 )

vs 将其渲染为元素:

 const App = () => (
     <div> <Counter/> </div>
 )

在react hooks中,是否允许将组件作为函数调用?如果我们这样做会出现什么问题?


似乎有一个问题可以认为与此类似;尽管那不是专门关于钩子的。而且这个问题似乎强调了性能影响。

javascript reactjs react-hooks
1个回答
28
投票

React docs 不鼓励将组件作为函数调用:

组件只能在 JSX 中使用。不要像平常那样称呼他们 功能。 React 应该调用它。

他们提到的原因之一是:

如果组件包含Hooks,很容易违反Hooks规则 当直接在循环中或有条件地调用组件时。

虽然他们没有详细说明上面的引用,但我认为下面的示例显示了他们可能的含义。

当您将组件作为函数调用并且其中包含钩子的使用时,在这种情况下,React 会认为该函数中的钩子属于父组件。因此,如果您有条件地调用此类组件(就像我们对下面的

TestB()
所做的那样),您将违反钩子的rules之一。点击重新渲染按钮可以看到错误:

错误:渲染的钩子数量少于预期。这可能是由于 意外提前退货声明。

 
function TestB() {
  let [B, setB] = React.useState(0);
  return (
    <div
      onClick={() => {
        setB(B + 1);
      }}
    >
      counter B {B}
    </div>
  );
}
 

function App() {
  let [A, setA] = React.useState(0);

  return (
    <div>
      <button
        onClick={() => {
          setA(A + 1);
        }}
      >
        re-render
      </button>
      {/* Conditionally render TestB() */}
      {A % 2 == 0 ? TestB() : null}
    </div>
  );
}
ReactDOM.render(
  <App />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

现在您可以使用上面的

<TestB/>
来代替并查看差异。

文档还提到将组件呈现为元素(而不是将其作为函数调用)允许组件类型参与协调

这意味着,当您将反应组件渲染为反应元素时,例如

<TestB/>
,然后在下一次渲染时,由于协调算法(并且因为组件类型已更改),React 将卸载
<TestC/>
组件(其所有状态都将消失)并安装一个新组件
<TestB/>
。但是,如果您将其称为函数(例如
<TestC/>
),则组件类型将不再参与协调,您可能无法获得预期的结果。
见下面的例子:

TestB()
function TestB() {    
  return (
    <div     
    >
      <input/>
    </div>
  );
}
function TestC() {
  console.log("TestC")
  return (
    <div     
    >
      <input/>
    </div>
  );
}

function App() {
  let [A, setA] = React.useState(0);

  return (
    <div>
      <button
        onClick={() => {
          setA(A + 1);
        }}
      >
        re-render
      </button>
      {/*  Here we are alternating rendering of components */}
      {A % 2 == 0 ? TestB() : TestC()} 
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById("react")
);

在输入框中输入内容
  • 现在单击重新渲染按钮
  • 您现在可以从日志中看到组件
  • <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script> <div id="react"></div>
  • 已渲染,但输入显示的值与您之前键入的值相同 - 这可能不是您想要的,因为您渲染了不同的组件。发生这种情况是因为 Reacts 协调算法无法检测到我们移动到了不同的组件(从
    TestC
    TestB
    )并且没有从 DOM 中删除先前的输入实例。
    
    
  • 现在将这些组件渲染为元素(
TestC

<TestB/>
)以查看差异。

将组件渲染为元素的好处

文档还提到了将组件渲染为元素的以下好处:

组件不再只是功能。 React 可以通过以下方式增强它们 通过 Hooks 绑定到本地状态等功能 组件在树中的身份。

组件类型参与协调。通过让 React 调用 你的组件,你还告诉它更多关于概念结构的信息 你的树。例如,当您从渲染移动到 页面,React 不会尝试重用它们。

React 可以增强您的用户体验。例如,它可以让 浏览器在组件调用之间做一些工作,以便重新渲染 大组件树不会阻塞主线程。

更好的调试故事。如果组件是一等公民 该库知道,我们可以构建丰富的开发工具 在发展中反省。

更高效的对账。 React 可以准确决定哪个 树中的组件需要重新渲染并跳过那些 不。这使您的应用程序更快、更敏捷。

© www.soinside.com 2019 - 2024. All rights reserved.