为啥 React 组件是通过 `React.createElement()` 方法执行的?

Posted

技术标签:

【中文标题】为啥 React 组件是通过 `React.createElement()` 方法执行的?【英文标题】:Why are React components executed through the `React.createElement()` method?为什么 React 组件是通过 `React.createElement()` 方法执行的? 【发布时间】:2022-01-16 05:02:37 【问题描述】:

鉴于 React 组件本质上是一个函数:

const Component = ([props]) => React.createElement(type[, props [, ...children]]);

当我尝试将其作为任何正常功能调用时:

Component([props]); 

它不起作用。

同时,在 React 文档中它是这样调用的:

React.createElement(Component([props]));

如何将组件用作type 属性?因为通常情况下,typestring,而不是函数。

【问题讨论】:

相关:***.com/questions/69432301/… “当我尝试将它作为任何普通函数调用时:...它按预期工作。” 你能在那里定义“工作”吗?它不会导致任何东西被渲染到 DOM,如果函数使用钩子,它会抛出错误。 【参考方案1】:

当我尝试将它作为任何普通函数调用时:...它按预期工作。

它可能不会抛出错误(尽管如果你使用了钩子会抛出错误),但在一般情况下它不会正常工作。 (如果您的函数是无状态的并返回调用createElement 的结果,它可能会起作用。)

当你将它传递给createElement 时,React 不会调用你的组件函数,它只是创建并返回一个 React Element 对象,该对象存储该函数以及元素的 props 和子元素:

const Example = (value) => 
    // (Returning a simple string to keep the example simple)
    console.log("Example called");
    return `Hi there, value = $value`;
;

console.log("Creating element (Example is never called)");
const element = React.createElement(Example, value: "x");
console.log("Element object:");
console.dir(element);
.as-console-wrapper 
    max-height: 100% !important;
<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>

React 调用函数稍后,如果你使用那个元素(如果你不使用它永远不会)。通常不止一次,因为大多数元素被多次渲染,并且每次渲染都会调用函数(除非你memoize 它并且备忘录函数说结果与上次相同)。所以这个函数可能永远不会被调用(如果元素从未被使用过),也可能会被重复调用(如果元素被使用过并且曾经重新渲染过)。

另外,React 在调用你的函数之前会在它的末尾设置一些内部的东西,这样如果你的函数使用钩子,它就知道在哪里存储钩子信息。如果你调用一个直接使用钩子的函数,钩子会抛出一个错误,因为没有设置上下文。

这是一个简单的示例(直接使用 createElement 而不是通过 JSX,因为您的问题特别提到了 createElement):

const  useState, createElement  = React;

const Example = (value) => 
    const [counter, setCounter] = useState(0);
    
    console.log(`Example called with value = $value`);
    return createElement(
        "div",
        value,
        [
            `Counter = $counter`,
            createElement(
                "input",
                
                    key: "input",
                    type: "button",
                    value: "+",
                    onClick: () => setCounter(c => c + 1),
                
            )
        ]
    );
;

console.log("Creating element without using it (function is never called):");
const result = createElement(Example, value: "not used");

console.log("Creating element and using it (function is called for each re-render):");
const App = () => 
    return createElement(Example, value: "used");
;

ReactDOM.render(
    createElement(App),
    document.getElementById("root")
);
<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>

【讨论】:

出于某种原因,我认为将其作为普通函数调用是可行的。现在再次验证后,我意识到我的问题是错误的。 @logicalclimber - 你不是唯一一个认为该函数被立即调用的人,这是一个非常常见的误解(当然是我在早期使用 React 时遇到的一个误解)。 :-)

以上是关于为啥 React 组件是通过 `React.createElement()` 方法执行的?的主要内容,如果未能解决你的问题,请参考以下文章

ReactFire - 将 Firebase 绑定到 React 状态时,获取“对象作为 React 子项无效”

在渲染期间,为啥要在 React.createElement 中包装一个函数式组件,而不是通过函数调用来使用它返回的元素呢?

为啥 React 组件库更喜欢基于 prop 的样式

为啥我无法播放 React HTML5 视频组件?

为啥我的 React 组件在 iframe 中的滚动高度为 150 像素?

为啥我的 React Native 桥接 iOS 组件不起作用?