React:返回 JSX 的函数和函数组件有什么区别?

jun*_*lin 19 javascript reactjs

function renderSomething(foo) {
  return <div>sth {foo}</div>
}

function Something({ foo }) {
  return <div>sth {foo}</div>
}

function Component(props) {
  const { foo } = props
  return (
    <>
        {renderSomething(foo)}
        <Something foo={foo} />
    </>
  )
}
Run Code Online (Sandbox Code Playgroud)

renderSomething()和的 JSX 结果<Something />是相同的。我想知道这两种方式之间有什么区别(例如渲染方式、行为、影响等)?

render方法(即)适用于什么场景renderSomething()?我可以在里面使用挂钩吗?

T.J*_*der 31

renderSomething()和的 JSX 结果<Something />是相同的。

renderSomething(foo)<Something foo={foo} />完全不同的事情,但在特定的用法中,最终结果是相同的:React 创建一个 React 元素,告诉它稍后渲染div(如果使用该元素)。DoingrenderSomething(foo)就像renderSomething一个钩子,它会影响你在它运行时如何使用它。

以下是两个主要区别:

  1. 使用renderSomething(foo)您的代码将立即调用该函数并传入参数。有了<Something foo={foo} />,你就不用打电话了Something。您要求 React 记住它,并在以后需要渲染您执行这些调用的组件时调用它。

  2. 因为你的代码(而不是 React)调用renderSomething,如果你在其中使用了钩子,它们会将信息放入调用组件的实例和状态信息中。相反,如果Something使用钩子,则该实例和状态信息将存储在其自身的组件实例中Something

(还有其他差异。您不能将renderSomethingvia 用作组件<renderSomething foo={foo} />,因为函数组件名称必须以大写字母开头,以区别于 HTML 元素,并且renderSomething需要字符串参数,而不是 props 对象。)

让我们更仔细地看看其中的一些差异:

function renderSomething(foo) {
    console.log(`renderSomething: Called with foo = ${foo}`);
    return <div>sth {foo}</div>;
};

function Something({ foo }) {
    console.log(`Something: Called with foo prop = ${foo}`);
    return <div>sth {foo}</div>;
}

console.log(`Creating elements via renderSomething("x"):`);
const ex1 = renderSomething("x");
console.log(`Creating elements via <Something foo="x" />:`);
const ex2 = <Something foo="x" />;
console.log(`Done`);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)

请注意,代码调用了 renderSomething(您看到它console.log发生了),但没有调用Something(您看不到它console.log)。这是因为上面的差异#1。关键是组件的函数只有在需要渲染时才会被调用。我们没有渲染上面的任何结果,因此该函数永远不会被调用。

第二个区别更微妙,但让我们假设两件事:

  1. 你的“东西”需要一个效果(也许它通过网络加载一些东西)(或者它可以是状态或引用或它使用钩子的任何其他东西),并且
  2. 使用它的组件(我们称之为Example)可能需要也可能不需要渲染它。

这适用于Something

const { useState, useEffect } = React;

function Something({ foo }) {
    const [thing, setThing] = useState(null);
    useEffect(() => {
        setTimeout(() => {  // Fake ajax
            setThing("thing"); 
        }, 100);
    }, []);
    return <div>foo = {foo}, thing = {thing}</div>;
}

function App() {
    const [foo, setFoo] = useState("");

    return <div>
        <div>
            <label>
                <input type="checkbox" checked={!!foo} onChange={() => setFoo(foo => foo ? "" : "hi")} />
                Toggle "foo"
            </label>
            {foo ? <Something foo={foo} /> : null}
        </div>
    </div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
label {
    user-select: none;
}
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)

但现在让我们尝试一下renderSomething

const { useState, useEffect } = React;

function renderSomething(foo) {
    const [thing, setThing] = useState(null);
    useEffect(() => {
        setTimeout(() => {  // Fake ajax
            setThing("thing"); 
        }, 100);
    }, []);
    return <div>foo = {foo}, thing = {thing}</div>;
}

function App() {
    const [foo, setFoo] = useState("");

    return <div>
        <div>
            <label>
                <input type="checkbox" checked={!!foo} onChange={() => setFoo(foo => foo ? "" : "hi")} />
                Toggle "foo"
            </label>
            {foo ? renderSomething(foo) : null}
        </div>
    </div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
label {
    user-select: none;
}
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)

当您勾选带有此错误的框时,它会爆炸:

警告:React 检测到 App 调用的 Hook 顺序发生了变化。如果不修复,这将导致错误和错误。有关更多信息,请阅读 Hooks 规则:https://reactjs.org/link/rules-of-hooks

   上一个渲染 下一个渲染
   -------------------------------------------------- ----
1. useState 使用状态
2. 未定义的useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^

    在应用程序 (<匿名>:35:22)

renderSomething这是因为当我们没有在第一次渲染时调用它时,就会调用代码。这是因为上面的#2:由于renderSomething不是组件函数,因此它不会获得自己的组件实例,并且对其中的钩子的任何调用就像对父组件(Example)中的钩子的调用一样。但函数组件有时不允许调用钩子,有时则不允许。这是因为React 依赖于调用钩子的顺序,并且该顺序必须保持一致才能进行钩子管理。同样,renderSomething像这样使用renderSomething就是用作钩子(这正是自定义钩子的工作方式,最终调用内置钩子,然后将信息存储在父实例中)。

正如您所看到的,虽然您在示例中得到的结果是相同的,但总的来说它们是完全不同的东西。:-)