You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
styled.js 是 styled API 的入口,常用的 API 如 styled.div 或者 styled(MyComponent) 即是从这个文件开始的,主要代码很少,如下所示:
styled.js
importconstructWithOptionsfrom'./constructWithOptions';importStyledComponentfrom'../models/StyledComponent';importdomElementsfrom'../utils/domElements';importtype{Target}from'../types';conststyled=(tag: Target)=>constructWithOptions(StyledComponent,tag);// Shorthands for all valid HTML ElementsdomElements.forEach(domElement=>{styled[domElement]=styled(domElement);});exportdefaultstyled;
exportdefaultfunctionconstructWithOptions(componentConstructor: Function,tag: Target,options: Object=EMPTY_OBJECT){if(!isValidElementType(tag)){thrownewStyledError(1,String(tag));}/* This is callable directly as a template function */// $FlowFixMe: Not typed to avoid destructuring argumentsconsttemplateFunction=(...args)=>componentConstructor(tag,options,css(...args));/* If config methods are called, wrap up a new template function and merge options */templateFunction.withConfig=config=>constructWithOptions(componentConstructor,tag,{ ...options, ...config});/* Modify/inject new props at runtime */templateFunction.attrs=attrs=>constructWithOptions(componentConstructor,tag,{
...options,attrs: Array.prototype.concat(options.attrs,attrs).filter(Boolean),});returntemplateFunction;}
renderInner(theme?: Theme){const{
componentStyle,
defaultProps,
displayName,
foldedComponentIds,
styledComponentId,
target,}=this.props.forwardedComponent;letgeneratedClassName;if(componentStyle.isStatic){generatedClassName=this.generateAndInjectStyles(EMPTY_OBJECT,this.props);}else{generatedClassName=this.generateAndInjectStyles(determineTheme(this.props,theme,defaultProps)||EMPTY_OBJECT,this.props);}constelementToBeCreated=this.props.as||this.attrs.as||target;constisTargetTag=isTag(elementToBeCreated);constpropsForElement={};constcomputedProps={ ...this.props, ...this.attrs};letkey;// eslint-disable-next-line guard-for-infor(keyincomputedProps){if(process.env.NODE_ENV!=='production'&&key==='innerRef'&&isTargetTag){this.warnInnerRef(displayName);}if(key==='forwardedComponent'||key==='as'){continue;}elseif(key==='forwardedRef')propsForElement.ref=computedProps[key];elseif(key==='forwardedAs')propsForElement.as=computedProps[key];elseif(!isTargetTag||validAttr(key)){// Don't pass through non HTML tags through to HTML elementspropsForElement[key]=computedProps[key];}}if(this.props.style&&this.attrs.style){propsForElement.style={ ...this.attrs.style, ...this.props.style};}propsForElement.className=Array.prototype.concat(,styledComponentId,generatedClassName!==styledComponentId ? generatedClassName : null,this.props.className,this.attrs.className).filter(Boolean).join(' ');returncreateElement(elementToBeCreated,propsForElement);}
本次阅读的源码包括:
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 创建内容。期间历经多次的数据预处理,组合与变换。
比较有意思的几个点:
SC 的 styledComponentId + foldedComponentIds)
总结
这三个文件虽然总代码量并不大,但基本算是 styled-components library 的核心了。我们看到了以下问题的答案:
css rules
是如何工作的?styled-components 官方介绍说 styled API 使用了 es6 的 Tagged Template Literals。其实这个东西我几乎每天都在用,比如输出 log :
之所以我会觉得 styled 的方式难以理解,是因为我一直没有看到这个知识点:标签模版
。阅读完之后你会知道,以下写法是等价的:
如果模板里包含变量,则会进行更复杂的转换:
这解释了为什么 constructWithOptions.js 中 args(css 内容)会以下面的方式传递进来。
每个 SC 拥有自己的 className 数组,我们称为 list。首先,SC 会计算并生成自己的 className ,添加到 list 中。 这个 className 会 match 到自己的 style-sheet(即模板中定义的 css 内容)。然后,如果 target 也是 SC,则当前 SC 也会将 target 的 className 添加到 list 。这样,list 中就包含了自己 style 的 className,和 target SC 的 className。所以, SC 就能利用到 target 的 style,相应的, target 也会同样利用到自己 target(如果有) 的 style。这看起来很像是面向对象的继承,起码起到了继承的效果不是。
能够继承 style,是 styled-components 一个很强大的地方。另一个方面,每个 SC 的 style 是独占的,别的 SC 不可访问更不可修改,从这个角度来说,也算是具有一定的封装。你看,面向对象的特征嘛。
The text was updated successfully, but these errors were encountered: