前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >styled-components 深入浅出 (三) : 源码解析

styled-components 深入浅出 (三) : 源码解析

作者头像
andyhu
发布2023-09-02 19:00:08
2120
发布2023-09-02 19:00:08
举报
文章被收录于专栏:andyhu-大前端andyhu-大前端

源码解析

?

首先拉取 styled-components 仓库,然后找到 /packages/styled-components 文件夹,核心代码都放在这个文件夹里;为了方便,后面会把 styled-components 简写为 sc。

往期 styled-components 文章:

《styled-components 深入浅出 (一) : 基础使用》

《styled-components 深入浅出 (二) : 高阶组件》

如何阅读源代码?

当我们对该项目有所了解的时候。

  • 第一种:当我们使用过该项目的一些 api ,对于某个功能是如何实现的,带着这个问题去看,比如当我们使用 styled 函数的时候,我们会想到 styled 函数应该是通过 document.createElement 来创建 <style> 标签来实现样式化的,然后全局搜索 document.createElement 快速定位到。这种方式适合看具体单独的某个功能如何实现。
  • 第二种:从入口文件看起,根据导入、导出的文件,找到对应的文件,一层一层剥开,但是这种在比较复杂的项目中,可能需要花比较长的时间才能理清逻辑。这个时候我们就需要通过打断点的方式,来调试代码,找到一些关键函数的调用关系,梳理清楚主要逻辑。

对于 sc 这个项目我是采用两种方式结合使用。

makeStyleTag

一开始我就通过 document.createElement('style') 定位到 makeStyleTag 函数。代码所在文件:

代码非常简单易懂,这是去掉类型后的代码:

代码语言:javascript
复制
export const makeStyleTag = (target) => {
  const head = document.head;
  const parent = target || head;
  const style = document.createElement('style');
  // style 标签的插入位置
  const prevStyle = findLastStyleTag(parent);
  const nextSibling = prevStyle !== ?developer/article/2322549/undefined ? prevStyle.nextSibling : null;

  // 自定义属性 data-set
  style.setAttribute(SC_ATTR, SC_ATTR_ACTIVE);
  style.setAttribute(SC_ATTR_VERSION, SC_VERSION);

  const nonce = getNonce();

  // 设置 style 标签的 nonce 属性:一种加密的随机数(一次使用的数字)
  if (nonce) style.setAttribute('nonce', nonce);

  // 在父节点里的最后一个子节点的位置插入新的 style 节点
  parent.insertBefore(style, nextSibling);

  return style;
};

我们可以看到,这个函数主要做了以下几件事:

  1. 获取父节点,如果没有传入,则默认获取 document.head
  2. 获取父节点的最后一个 <style> 标签,如果没有,则返回 null
  3. 创建 <style> 标签,并设置了自定义属性
  4. 设置 <style> 标签的 nonce 属性
  5. <style> 标签插入到父节点的最后一个子节点的位置,然后返回 style 节点。

标签模板字符串

通过第一篇文章,我们知道通过 styled.divstyled.div() 都能创建一个样式化的 <div> 标签,这是怎么回事?来看一个例子:

这其实是 ES6 的一个新语法:模板字符串,在这可以把它看做是一个函数,接受传参。

styled 函数

我们找到入口文件:packages/styled-components/src/index.ts,

代码语言:javascript
复制
import styled from './constructors/styled';

export * from './base';
export {
  CSSKeyframes,
  CSSObject,
  CSSProp,
  CSSProperties,
  CSSPseudos,
  DefaultTheme,
  ExecutionContext,
  ExecutionProps,
  IStyledComponent,
  IStyledComponentFactory,
  IStyledStatics,
  PolymorphicComponent,
  PolymorphicComponentProps,
  RuleSet,
  Runtime,
  StyledObject,
  StyledOptions,
  WebTarget,
} from './types';
export { styled as default, styled };

默认导出的 styled 函数是从 packages/styled-components/src/constructors/styled.tsx 导出的,找到它,去掉类型后的代码方便阅读。

代码语言:javascript
复制
import createStyledComponent from '../models/StyledComponent';

// HTML 标签列表
import domElements from '../utils/domElements';
import constructWithOptions from './constructWithOptions';

// 创建基础的 styled 方法
const baseStyled = (tag) =>
 constructWithOptions(createStyledComponent, tag);
 
const styled = baseStyled;

// 实现通过 styled[domElement] 和 styled(domElement) 都能创建样式化组件
domElements.forEach(domElement => {
 styled[domElement] = baseStyled(domElement);
});

export default styled;

createStyledComponent 构造样式化组件

我们看到通过 styled 函数是基础的 styled 方法:baseStyled 调用了 constructWithOptions 方法,找到 constructWithOptions 方法所在的文件 src/constructors/constructWithOptions.ts,去掉类型只留下关键的代码如下

代码语言:javascript
复制
import css from './css';

export default function constructWithOptions(componentConstructor, tag, options) {

 const templateFunction = (initialStyles, ...interpolations) =>
   componentConstructor(tag, options, css(initialStyles, ...interpolations));
 
 // 返回样式化组件
 return templateFunction;
 
}

constructWithOptions 函数的核心是 templateFunction 方法,它调用组件的构造方法 componentConstructor返回一个样式化组件(携带样式的组件)。其实这个方法就是上面说到的 baseStyled 方法传入的 createStyledComponent 方法。该方法所在文件:src/models/StyledComponent.ts

为了方便理解,我画了个简单的函数调用关系图:

从这我们可以看出进来,当 sc 的使用者调用 styled() 这个api创建样式化组件时,本质上是通过 createStyledComponent 这个组件构造函数来实现的. 它返回一个 WrappedStyledComponent 组件,这个组件是通过 React.forwardRef 创建的,并且在返回之前会对 WrappedStyledComponent 组件添加一些属性。

未完待续...

写在最后

我是 AndyHu,目前暂时是一枚前端搬砖工程师。

文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注呀?

未经许可禁止转载?

speak less,do more.

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-09-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码解析
    • 如何阅读源代码?
      • makeStyleTag
        • 标签模板字符串
          • styled 函数
            • createStyledComponent 构造样式化组件
              • 未完待续...
              • 写在最后
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com