## 1 引言

Flex 与 Grid 相比就像功能键盘和触摸屏。触摸屏的控制力相比功能键盘来说就像是降维打击，因为功能键盘只能上下左右控制（x、y 轴），而触摸屏打破了布局障碍，直接从（z 轴）触达，这样 **无论 UI 内部布局再复杂，都可以通过 touch 直接定位。**

Flex 是一维布局方式，我们需要不断嵌套 Div 才能形成复杂结构，而一旦布局产生了变化，原有嵌套结构如果不能 “兼容变化” 到新结构，代码就需要重构。而 Grid 就像触摸屏一样，可以二维布局，即便布局方式做了翻天覆地的调整，也仅需少量修改就能适配。

这就是这次精读 [用 css grid 重新思考布局](https://www.freecodecamp.org/news/css-grid-changes-how-we-can-think-about-structuring-our-content/) 的原因，理解这个革命性布局技术给布局，甚至代码逻辑组织带来的变化。

## 2 概述

作者首先抛出了 Flex 的问题，其实是 `block` `float` `flex` 这三种布局模式的通病：

- 布局结构由 Div 层级结构描述，导致 Div 层级复杂且遇到结构变更时难以维护。
- 定制能力弱。Flex 布局有一些不受控制的智能设定，比如宽度 50% 的子元素会被同级元素挤到 50% 以下，这种智能化在某些场景是需要的，但由于没有提供像 Grid 的 `minmax` 之类的 API，所以定制型不足。

![](https://img.alicdn.com/tfs/TB1X8Wvi4D1gK0jSZFyXXciOVXa-608-324.png)

举个例子，上图的结构用 Flex 描述可能是这样的：

```html
<div class="card">
  <div class="profile-sidebar">
    <img src="https://i.pravatar.cc/125?image=3" alt="" class="profile-img" />
    <ul class="social-list">
      <li>
        <a href="#" class="social-link"
          ><i class="fab fa-dribbble-square"></i
        ></a>
      </li>
      <li>
        <a href="#" class="social-link"
          ><i class="fab fa-facebook-square"></i
        ></a>
      </li>
      <li>
        <a href="#" class="social-link"
          ><i class="fab fa-twitter-square"></i
        ></a>
      </li>
    </ul>
  </div>
  <div class="profile-body">
    <h2 class="profile-name">Ramsey Harper</h2>
    <p class="profile-position">Graphic Designer</p>
    <p class="profile-info">
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere a tempore,
      dignissimos odit accusantium repellat quidem, sit molestias dolorum
      placeat quas debitis ipsum esse rerum?
    </p>
  </div>
</div>
```

利用 HTML 嵌套结构，我们将图形纵向分成两大块，然后在每块内部继续嵌套划分布局，这是最经典的布局行为了。

![](https://img.alicdn.com/tfs/TB17_Oqi2b2gK0jSZK9XXaEgFXa-608-324.jpg)

样式文件里，我们需要对每层布局进行描述，同时支持多分辨率弹性布局，包括顶层 `card` 容器在内的一些样式需要做一定调整：

```scss
.card {
  width: 80%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  max-width: 600px;
  background: #005e9b;
  flex-basis: 250px;
  color: white;
  padding: 2em;
  text-align: center;
}

.profile-info {
  font-weight: 300;
  opacity: 0.7;
}

.profile-sidebar {
  margin-right: 2em;
  text-align: center;
}

.profile-name {
  letter-spacing: 1px;
  font-size: 2rem;
  margin: 0.75em 0 0;
  line-height: 1;
}

.profile-name::after {
  content: "";
  display: block;
  width: 2em;
  height: 1px;
  background: #5bcbf0;
  margin: 0.5em auto 0.65em;
  opacity: 0.25;
}

.profile-position {
  text-transform: uppercase;
  font-size: 0.875rem;
  letter-spacing: 3px;
  margin: 0 0 2em;
  line-height: 1;
  color: #5bcbf0;
}

.profile-img {
  max-width: 100%;
  border-radius: 50%;
  border: 2px solid white;
}

.social-list {
  list-style: none;
  justify-content: space-evenly;
  display: flex;
  min-width: 125px;
  max-width: 175px;
  margin: 0 auto;
  padding: 0;
}

.social-link {
  color: #5bcbf0;
  opacity: 0.5;
}

.social-link:hover,
.social-link:focus {
  opacity: 1;
}

.bio {
  padding: 2em;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@media (min-width: 450px) {
  .bio {
    text-align: left;
    max-width: 350px;
  }
}

.bio-title {
  color: #0090d1;
  font-size: 1.25rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  line-height: 1;
  margin: 0;
}

.bio-body {
  color: #555;
}

.profile {
  display: flex;
  align-items: flex-start;
}

@media (min-width: 450px) {
  .card {
    flex-direction: row;
    text-align: left;
  }

  .profile-name::after {
    margin-left: 0;
  }
}
```

让我们看看 Grid 是怎么做的吧！Grid 有许多 API，我们重点看 `grid-template-areas` 这个属性，利用它，我们可以不关心模块的 HTML 结构，直接平铺方式描述：

```html
<div class="card">
  <img src="https://i.pravatar.cc/125?image=3" alt="" class="profile-img" />
  <ul class="social-list">
    <li>
      <a href="#" class="social-link"><i class="fab fa-dribbble-square"></i></a>
    </li>
    <li>
      <a href="#" class="social-link"><i class="fab fa-facebook-square"></i></a>
    </li>
    <li>
      <a href="#" class="social-link"><i class="fab fa-twitter-square"></i></a>
    </li>
  </ul>
  <h2 class="profile-name">Ramsey Harper</h2>
  <p class="profile-position">Graphic Designer</p>
  <p class="profile-info">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere a tempore,
    dignissimos odit accusantium repellat quidem, sit molestias dolorum placeat
    quas debitis ipsum esse rerum?
  </p>
</div>
```

可以看到，使用 Grid 可以将 UI 结构与 HTML 结构分离，HTML 结构仅描述包含关系，我们只需在样式文件中描述具体 UI 结构。

样式文件只截取 Grid 相关部分：

```scss
.card {
  width: 80%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  max-width: 600px;
  background: #005e9b;
  flex-basis: 250px;
  color: white;
  padding: 2em;
  text-align: left;

  display: grid;
  grid-template-columns: 1fr 3fr;
  grid-column-gap: 2em;
  grid-template-areas:
    "image name"
    "image position"
    "social description";
}

.profile-name {
  grid-area: name;
}
.profile-position {
  grid-area: position;
}
.profile-info {
  grid-area: description;
}
.profile-img {
  grid-area: image;
}
.social-list {
  grid-area: social;
}
```

可以看到，`grid-template-areas` 是进一步抽象的语法，将页面结构通过直观的文本描述，无论是理解还是修改都更为轻松。

这种描述方式适配不同分辨率下也具有优势，只要重组 `grid-template-areas` 即可：

```scss
@media (min-width: 600px) {
  .card {
    text-align: left;
    grid-template-columns: 1fr 3fr;
    grid-template-areas:
      "image name"
      "image position"
      "social description";
  }
}
```

归根结底，Grid 通过二维结构描述，将子元素布局控制收到了父级，使布局描述更加直观。

最后作者也提到，Flex 依然有使用场景，即简单的一维结构，或者 `space-between` 等 Flex 独有语法的情况。因此推荐整体、复杂的二维布局采用 Grid，一维的简单布局采用 Flex。

## 3 精读

Grid 的布局思路给了我很多启发，HTML 结构与 UI 结构的分离有助于减少 DIV 的层级结构，使代码看上去更清晰。

也许有人会疑惑，Grid 无非将 HTML 布局部分功能挪到了 CSS，整体复杂度应该不变。其实，从 `grid-template-areas` 这个 API 可以看到，Grid 不仅仅将布局功能抽到 CSS 中，更是将布局描述进行了一层抽象，使代码更易维护。

### 抽象，再抽象

为什么 Grid 可以对布局进行抽象？因为 Grid 将二维结构都掌握在手中，得到了更大的布局能力，才能进一步将结构化语法抽象为字符串的描述。

抽象的好处是不言而喻的，你觉得一堆嵌套的 DIV 与下面的代码，哪个更易读呢？

```scss
.card {
  grid-template-areas:
    "image name"
    "image position"
    "social description";
}
```

这就是抽象的好处，一般来说，代码抽象程度越高就越易读，越易维护。

再看一个 Chrome Grid 插件，将 Grid 可视化显示出来，并可以以 UI 方式进行调整：

![](https://img.alicdn.com/tfs/TB1cAmui2b2gK0jSZK9XXaEgFXa-640-400.jpg)

UI 是对文本的再抽象，同时可以规避一些不可能存在的语法，比如：

```scss
.card {
  grid-template-areas:
    "image name"
    "image position"
    "social image";
}
```

布局只能以凸多边形方式拓展，不可能分离，也不可能突然插入一个其他模块而变成凹多边形。因此 UI 可以将这个错误规避，并简化为横竖多条线的方式对 UI 进行划分，显然这种描述方式效率更高。

不得不说，Grid 以及图形化插件的探索，是布局领域的一大进步，是不断抽象的尝试，要解决的问题只有一个：如何提供一种更直观的描述 UI 的方式。

### 布局对模块化的影响

Grid 将布局方式提高了一个维度，会直接影响到 JS 模块化方式。

尤其是以 JSX 组织代码的情况下，一个模块等于 UI + JS，通过嵌套方式的布局会让我们更倾向于站在 UI 视角划分模块。

![](https://img.alicdn.com/tfs/TB1WQCvi.Y1gK0jSZFMXXaWcVXa-1052-750.png)

比如对于上图模块，如果用 Flex 方式布局，我们可能会首先创建模块 X 作为左侧容器，子元素是 A 和 B，创建模块 Y 作为右侧容器，子元素是 C 以及新容器 Z，Z 容器的子元素是 D 和 E。

如果你的第一印象是这么组织代码，不得不承认模块化会受到布局方式的影响。虽然许多时候这样划分是正确的，但当这 5 个模块各自没有关联时，我们创建的容器 X、Y、Z 就失去了复用性，在新的组合场景我们又要重新组合一遍。

但是在 Grid 语法中，我们不需要 X、Y、Z，只需要用 [css grid generator](https://cssgrid-generator.netlify.com/) 按照上图的方式拖拖拽拽即可自动生成如下布局代码：

```scss
.parent {
  display: grid;
  grid-template-columns: 3fr repeat(2, 1fr);
  grid-template-rows: repeat(5, 1fr);
  grid-column-gap: 0px;
  grid-row-gap: 0px;
}

.div1 {
  grid-area: 1 / 1 / 3 / 2;
}
.div2 {
  grid-area: 3 / 1 / 6 / 2;
}
.div3 {
  grid-area: 1 / 2 / 2 / 4;
}
.div4 {
  grid-area: 2 / 2 / 6 / 3;
}
.div5 {
  grid-area: 2 / 3 / 6 / 4;
}
```

其实 `grid-template-columns` `grid-template-rows` 组合起来使用比 `grid-template-areas` 更强大，但是纯代码方式描述没有 `grid-template-areas` 直观，可是配合一些可视化系统就非常直观了：

![](https://img.alicdn.com/tfs/TB1E.9AiYj1gK0jSZFuXXcrHpXa-2006-1470.png)

将 A ～ E 这 5 个模块布局抽出来后，它们之间的关系就打平了，我们可以完全从逻辑视角审视如何做模块化了。

## 4 总结

CSS Grid 本质上是一种二维布局的语法，相比 [Block](https://www.w3schools.com/Css/css_inline-block.asp)、[Flex](https://www.w3schools.com/Css/css3_flexbox.asp) 等一维布局方案，多了一个维度可以同时从行与列角度定义布局，因此派生出 `grid-template-areas` 等语法，整体上更内聚更直观，抽象度也更高了。

理解了这些也就理解了布局未来的发展方向，**让布局与 Dom 分离** 一直是前端的一个梦想，开发 UI 部分时，只需关心页面由哪些模块组成，去实现这些模块就行了，而不需要关心模块之间应该如何组合。在描述组合时，可以通过可视化或比较抽象的字符串描述布局的结构，并对应到写好的模块上，这样的代码维护性远高于用 DIV 描述结构的方案。

> 讨论地址是：[精读《用 css grid 重新思考布局》 · Issue #211 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/211)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
