---
title: 用 CSS in JS 写组件
---

上一章节中，我们学习了如何使用 UI 组件库、标准三方库中的组件。

这一章节中，我们将学习如何实现 UI 组件。

首先我们希望自己控制联系人头像的展示，实现这种设计稿：

![design](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/06/design1.png)

假设没有现成的组件可以实现，那就需要自己写些 CSS 了，传统上，我们有如下选择：

1. 直接在元素的 style 属性上写样式，缺点是：不方便维护，UI 视觉上的细节也会跟 UI 结构上的细节和业务逻辑混在一起。
2. 在 CSS 代码里用选择器找到这个头像元素，写样式，避免了 1 的缺点，但新的缺点是：不方便在其他有头像出现的地方复用，集中维护，做到 [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself)。
3. 在 CSS 代码里写一个 classname，封装这个样式，避免了 2 的缺点，但新的缺点是：需要考虑命名问题，避免在全局命名空间下重名，可能要用到 [BEM](http://getbem.com/) 之类的规范。
4. 用 CSS Modules 技术，让 CSS 文件和其中的 classname 变得【 模块化 】，避免了 3 的缺点。

Modern.js 开箱即用的支持 CSS Modules，但我们更推荐优先采用 CSS Modules 的继承者、在【 模块化 】上更进一步的 [styled-components](https://styled-components.com/)，来实现类似的需求。

Modern.js 同样开箱即用的支持 styled-components，既不需要安装依赖，也不需要做任何配置。

在 `src/App.tsx` 里修改顶部的代码：

```js
import styled from '@modern-js/runtime/styled';
```

添加以下代码：

```js
const Avatar = styled.img`
  width: 50px;
  height: 50px;
  border: 4px solid #0ef;
  border-radius: 50%;
`;
```

修改 `List.Item.Meta` 的代码：

```tsx
<List.Item.Meta
  avatar={<Avatar src={avatar} />}
  title={name}
  description={email}
/>
```

执行 `pnpm run dev`，可以看到预期的运行结果：

![result](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/docs/06/result1.png)

接下来我们做一点重构，为了增强可读性，让代码更容易维护，可以把 Avatar 组件拆分出去

在终端执行以下命令：

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<Tabs>
<TabItem value="macOS" label="macOS" default>

```bash
mkdir -p src/components/Avatar
touch src/components/Avatar/index.tsx
```

</TabItem>
<TabItem value="Windows" label="Windows">

```powershell
mkdir -p src/components/Avatar
ni src/components/Avatar/index.tsx
```

</TabItem>
</Tabs>

把 `src/App.tsx` 里的 Avatar 实现删掉，改成：

```js
import Avatar from './components/Avatar';
```

`src/components/Avatar/index.tsx` 的内容：

```js
import styled from '@modern-js/runtime/styled';

const Avatar = styled.img`
  width: 50px;
  height: 50px;
  border: 4px solid #0ef;
  border-radius: 50%;
`;

export default Avatar;
```

执行 `pnpm run dev`，运行结果应该是一样的。

:::info 注
采用目录形式（Avatar/index.tsx）而不是单文件形式（Avatar.tsx）的原因是，之后可以方便在目录内部增加子文件，包括专用的资源（图片等）、专用子组件、CSS 文件等，在这个**黑盒**内部可以随意重构，只考虑**最小局部**。
:::

---

> 本小节的代码可以在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/tutorials/c06/hello-modern)。
