细读 React | Fragment
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。假设我们要使用 React 组件渲染以下这段真实 DOM 节点。
Some text. <h2>A heading</h2> More text. <h2>Another heading</h2> Even more text.
要怎么做呢?很简单,谁都知道...
React.Fragment 是在 React 16.2 新增的新特性,旧版本并不支持。下面我们从几个方面,说明 Fragment 的好处。
一、React 16.0 之前
在低于 React 16.0 的版本,类组件或函数组件有很多限制。
比如,它们必须返回 React 元素或 null
。其中 React 元素包括类似 <MyComponent />
等自定义组件、类似 <div />
等 DOM 节点元素。
正确示例:
function MyComponent() { // ✅ 合法,也可以是其他 HTML 元素 return <div>...</div> } function MyComponent() { // ✅ 合法,返回 React 组件 return <ChildComponent /> } function MyComponent() { // ✅ 合法,不渲染任何真实 DOM 节点 return null }
错误示例:
function MyComponent() { // ❌ 不能返回数组 return [1, 2, 3].map((item, index) => ( <div key={index}>{item}</div> )) // ✅ 但注意,下面这种包裹在 {} 内是合法的, // map 方法返回的数组,目测是除了子元素时,做了扁平化处理。 // return ( // <div> // {[1, 2, 3].map((item, index) => ( // <div key={index}>{item}</div> // ))} // </div> // ) } function MyComponent() { // ❌ 一定要有返回值,跟 return null 是两回事 return undefined }
类组件同理。当不正确使用时,将会报错:Warning: MyComponent(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
这种方案的缺点也是显而易见的,在组件的返回值上,总需要一层、 或其他 DOM 节点包装起来。当 React 渲染成真实 DOM 时,这个包装节点总是会存在的。很多时候,往往这个包装节点对我们的 UI 层是没有意义的,反而加深了 DOM 树的层次。但很无奈,谁让我们要用 React 呢,人家语法限制就那样...
二、React 16.0 起
除了原来的 React 元素和 null
之外,新增了几种类型:
其中布尔值和 null
什么都不渲染,字符串或数值类型会渲染为文本节点。
例如:
function MyComponent() { // ✅ 合法,支持数组了,需要添加 key 属性去避免警告, // 这种情况下,底层会默认嵌套一个 <Fragment> 包裹起来。 return [1, 2, 3].map((item, index) => ( <div key={index}>{item}</div> )) // 或者是 // return [ // <div key="1">1</div>, // <div key="2">2</div>, // <div key="3">3</div> // ] } function MyComponent() { // ✅ 合法,自 React 16.2 起支持 Fragment 语法,不用像上面一样需要 key 了 return ( <React.Fragment> <div>1</div> <div>2</div> <div>3</div> </React.Fragment> ) } function MyComponent() { // ✅ 合法,最终会渲染为文本节点(注意,不是 <span>some string...</span> 哦) return 'some string...' }
相比 React 15.x 及更早版本,这种方式实在是太棒了。除了支持更多类型,最重要的是不会增加额外的节点。前面提到,React 15.x 里的 React 组件总是避免不了需要一层可能是 无谓 的节点节点进行包装,那么 React 16.0 的改进,可以解决如下场景:
问题示例:
function Table() { return ( <table> <tbody> <tr> <Columns /> </tr> </tbody> </table> ) } function Columns() { // 按照 React 15.x 的语法要求,Columns 组件的返回值, // 必须要用一个类似 div 元素等包装起来 return ( <div> <td>Hello</td> <td>World</td> </div> ) }
根据 W3C 的要求,一个合法的 <table>
,<tr>
的子元素必须是 <td>
。而 React 这种组件的写法直接破坏了 <table>
结构,最终也得不到我们的预期结果。
一个合法的
结构应该是这样的,table > thead/tbody/tfoot > tr > td > div/other
。
如果按照 React 16.x 提供的新特性,可以轻松解决...
function Columns() { // React.Fragment 最终渲染为真实 DOM 并不会产生任何 DOM 节点, // 因此,不会破坏 <table> 的结构了。(数组形式也是可以的) return ( <React.Fragment> <td>Hello</td> <td>World</td> </React.Fragment> ) }
三、Fragment
自 React 16.2 起,开始支持 React.Fragment 语法。前面提到该特性是对数组形式的一种增强用法。
语法
它的语法非常简单,把它是 React 内置的一个 React 组件。
<React.Fragment> // One or more child elements </React.Fragment>
key
是唯一可以传递给 Fragment 的属性。将来可能会添加对其他属性的支持,例如事件处理程序。
class App extends React.Component { state = { items: [ { id: '`2`', name: '计算机', description: '用来计算的仪器...' }, { id: '2', name: '显示器', description: '以视觉方式显示信息的装置...' } ] } render() { return <Glossary items={this.state.items} ></Glossary> } } function Glossary(props) { return ( <dl> {props.items.map(item => ( // 没有 `key`,React 会发出一个关键警告 <React.Fragment key={item.id}> <dt>{item.name}</dt> <dd>{item.description}</dd> </React.Fragment> ))} </dl> ) }
也可以使用它的简写语法 <></>
,但这种写法不接受任意属性,包括 key
。
JSX 中的片段语法受到现有技术的启发,例如 E4X 中的 XMLList() <></>
构造函数。使用一对空标签是为了表示它不会向 DOM 添加实际元素的想法。
对比
回到文章开头的示例,要渲染这样一段真实 DOM 节点。
Some text. <h2>A heading</h2> More text. <h2>Another heading</h2> Even more text.
前面提到,可以有几种解决方案,各有利弊。
解决方法一
低于 React 16.0 版本,由于不支持 Fragment 和数组形式,唯一的方法是将它们包装在一个额外的元素中,通常是 div
或 span
。如下:
function MyComponent() { return ( <div> Some text. <h2>A heading</h2> More text. <h2>Another heading</h2> Even more text. </div> ) }
但上述这种方法有个缺点,在渲染成真实 DOM 的时候,会增加一个节点,比如上述的 <div />
。
解决方法二
自 React 16.0 起,支持数组形式。因此可以这么做:
function MyComponent() { return [ 'Some text.', <h2 key="heading-1">A heading</h2>, 'More text.', <h2 key="heading-2">Another heading</h2>, 'Even more text.' ] }
这种方式有点麻烦,我们对比一下 Fragment 形式。
解决方法三(推荐)
自 React 16.2 起,支持 React.Fragment 语法,因此我们可以这样使用。
function MyComponent() { return ( <React.Fragment> Some text. <h2>A heading</h2> More text. <h2>Another heading</h2> Even more text. </React.Fragment> ) }
仔细对比数组和 Fragment 形式,可以发现数组形式有以下缺点:
- 数组中的子项必须用逗号分隔。
- 数组中的 children 必须有一个 key 来防止 React 的 key 警告。
- 字符串必须用引号括起来。
以上这些限制 Fragment 统统都没有,我们就按正常的思维去编写 DOM 节点就好了。
四、References
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论