整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

2021 年你需要知道的 CSS 工程化技术

家好,我是皮汤。最近业务调整,组内开启了前端工程化方面的基建,我主要负责 CSS 技术选型这一块,针对目前业界主流的几套方案进行了比较完善的调研与比较,分享给大家。

目前整个 CSS 工具链、工程化领域的主要方案如下:

而我们技术选型的标准如下:

- 开发速度快

- 开发体验友好

- 调试体验友好

- 可维护性友好

- 扩展性友好

- 可协作性友好

- 体积小

- 有最佳实践指导

目前主要需要对比的三套方案:

- Less/Sass + PostCSS 的纯 CSS c侧方案

- styled-components / emotion 的纯 CSS-in-JS 侧方案

- TailwindCSS 的以写辅助类为主的 HTML 侧方案

## 纯 CSS 侧方案

### 介绍与优点

> 维护状态:一般

> Star 数:16.7K

> 支持框架:无框架限制

> 项目地址:https://github.com/less/less.js

Less/Sass + PostCSS 这种方案在目前主流的组件库和企业级项目中使用很广,如 ant-design 等

它们的主要作用如下:

- 为 CSS 添加了类似 JS 的特性,你也可以使用变量、mixin,写判断等

- 引入了模块化的概念,可以在一个 less 文件中导入另外一个 less 文件进行使用

- 兼容标准,可以快速使用 CSS 新特性,兼容浏览器 CSS 差异等

这类工具能够与主流的工程化工具一起使用,如 Webpack,提供对应的 loader 如 sass-loader,然后就可以在 React/Vue 项目中建 `.scss` 文件,写 sass 语法,并导入到 React 组件中生效。

比如我写一个组件在响应式各个断点下的展示情况的 sass 代码:

```

.component {

width: 300px;

@media (min-width: 768px) {

width: 600px;

@media (min-resolution: 192dpi) {

background-image: url(/img/retina2x.png);

}

}

@media (min-width: 1280px) {

width: 800px;

}

}

```

或导入一些用于标准化浏览器差异的代码:

```

@import "normalize.css";

// component 相关的其他代码

```

### 不足

这类方案的一个主要问题就是,只是对 CSS 本身进行了增强,但是在帮助开发者如何写更好的 CSS、更高效、可维护的 CSS 方面并没有提供任何建议。

- 你依然需要自己定义 CSS 类、id,并且思考如何去用这些类、id 进行组合去描述 HTML 的样式

- 你依然可能会写很多冗余的 Less/Sass 代码,然后造成项目的负担,在可维护性方面也有巨大问题

### 优化

- 可以引入 CSS 设计规范:BEM 规范,来辅助用户在整个网页的 HTML 骨架以及对应的类上进行设计

- 可以引入 CSS Modules,将 CSS 文件进行 “作用域” 限制,确保在之后维护时,修改一个内容不会引起全局中其他样式的效果

#### BEM 规范

B (Block)、E(Element)、M(Modifier),具体就是通过块、元素、行为来定义所有的可视化功能。

拿设计一个 Button 为例:

```

/* Block */

.btn {}

/* 依赖于 Block 的 Element */

.btn__price {}

/* 修改 Block 风格的 Modifier */

.btn--orange {}

.btn--big {}

```

遵循上述规范的一个真实的 Button:

```

<a class="btn btn--big btn--orange" href="#">

<span class="btn__price"></span>

<span class="btn__text">BIG BUTTON</span>

</a>

```

可以获得如下的效果:

#### CSS Modules

CSS Modules 主要为 CSS 添加局部作用域和模块依赖,使得 CSS 也能具有组件化。

一个例子如下:

```

import React from 'react';

import style from './App.css';

export default () => {

return (

<h1 className={style.title}>

Hello World

</h1>

);

};

```

```

.title {

composes: className;

color: red;

}

```

上述经过编译会变成如下 hash 字符串:

```

<h1 class="_3zyde4l1yATCOkgn-DBWEL">

Hello World

</h1>

```

```

._3zyde4l1yATCOkgn-DBWEL {

color: red;

}

```

CSS Modules 可以与普通 CSS、Less、Sass 等结合使用。

## 纯 JS 侧方案

