- Created: February 23, 2024 11:11
- 模块: React
- 分类: React
- 相关链接: https://www.robinwieruch.de/react-element-component/
- 学习状态: Yes
- Update: February 26, 2024 9:46 PM
While a React Component is the one time declaration of a component, it can be used once or multiple times as React Element in JSX. In JSX it can be used with angle brackets, however, under the hood React's
createElement
method kicks in to create React elements as JavaScript object for each HTML element.
从代码片段看
import { useState } from 'react';
import './App.css';
// component
const HelloWorld = (props) => {
// element
return <p>hi {props.name}, hello world</p>;
};
function App() {
const [count, setCount] = useState(0);
// instance
const helloWorldInstance1 = <HelloWorld name="Tom" />;
const helloWorldInstance2 = <HelloWorld name="Jack" />;
console.log('helloWorldInstance1:', helloWorldInstance1);
console.log('helloWorldInstance2:', helloWorldInstance2);
return (
<>
{/* element */}
<HelloWorld name="Lucy" />
{/* render instance */}
{helloWorldInstance1}
{helloWorldInstance2}
{/* discouraged!!! */}
{HelloWorld({ name: 'test' })}
</>
);
}
export default App;
为什么不建议直接调用组件方法,例如下面这段代码
import { useState } from 'react';
import './App.css';
// component
const ChildComponent = (props) => {
let [count, setCount] = useState(props.defaultCount || 0);
// element
return (
<div style={{ border: '1px solid red', padding: 8 }}>
<p>
hi {props.name}, count is {count}
</p>
<button onClick={() => setCount(count++)}>Click Me!</button>
</div>
);
};
function Discouraged() {
let [visible, setVisible] = useState(true);
// instance
const childInstance1 = <ChildComponent name="Tom" defaultCount={1} />;
return (
<>
{childInstance1}
<ChildComponent name="Jack" defaultCount={1} />
{/* discouraged!!! */}
{ChildComponent({ name: 'test1', defaultCount: 1 })}
{ChildComponent({ name: 'test2', defaultCount: 1 })}
{visible ? ChildComponent({ name: 'test3', defaultCount: 1 }) : null}
<button onClick={() => setVisible(!visible)}>
{visible ? 'Hidden Test3' : 'Show Test3'}
</button>
</>
);
}
export default Discouraged;
点击 button,隐藏 Test3,会收到报错
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
这是因为在 React 组件的渲染过程中,实际运行的 Hooks 数量少于 React 预期运行的 Hooks 数量。在 React 中,不能在条件语句或循环中调用 Hooks,它们必须始终在最顶层调用,并且每次调用时都应保证 Hooks 的数量相同。
要理解上述代码为何会导致 Hook 数量不一致,我们需要从 React JSX 渲染的角度进行分析。
Babel Compiler Demo,编译后代码如下:
import { useState } from "react";
import "./App.css";
// component
const ChildComponent = (props) => {
let [count, setCount] = useState(props.defaultCount || 0);
// element
return /*#__PURE__*/ React.createElement(
"div",
{
style: {
border: "1px solid red",
padding: 8
}
},
/*#__PURE__*/ React.createElement(
"p",
null,
"hi ",
props.name,
", count is ",
count
),
/*#__PURE__*/ React.createElement(
"button",
{
onClick: () => setCount(count++)
},
"Click Me!"
)
);
};
function Discouraged() {
let [visible, setVisible] = useState(true);
// instance
const childInstance1 = /*#__PURE__*/ React.createElement(ChildComponent, {
name: "Tom",
defaultCount: 1
});
return /*#__PURE__*/ React.createElement(
React.Fragment,
null,
childInstance1,
/*#__PURE__*/ React.createElement(ChildComponent, {
name: "Jack",
defaultCount: 1
}),
ChildComponent({
name: "test1",
defaultCount: 1
}),
ChildComponent({
name: "test2",
defaultCount: 1
}),
visible
? ChildComponent({
name: "test3",
defaultCount: 1
})
: null,
/*#__PURE__*/ React.createElement(
"button",
{
onClick: () => setVisible(!visible)
},
visible ? "Hidden Test3" : "Show Test3"
)
);
}
export default Discouraged;
当使用 JSX 渲染子组件时,React 会将其视为 Element,并隔离其内部实现。而当使用函数的方式渲染时,React 不会将其视为子组件,而是将其所有实现细节(例如 Hooks)都包含在父组件中。因此,当 visible 变化时,会导致父组件的 Hooks 数量发生变化,这违反了 React Hook 的渲染原则,因此会抛出错误。