如何编写更简洁优雅的React代码

原文地址:How to Write Cleaner React code原文作者:Aditya Tyagi本文永久链接:https://github.com/xitu/gold-miner/blob/master/article/2022/how-to-write-cleaner-react-code.md译者:Z招锦校对者:zaviertang

避免使用不必要的标签

在接触了十多个生产级别的 React 项目之后,我发现在大多数项目中都存在一个问题,那就是使用了不必要的 HTML 元素或标签。保持代码容易维护、编写、阅读和调试是非常重要的,你可以按照以下方法,来判断代码是否遵循清洁代码准则。


这些不必要的标签造成了 DOM 污染。虽然引入这些标签是为了克服 React 中 JSX 的缺点(JSX 应该总是返回单个 HTML 根元素[6])。

换句话说,这是无效的 JSX:

// 括号有助于编写多行 HTML
const element = (

// 第 1 个 div 块
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
// 第 2 个 div 块
<div>
<h1>Sibling element</h1>
<h2>I will break JSX element</h2>
</div>
);

很多开发者通过添加 div 标签包裹代码块来解决 JSX 的这个问题。

const element = (

// div 包裹
<div>
// 第 1 个 div 块
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
// 第 2 个 div 块
<div>
<h1>Sibling element</h1>
<h2>I will break JSX element</h2>
</div>
</div>
);

目前这个方法适用于小型项目。当我开始在开发大型 React 项目时,发现代码中充满了 div 标签。这迟早会导致出现 div soup

什么是 div soup

接下来看一个示例。

请看以下这段 React 代码:

return (
// 这个 div 什么也没做,只是包裹了两个子标签
<div>
<h1>This is heading</h1>
<h2>This is a sub-heading</h2>
</div>
)

这在 DOM 中的结果将会是这样:

这只是一个小示例,真正的 React 应用要复杂得多。你可以在组件之间有一个深度嵌套的父子关系。例如:

return (
<div>
<h1>This is heading</h1>
<h2>This is a sub-heading</h2>
<Child1>
<Child2>
<Child3>
<Child4/>
</Child3>
</Child2>
</Child1>
</div>
)

再来看子标签的代码:

// 每个 React JSX 元素都接收 props 参数
const Child1 = (props) => (
<div>
<h3>I am child 1</h3>
{/* 在 <Child1> 和 </Child1> 之间传递的任何内容 */}
{props.children}
</div>
);
const Child2 = (props) => (
<div>
<h3>I am child 2</h3>
{props.children}
</div>
);
const Child3 = (props) => (
<div>
<h3>I am child 3</h3>
{props.children}
</div>
);
const Child4 = () => (
<div>
<h3>I am child 4</h3>
</div>
);

最终的 DOM 将会是这样:



如果你仔细检查生成的 DOM,会看到大量的 div 标签,这些标签没有任何作用,只是为了包裹代码和克服 JSX 的限制。最终,这将会导致一个 div soup

这可能会成倍地增加调试时间,对更快的交付和错误修复造成影响。

如何避免 div soup

目光敏锐的读者一定已经注意到了问题代码的解决方案。我们所要做的就是创建一个封装的 React 组件,它可以返回不带 div 的传递组件:

// 包裹组件,返回 <Wrapper></Wrapper> 之间传递的任何 DOM 元素
// 在 props 上本来就有 children 属性
// 所有的 React JSX 元素都应该大写,作为一种命名规范

const Wrapper = (props) => {
return props.children;
}

重构之前的代码:

// 带有 Wrapper 组件的 Children
// 每个 React JSX 元素都接收 props 参数
const Child1 = (props) => (
<Wrapper>
<h3>I am child 1</h3>
{/* 在 <Child1> 和 </Child1> 之间传递的任何内容 */}
{props.children}
</Wrapper>
);
const Child2 = (props) => (
<Wrapper>
<h3>I am child 2</h3>
{props.children}
</Wrapper>
);
const Child3 = (props) => (
<Wrapper>
<h3>I am child 3</h3>
{props.children}
</Wrapper>
);
const Child4 = () => (
<Wrapper>
<h3>I am child 4</h3>
</Wrapper>
);

同时:

// 带有 Wrapper 组件的 Parent
return (
<Wrapper>
<h1>This is heading</h1>
<h2>This is a sub-heading</h2>
<Child1>
<Child2>
<Child3>
<Child4/>
</Child3>
</Child2>
</Child1>
</Wrapper>
)

这将删除不必要的 div 标签,从而防止出现 div soup

使用 React Fragments

在每个 React 项目中引入这个 Wrapper 组件是很困难的,也是一种额外的耗费,开发者正是要避免这种情况。

React Fragments 的简介。

根据官方文档的说明:

React 中一个常见的模式是一个组件返回多个元素。Fragments 可以对子元素列表进行分组,而无需向 DOM 添加额外的节点。

-- ReactJs.org[7]

可以通过两种方式来实现:

1.使用 React.Fragment[8]2.使用 React.Fragment 的简写语法[9],即 <> 和 </>

通过代码示例来说明:

// 使用 React.Fragment 包裹 Parent 组件
return (
<React.Fragment>
<h1>This is heading</h1>
<h2>This is a sub-heading</h2>
<Child1>
<Child2>
<Child3>
<Child4/>
</Child3>
</Child2>
</Child1>
</React.Fragment>
)

使用使用 React.Fragment 简写语法会更好。

// 使用 React.Fragment 简写语法的 Parent 组件
return (
<>
<h1>This is heading</h1>
<h2>This is a sub-heading</h2>
<Child1>
<Child2>
<Child3>
<Child4/>
</Child3>
</Child2>
</Child1>

</>
)

最终的代码将是这样:

//  使用 React.Fragment 简写语法的 Children 组件
const Child1 = (props) => (
<>
<h3>I am child 1</h3>
{/* 在 <Child1> 和 </Child1> 之间传递的任何内容 */}
{props.children}
</>
);
const Child2 = (props) => (
<>
<h3>I am child 2</h3>
{props.children}
</>
);
const Child3 = (props) => (
<>
<h3>I am child 3</h3>
{props.children}
</>
);
const Child4 = () => (
<>
<h3>I am child 4</h3>
</>
);

这将帮助你获得同样的结果,避免了 div soup