### 介绍与优点

> 维护状态:一般

> Star 数:35.2K

> 支持框架:React ,通过社区支持 Vue 等框架

> 项目地址:https://github.com/styled-components/styled-components

使用 JS 的模板字符串函数,在 JS 里面写 CSS 代码,这带来了两个认知的改变:

- 不是在根据 HTML,然后去写 CSS,而是站在组件设计的角度,为组件写 CSS,然后应用组件的组合思想搭建大应用

- 自动提供类似 CSS Modules 的体验,不用担心样式的全局污染问题

同时带来了很多 JS 侧才有的各种功能特性,可以让开发者用开发 JS 的方式开发 CSS,如编辑器自动补全、Lint、编译压缩等。

比如我写一个按钮:

```

const Button = styled.button`

/* Adapt the colors based on primary prop */

background: ${props => props.primary ? "palevioletred" : "white"};

color: ${props => props.primary ? "white" : "palevioletred"};

font-size: 1em;

margin: 1em;

padding: 0.25em 1em;

border: 2px solid palevioletred;

border-radius: 3px;

`;

render(

<div>

<Button>Normal</Button>

<Button primary>Primary</Button>

</div>

);

```

可以获得如下效果:

还可以扩展样式:

```

// The Button from the last section without the interpolations

const Button = styled.button`

color: palevioletred;

font-size: 1em;

margin: 1em;

padding: 0.25em 1em;

border: 2px solid palevioletred;

border-radius: 3px;

`;

// A new component based on Button, but with some override styles

const TomatoButton = styled(Button)`

color: tomato;

border-color: tomato;

`;

render(

<div>

<Button>Normal Button</Button>

<TomatoButton>Tomato Button</TomatoButton>

</div>

);

```

可以获得如下效果:

### 不足

虽然这类方案提供了在 JS 中写 CSS,充分利用 JS 的插值、组合等特性,然后应用 React 组件等组合思想,将组件与 CSS 进行细粒度绑定,让 CSS 跟随着组件一同进行组件化开发,同时提供和组件类似的模块化特性,相比 Less/Sass 这一套,可以复用 JS 社区的最佳实践等。

但是它仍然有一些不足:

- 仍然是是对 CSS 增强,提供非常大的灵活性,开发者仍然需要考虑如何去组织自己的 CSS

- 没有给出一套 “有观点” 的最佳实践做法

- 在上层也缺乏基于 styled-components 进行复用的物料库可进行参考设计和使用,导致在初始化使用时开发速度较低

- 在 JS 中写 CSS,势必带来一些本属于 JS 的限制,如 TS 下,需要对 Styled 的组件进行类型注释

- 官方维护的内容只兼容 React 框架,Vue 和其他框架都由社区提供支持

整体来说不太符合团队协作使用,需要人为总结最佳实践和规范等。

### 优化

- 寻求一套写 CSS 的最佳实践和团队协作规范

- 能够拥有大量的物料库或辅助类等,提高开发效率,快速完成应用开发

## 偏向 HTML 侧方案

### 介绍与优点

> 维护状态:积极

> Star 数:48.9K

> 支持框架:React、Vue、Svelte 等主流框架

> 项目地址:https://github.com/tailwindlabs/tailwindcss

典型的是 TailwindCSS,一个辅助类优先的 CSS 框架,提供如 `flex` 、`pt-4` 、`text-center` 、`rotate-90` 这样实用的类名,然后基于这些底层的辅助类向上组合构建任何网站,而且只需要专注于为 HTML 设置类名即可。

一个比较形象的例子可以参考如下代码:

```

<button class="btn btn--secondary">Decline</button>

<button class="btn btn--primary">Accept</button>

```

上述代码应用 BEM 风格的类名设计,然后设计两个按钮,而这两个类名类似主流组件库里面的 Button 的不同状态的设计,而这两个类又是由更加基础的 TailwindCSS 辅助类组成:

```

.btn {

@apply text-base font-medium rounded-lg p-3;

}

.btn--primary {

@apply bg-rose-500 text-white;

}

.btn--secondary {

@apply bg-gray-100 text-black;

}

```

上面的辅助类包含以下几类:

