styled.js 是 styled API 的入口,常用的 API 如 styled.div 或者 styled(MyComponent) 即是从这个文件开始的,主要代码很少,如下所示:
styled.js
import constructWithOptions from './constructWithOptions';
import StyledComponent from '../models/StyledComponent';
import domElements from '../utils/domElements';
import type { Target } from '../types';
const styled = (tag: Target) => constructWithOptions(StyledComponent, tag);
// Shorthands for all valid HTML Elements
domElements.forEach(domElement => {
styled[domElement] = styled(domElement);
});
export default styled;
本次阅读的源码包括: https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constructors/styled.js https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constructors/constructWithOptions.js https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/models/StyledComponent.js
约定一下简称,方便阅读: SC - styled-component, 即一个 styled-component
详细解析
styled.js
styled.js 是 styled API 的入口,常用的 API 如 styled.div 或者 styled(MyComponent) 即是从这个文件开始的,主要代码很少,如下所示:
styled.js
styled 函数接收一个 targe (预定义组件如 div 或者任何自定义组件),返回 constructWithOptions 函数的运算结果。后面我们会看到 constructWithOptions 的运算结果就是生成一个 SC。
constructWithOptions.js
constructWithOptions 的代码也不多,如下所示: constructWithOptions.js
我们可以看到函数返回一个叫做 templateFunction 的函数,它会把传入的 args 作为 css 内容,交由 css-helper 生成为 style-sheet,然后调用 componentConstructor 以生成 SC。在上一个文件中,我们已经知道这里的 componentConstructor 是指第三个文件 StyledComponent.js 的 default export 的函数。这里可以预先说明一下这个函数,主要功能就是根据 tag 和 css 生成 styled-component。
另外一个值得注意的是 withConfig 和 attrs 的实现,attrs 和 config 的内容会被合并到 options,最后依然递归调用 constructWithOptions。这也是为什么我们在定义 SC 时像 Jquery 那样写链式代码。有兴趣的可以搜一下关于 Jquery 链式操作的文章,这里不多说。
StyledComponent.js
这个文件比较长,还是按照调用顺序,我们先看一下上面提到的 default export。
先不理最前面准备数据的部分,从 let WrappedStyledComponent; 看起。我们看到这里通过 React.forwardRef 将 ref 传递给 SC (也就是这个 ParentComponent)。比较特别的,WrappedStyledComponent 的构造函数也作为一个 prop 传递给了 SC。这里的真实目的是将后续赋予给 WrappedStyledComponent 诸多 props 如 id,attrs,css,捎带给 SC。
下面是 SC 的 render 方法中,使用这些 props 创建内容。期间历经多次的数据预处理,组合与变换。
比较有意思的几个点:
总结
这三个文件虽然总代码量并不大,但基本算是 styled-components library 的核心了。我们看到了以下问题的答案:
css rules
是如何工作的? styled-components 官方介绍说 styled API 使用了 es6 的 Tagged Template Literals。其实这个东西我几乎每天都在用,比如输出 log :之所以我会觉得 styled 的方式难以理解,是因为我一直没有看到这个知识点:标签模版 。阅读完之后你会知道,以下写法是等价的:
如果模板里包含变量,则会进行更复杂的转换:
tag
Hello ${ a + b } world ${ a * b }
; // 等同于 tag(['Hello ', ' world ', ''], 15, 50);能够继承 style,是 styled-components 一个很强大的地方。另一个方面,每个 SC 的 style 是独占的,别的 SC 不可访问更不可修改,从这个角度来说,也算是具有一定的封装。你看,面向对象的特征嘛。