- 设置文本相关: `text-base` 、`font-medium` 、`text-white` 、`text-black`

- 设置背景相关的:`bg-rose-500` 、`bg-gray-100`

- 设置间距相关的:`p-3`

- 设置边角相关的:`rounded-lg`

通过 Tailwind 提供的 `@apply` 方法来对这些辅助类进行组合构建更上层的样式类。

上述的最终效果展示如下:

可以看到 TailwindCSS 将我们开发网站的过程抽象成为使用 Figma 等设计软件设计界面的过程,同时提供了一套用于设计的规范,相当于内置最佳实践,如颜色、阴影、字体相关的内容,一个很形象的图片可以说明这一点:

TailwindCSS 为我们规划了一个元素可以设置的属性,并且为每个属性给定了一组可以设置的值,这些属性+属性值组合成一个有机的设计系统,非常便于团队协作与共识,让我们开发网站就像做设计一样简单、快速,但是整体风格又能保持一致。

TailwindCSS 同时也能与主流组件库如 React、Vue、Svelte 结合,融入基于组件的 CSS 设计思想,但又只需要修改 HTML 上的类名,如我们设计一个食谱组件:

```

// Recipes.js

import Nav from './Nav.js'

import NavItem from './NavItem.js'

import List from './List.js'

import ListItem from './ListItem.js'

export default function Recipes({ recipes }) {

return (

<div className="divide-y divide-gray-100">

<Nav>

<NavItem href="/featured" isActive>Featured</NavItem>

<NavItem href="/popular">Popular</NavItem>

<NavItem href="/recent">Recent</NavItem>

</Nav>

<List>

{recipes.map((recipe) => (

<ListItem key={recipe.id} recipe={recipe} />

))}

</List>

</div>

)

}

// Nav.js

export default function Nav({ children }) {

return (

<nav className="p-4">

<ul className="flex space-x-2">

{children}

</ul>

</nav>

)

}

// NavItem.js

export default function NavItem({ href, isActive, children }) {

return (

<li>

<a

href={href}

className={`block px-4 py-2 rounded-md ${isActive ? 'bg-amber-100 text-amber-700' : ''}`}

>

{children}

</a>

</li>

)

}

// List.js

export default function List({ children }) {

return (

<ul className="divide-y divide-gray-100">

{children}

</ul>

)

}

//ListItem.js

export default function ListItem({ recipe }) {

return (

<article className="p-4 flex space-x-4">

<img src={recipe.image} alt="" className="flex-none w-18 h-18 rounded-lg object-cover bg-gray-100" width="144" height="144" />

<div className="min-w-0 relative flex-auto sm:pr-20 lg:pr-0 xl:pr-20">

<h2 className="text-lg font-semibold text-black mb-0.5">

{recipe.title}

</h2>

<dl className="flex flex-wrap text-sm font-medium whitespace-pre">

<div>

<dt className="sr-only">Time</dt>

<dd>

<abbr title={`${recipe.time} minutes`}>{recipe.time}m</abbr>

</dd>

</div>

<div>

<dt className="sr-only">Difficulty</dt>

<dd> · {recipe.difficulty}</dd>

</div>

<div>

<dt className="sr-only">Servings</dt>

<dd> · {recipe.servings} servings</dd>

</div>

<div className="flex-none w-full mt-0.5 font-normal">

<dt className="inline">By</dt>{' '}

<dd className="inline text-black">{recipe.author}</dd>

</div>

<div class="absolute top-0 right-0 rounded-full bg-amber-50 text-amber-900 px-2 py-0.5 hidden sm:flex lg:hidden xl:flex items-center space-x-1">

<dt className="text-amber-500">

<span className="sr-only">Rating</span>

<svg width="16" height="20" fill="currentColor">

<path d="M7.05 3.691c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.372 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.539 1.118l-2.8-2.034a1 1 0 00-1.176 0l-2.8 2.034c-.783.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.363-1.118L.98 9.483c-.784-.57-.381-1.81.587-1.81H5.03a1 1 0 00.95-.69L7.05 3.69z" />

</svg>

</dt>

<dd>{recipe.rating}</dd>

</div>

</dl>

</div>

</article>

)

}

```

上述食谱的效果如下:

可以看到我们无需写一行 CSS,而是在 HTML 里面应用各种辅助类,结合 React 的组件化设计,既可以轻松完成一个非常现代化且好看的食谱组件。

除了上面的特性,TailwindCSS 在响应式、新特性支持、Dark Mode、自定义配置、自定义新的辅助类、IDE 方面也提供非常优秀的支持,除此之外还有基于 TailwindCSS 构建的物料库 Tailwind UI ,提供各种各样成熟、好看、可用于生产的物料库:

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1cb983da6f71470b91ac764d14907998~tplv-k3u1fbpfcp-zoom-1.image)

因为需要自定的 CSS 不多,而需要自定义的 CSS 可以定义为可复用的辅助类,所以在可维护性方面也是极好的。

### 不足

- 因为要引入一个额外的运行时,TailwindCSS 辅助类到 CSS 的编译过程,而随着组件越来越多,需要编译的工作量也会变大,所以速度会有影响

- 过于底层,相当于给了用于设计的最基础的指标,但是如果我们想要快速设计网站,那么可能还需要一致的、更加上层的组件库

- 相当于引入了一套框架,具有一定的学习成本和使用成本

### 优化

- Tailwind 2.0 支持 [JIT](https://blog.tailwindcss.com/tailwindcss-2-1 "JIT"),可以大大提升编译速度,可以考虑引入

- 基于 TailwindCSS,设计一套符合自身风格的上层组件库、物料库,便于更加快速开发

- 提前探索、学习和总结一套教程与开发最佳实践

- 探索 styled-components 等结合 TailwindCSS 的开发方式

## 参考链接

- [CSS 工程化发展历程](https://bytedance.feishu.cn/docs/doccnTRF0OZtJMgKuo3y0hIDMbc# "CSS 工程化发展历程")


/ 感谢支持/

以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了 分享、点赞、收藏 三连哦~

欢迎关注公众号 程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程经验、技术干货与职业规划,助你少走弯路进大厂。

很长的一段时间中,Vue 官方都以简单上手作为其推广的重点。这确实给 Vue 带来了非常大的用户量,尤其是最追求需求开发效率, 往往不那么在意工程代码质量的国内中小企业中,Vue 占据的份额极速增长。但是作为开发者自身,我们必须要认清一个重点,简单易用从来不应该在技术选型中占据很大的份额,可维护性才是。

以防万一有的同学实在不看官方文档,我先提一嘴,SFC 就是写 Vue 组件的时候写的.vue文件,这一个文件就是一个 SFC,全称 Single File Component,也即单文件组件。

在开始说我个人的观点之前,我们先来看几个事实:

一是:Vue3 的定义原生支持 JSX,并且 Vue3 源码中有jsx.d.ts来便于使用 JSX。 不知道同学们看到这里会想到什么, 我的第一反应是:社区对于 JSX 的需求声音是不小的,所以会反向推动 Vue3 官方对于 JSX 的支持。

二是:AntDesign 的 vue3 版本,基本全部都是用 JSX 开发的,而且 Vue3 现在官方的 babel-jsx 插件就是阿里的人一开始维护的, 虽然我向来不喜欢阿里系的 KPI 推动技术方式,而且现在的 JSX 语法支持也不是很符合我的期望,但至少在使用 JSX 开发是更优秀的选择这点上,我还是很认可 AntDesign 团队的。

OK,说这些呢,主要是先摆出一些事实作为依据,让有些同学可以不需要拿什么:

  • 啊,这都是你空想的,你太自以为是了
  • 你再怎么想都没用,咱们 Vue 就是应该用 SFC 开发

这些观点来批斗我,首先我都会从客观的角度来分析为什么,至少是我是能讲出优劣势的理由的。

OK,前言差不多到这里,接下来咱给您分析分析,为什么你应该选择 JSX 来开发 Vue。


TypeScript 支持

其实第一点就已经是杀手了,对于想要使用 TypeScript 来开发 Vue3 应用的同学来说,这简直就是 SFC 无法克服的世界难题。

一句话概括:TypeScript 原生支持 JSX 语法,而基本无望 TS 官方能支持 SFC 的 template 语法

TS 毫无疑问在前端社区的重要性越来越大,但凡未来对于代码质量有一定要求的前端团队,都应该会选择使用 TS 来进行开发。 而且现在基本上在 NPM 上都能看到包你都能找到对应的 TS 定义,现在使用 TS 开发成本已经只剩下你是不是会 TS 语法了,在这种情况下是否支持 TS 则是开发模式在未来走不走的远的重要原因。

目前 SFC 只能通过shim让 TS 可以引入.vue文件,但是对于所有 SFC 的组件的定义都是一样的:

declare module '*.vue' {
    import { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, {}, any>
    export default component
}

也就是说你引入的 SFC 组件,TS 是不知道这个组件的 Props 应该接收什么的。所以你无法享受到这些 TS 的优势:

  • 开发时的自动提示
  • 编译时的 TS 校验,让你尽早发现问题
  • 编译组件生成你的组件定义(对于类库开发尤其重要)

当然你会说既然 Vue 官方能开发处 SFC 的语法,自然会支持这些特性。我表示这当然有可能,但是这个难度是非常大的,需要很多方面的支持,甚至可能需要 TS 官方团队愿意协助, 但是我想不到 TS 官方有什么理由来支持 SFC,因为这只是 Vue 自己创建的方言,在其他场景下是没有使用的,TS 是面向全社区的,我觉得他们不会考虑主动来支持 SFC。

那么有同学要问了,JSX 不也是非原生的 JS 语法么,他怎么就能让 TS 官方支持了呢,是不是 FB 和微硬之间有什么 PY 交易?

这就涉及第二点了,JSX 和静态模板的灵活性区别。

JSX 其实并不是方言

很多人弄错了一个问题,就是觉得 SFC 的模板语法和 JSX 是一样的,都是一种别人发明的语法,并不是 JS 原生的。这是事实,但又有一些区别,这个区别主要是体现在对于 JSX 的认知上。

一句话概括:JSX 并没有扩展 JS 的语法,他只是缩略了 JS 的写法!其本质就是 JS 的语法糖

就像 es6 给增加的语法糖,比如

const a = 1
const b = 2

const obj = { a, b }

// 其实就等价于
const obj = { a: a, b: b }

这种写法并没有扩展 JS 的能力,只是简便了写法,JSX 也是一样的。

JSX 其实就是方法调用,他和 JS 是有一对一对应关系的,我们来看一个例子:

const element = <div id="root">Hello World</div>

这里的 JSX 语法编译之后其实就是:

const element = createElement('div', { id: 'root' }, 'Hello World')

而 JSX 就是这些了,没有什么更多的内容,所以说 JSX 只是方便我们写嵌套的函数调用的语法糖,而其本身没有扩展任何其他的内容。

但是 SFC 就不一样了。

SFC 定义的不仅是语法,更是文件。

SFC 的具体定义是单文件组件,它本身就是把一个文件看作一个单位,所以他的约束性是要大很多的,你必须具有固定的文件结构才能使用 SFC,这做了很多的限制:

  • 一个文件只能写一个组件
  • 节点片段只能写在 template 里面,非常不灵活
  • 变量绑定只能获取this上面的内容,不能使用全局变量(很多时候我们都要把全局变量先挂载到this上)

我们一点点来讲

一个文件只能写一个组件

这个说实话非常非常不方便,很多时候我们写一个页面的时候其实经常会需要把一些小的节点片段拆分到小组件里面进行复用(如果你现在没有这个习惯可能就是因为 SFC 的限制让你习惯了全部写在一个文件内)。

React 生态中丰富的 css-in-js 方案就是很好的例子,我们可以通过:

const StyledButton = styled('button', {
    color: 'red',
})

如果我们这个页面需要使用特定样式的按钮,通过这种方式在页面文件里面封装一下是非常常见的。因为没必要把这个组件拆分出去,他也不是一个可复用的组件,拆分出去了还要多一次import。

Vue 生态基本没有 css-in-js 的成熟方案其实跟这个限制也很有关系。

再来一个例子,比如我们封装了一个 Input 组件,我们希望同时导出 Password 组件和 Textarea 组件来方便用户根据实际需求使用,而这两个组件本身内部就是用的 Input 组件,只是定制了一些 props:

const Input = { ... }

export default Input

export const Textarea = (props) => <Input multiline={true} {...props} />

export const Password = (props) => <Input type="password" {...props} />

在 JSX 中可以非常简单地实现,但是如果通过 SFC,你可能就要强行拆成三个文件,另外为了方便,你可能还要增加一个index.js来导出这三个组件,你能想象这多了多少工作量么。

节点片段只能写在 template 里面,非常不灵活

我不知道有多少同学看过 Vue 的 template 编译出来之后的代码,以我的经验来说看过的可能不会超过 50%(乐观估计),建议同学们如果还不了解的,可以去尝试看一下。

为什么要看这个呢?因为你看了之后你会发现,你在 template 里面写的类似 HTMl 的内容,其实跟 HTML 根本没啥关系,他们也会被编译成类似 JSX 编译出来的结果。

{
    render(h) {
        return h('div', {on: {}, props: {}}, h('span'))
    }
}

类似这样的结果,而这里面h函数调用的结果就是一个 VNode,是 Vue 中的节点的基础单元。那么既然这些单元就是一个对象,其实理所当然的,他们是可以作为参数传递的。 也就是说,理论上他们是可以通过props把节点当作参数传递给其他组件的。

这个做法在 React 中非常常见,叫做renderProps,并且其非常灵活:

const Comp = () => <Layout header={<MyHeader />} footer={<MyFooter />} />

但是因为 SFC 模板的限制,我们很难在 SFC 里面的 props 上写节点:

<template>
    <Layout :header="<MyHeader/>"></Layout>
</template>

这样写是不行的,因为 SFC 定义了:header绑定接受的只能是 js 表达式,而<MyHeader/>显然不是。

因为通过 props 传递不行,所以 Vue 才发明了 slot 插槽的概念

虽然我们一直再说 Vue 简单,但是事实上ScopedSlots一度成为新手理解 Vue 的噩梦,很多同学都被这个绕来绕去的作用域整的死去活来。

我们看一个ScopedSlots的例子:

<template>
    <Comp>
        <template v-slot:scope="ctx">
            <div>{{ctx.name}}</div>
        </template>
    </Comp>
</template>

这里ctx是Comp里面的属性,通过这种方式传递出来,让我们在当前组件可以调用父组件里面的属性。这简直就是理解的噩梦,但是如果用 JSX 实现类似功能就非常简单:

<Comp scope={name => <div>{name}</div>} />

我们只是给一个叫做scope的 props 传递来一个函数,这个函数接受一个name属性,在Comp里面会调用这个函数并传入name。 简单来说我们传入的就是一个构建节点片段的函数,就是这么简单。

这就是因为 SFC 的模板的限制,导致灵活性不足,Vue 需要去创造概念,创造关键字来抹平这些能力的不足,而创造的概念自然就引入了学习成本。

所以其实我一直不认可 Vue 比 React 好学的说法的,如果你真的认真研究所有用法,并且总是尝试用最合理的方式实现功能,那么 Vue 绝对不会比 React 简单。

变量绑定只能获取this上面的内容,不能使用全局变量

这个体现在两个方面,一个是我们定义在全局的一些固定数据如果要在组件内使用的话,就要通过this挂载到组件上。

比如我们缓存了一份城市数据,这种数据基本上是不会改的,所以也没必要挂载到组件上让其能够响应式。但是在 SFC 里面这是做不到的, 因为模板的执行上下文是在编译时绑定。你在模板里面访问的变量,都会在编译时自动绑定到this上,因为模板需要编译,其本身也是字符串不具有作用域的概念。

而这在 JSX 中则不复存在:

const citys = []

const Comp = () => {
    return citys.map(c => <div>{c}</div>)
}

另外一个方面则是在组件使用上,在 SFC 中,组件必须事先注册,因为我们在模板里面写的只能是字符串而不能是具体某个组件变量。 那么模板中的组件和真实的组件对象只能通过字符串匹配来实现绑定。这带来了以下问题:

  • 多了注册组件这个步骤,增加代码量
  • 通过字符串名注册自然就会出现可能的冲突问题
  • 模板解析组件支持不同的样式,比如<MyComp>和<my-comp>,容易导致风格不一的问题

在 JSX 中则没有这些问题,因为 JSX 里面直接使用组件引用作为参数:

const Comp = {...}

const App = () => <Comp />

需要通过directive来扩展能力

其实上面能看出来,除了 SFC 本身的问题之外,Vue 使用字符串模板也会带来很多的灵活性问题。 最直接的证据,就是 Vue 使用了directive来扩展功能(当然这不是 Vue 发明的,老早的模板引擎就有类似问题)。

为什么说directive是不得已的选择呢?因为静态模板缺失逻辑处理的能力。我们拿列表循环举例,在 JS 中我们可以非常方便地通过map函数来创建列表:

const list = arr.map(name => <span key={name}>{name}</span>)

而因为 JSX 本身就是函数调用,所以上面的代码和 JSX 结合起来也非常自然:

const App = () => (
    <div>
        <Header />
        {arr.map(name => (
            <span key={name}>{name}</span>
        ))}
    </div>
)

上面的例子对应到 JS 如下:

const App = () =>
    createElement('div', {}, [
        <Header />,
        arr.map(name => createElement('span', { key: name }, name)),
    ])

这仍然是因为 JSX 只是 JS 的语法糖的原因,所有能在 JS 中实现的在 JSX 里面都能实现。

而 SFC 的模板是基于字符串编译的,其本身就是一段字符串,我们不能直接在模板里面写map来循环节点,(当然我们可以在可以接收表达式的地方写,比如v-on里面)。

那么我们不能循环节点,有需要这样的功能来渲染列表,怎么办呢?就是发明一个标志来告诉编译器这里需要循环,在 Vue 中的体现就是v-for指令。

同学们可能要问了,既然 Vue 能实现v-for,为什么不直接实现表达式循环列表呢?他当然也可以实现,但是他肯定不会这么选,因为成本太高了。 他要这么做就相当于他要实现一个 JS 引擎,而其实里面很多内容又是不必须的,一个v-for其实就能够适用大部分情况了。

但有了v-for就需要v-if,那么后面还会需要其他各种能力,这就是一种方言的产生和发展的过程。

当然指令也不仅仅是 JS 表达式的代替品,其本身也是增加了一些其他能力的,比如它能够让我们更方便地访问 DOM 节点, 但是嘛,我们用框架的理由不就是为了能够尽可能的屏蔽 DOM 操作嘛~

总结

以上就是我对应该选择使用 JSX 还是 SFC 进行开发的分析,其实归根到底 SFC 的问题在于其没有拥抱 JS, 他的语法是自己发明的,他需要有一个 JS 实现的 compiler 来让其最终能在 JS 环境中运行,这本质上就是一种发明, 我们不能否认发明确实有优点,但我们也不能只看有点不看问题,没能拥抱 JS 自然就很难完全复用 JS 社区的优势 而 JS 社区一直在蓬勃发展,好用的工具一直在涌现,而 SFC 想要使用 JS 社区的这些工具还要自己再实现一份,我们可以细数以下 SFC 做了哪些兼容

  • vue-loader 之于 webpack
  • eslint-plugin-vue 之于 eslint
  • rollup-plugin-vue 之于 rollup
  • vue-jest 之于 jest
  • Vetur 用来做代码提醒

基本上常用的工具我们都需要等待 Vue 社区或者官方开发了插件之后才能运行。而 JSX 因为有 babel 和 typescript 的官方支持, 基本上所有新的 JS 生态工具原生都是支持的。

在这 Vue3 开始预备发力的阶段,我们还是希望 Vue 社区能够使用更优秀更规范的方式来进行开发, 其实如果我们直接使用 JSX 开发 Vue3,我们会发现很多时候我们都不需要用到emit、attrs这些概念, 甚至如果 Vue3 的 JSX 插件支持,我们甚至能够抛弃slots。

但是因为 Vue3 一定要考虑兼容 Vue2,导致本身潜力很好的 Vue3 总是显得缩手缩脚,这不得不说是一种遗憾。

者:小迷妹

转发链接:https://mp.weixin.qq.com/s/49mJuvEEv5L9x3y0a5lRWw