CSS in JS之styled-components

代码已经关联到github: 链接地址 觉得不错可以顺手点个star,这里会持续分享自己的开发经验(:

我们都知道,JSXJS语法的扩展,增加了对HTML语法的支持,那距离all in js就只差一个CSS语法支持了,目前实现该功能的库比较出名的有60+,感兴趣的可以自己查看:css-in-js
下面要介绍的就是其中之一的styled-components

简介

styled-components应该可以说CSS-in-JS最热门的一个库了,到目前为止github的star数已经超过了34k。
通过styled-components,你可以使用ES6的 标签模板字符串 语法在Component中定义一系列CSS属性,当该组件的JS代码被解析执行的时候,styled-components会动态生成一个CSS选择器,并把对应的CSS样式通过style标签的形式插入到head标签里面。
动态生成的CSS选择器会有一小段哈希值来保证全局唯一性来避免样式发生冲突。

Hello World

下面我们简单来了解下其写法:

const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;


const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

render(<Wrapper>
    <Title>Hello World</Title>
  </Wrapper>
}

编译后的样子:

在这里插入图片描述

其特点是什么?

  • 相当与定义了一个空的组件
  • 每一段样式必须绑定到一个组件上
  • 语法与CSS完全一样
  • 模板字符串编写

原理

基于5.3.1版本代码,下面简单解释其原理。

语法解释

从其写法分析,代码的可以拆解为styled.xx和后面的模板字符串。

前者可以理解成一个styled-components定义的函数,eg:

const styled = (tag) => {
	//...
};

domElements.forEach(domElement => {
  styled[domElement] = styled(domElement);
});

那后面的模板字符串,函数是怎么去解析的呢?这就不得不说模板字符串的一个功能带标签的模板字符串,该功能可以用函数解析模板字符串,eg:

function tag(strings,...keys){
  console.log('strings:', strings)
  console.log('keys:',keys)
}

tag`
	color:red;
	background:${props => props.bgcolor};
`

//strings: ["\n\tcolor:red;\n\tbackground:", ";\n", raw:["\n\tcolor:red;\n\tbackground:", ";\n"]]
//keys: [props => props.bgcolor]

得到原始字符串和占位符数据,这样就可以组装好css模板,渲染的时候根据传入值渲染即可。

组件创建过程
  1. 生成唯一id

根据参数和父组件生成,比如上面编译后的sc-jlyJG

const styledComponentId = generateId(options.displayName, options.parentComponentId)
  1. 对模板字符串解析得到样式,在head 中插入一个 style 节点并将样式注入,返回 className,比如之前编译后的eFyTid。下面简单描述这一过程:
// 构造className 
const generatedClassName = generateName(cssStatic >>> 0);

//构造style节点并获得
const style = document.createElement('style');
style.setAttribute(SC_ATTR, SC_ATTR_ACTIVE);
style.setAttribute(SC_ATTR_VERSION, SC_VERSION);
//...省略上一个style标签位置查找
// Avoid Edge bug where empty style elements don't create sheets
style.appendChild(document.createTextNode(''));
//根据传入的节点获得sheet
this.sheet = style.sheet //getSheet(style)

//最后插入css
this.sheet.insertRule(rule, index);

  1. 解析组件的其他propsattr得到新的元素参数propsForElement
  const computedProps = attrs !== props ? { ...props, ...attrs } : props;
  const propsForElement = {};

  for (const key in computedProps) {
    if (
      shouldForwardProp
        ? shouldForwardProp(key, validAttr, elementToBeCreated)
        : isTargetTag
        ? validAttr(key)
        : true
    ) {
      // Don't pass through non HTML tags through to HTML elements
      propsForElement[key] = computedProps[key];
    }
  }
  1. 最后根据propsForElementclassName生成组件
propsForElement.className = Array.prototype
  .concat(
  styledComponentId,
  generatedClassName !== styledComponentId ? generatedClassName : null,
  props.className,
  attrs.className
)
  .filter(Boolean)
  .join(' ');

// elementToBeCreated生成过程被省略了,该参数是一个元素标签或者是一个react组件
return createElement(elementToBeCreated, propsForElement);

基础使用指南

基于Props设置样式

styled定义的组件可以接受props参数,根据不同的参数设置样式。

const Button = styled.button`
  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>
);

继承样式

可以在继承一个已有组件的基础上生成一个新的组件,可以覆盖和新增样式。

const Button = styled.button`
  background: white;
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

const PrimaryButton = styled(Button)`
	background: palevioletred;
  color: white;
`

render(
  <div>
    <Button>Normal</Button>
    <PrimaryButton>Primary</Button>
  </div>
);

传递HTML属性

我们都知道html元素有很多属性,比如input 的 type、readonly和onChange 等属性。

const Input = styled.input`
  padding: 0.5em;
  margin: 0.5em;
  background: papayawhip;
  border: none;
`;

const handleChanged = (e) => {
	console.log('input changed:',e.target.value)
}

render(
  <Input defaultValue="default val" type="email" onChange={handleChanged}/>
)

组件中维护其他HTML属性

styled-components 同时支持为组件传入 html 元素的其他属性,比如为 input 元素指定一个 type 属性,我们可以使用 attrs关键字来实现。

使用过程中,我们可以使用静态属性,也可以使用props参数设置:

const Password = styled.input.attrs({
  type: 'password',
  placeholder:${props=>props.placeholder?props.placeholder:''}
})`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

在实际开发中,这个方法还有一个有用处,用来引用第三方类库的 css 样式:

const Input = styled.input.attrs({
  className: 'my-input',
})`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

与className和css框架搭配使用

可以与组件的className搭配使用,当然前提className是不会混淆的命名

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
	& > .my-h1{
		font-size: 1.5em;
    text-align: center;
    color: palevioletred;
	}
`;

render(
   <Wrapper>
    <h1 className={'my-h1'}>My H1</h1>
  </Wrapper>
)

同样的也兼容less等css框架使用。

import * as styles from './index.less'

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

render(
   <Wrapper>
    <h1 className={styles['my-h1']}>My H1</h1>
  </Wrapper>
)

& 关键字

less 中类似,& 在语法中代表自身选择器,我们上一节就使用了该用法。

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
	& > .my-h1{
		font-size: 1.5em;
    text-align: center;
    color: palevioletred;
	}
`;

除了这种用法,他还可以用来增加样式的权重

const Example = styled.li`
    color: red; 
    & {
        color:blue;
    }
    
    && {
        color: green;
    }
`;

动画

styled-components 同样对 css 动画中的 @keyframe 做了很好的支持。

const rotate = styled.keyframes`
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
`;

const styles = styled.css`
  animation: ${rotate} 2s linear infinite;
`

const Rotate = styled.div`
	${styles};
  display: inline-block;
  padding: 2rem 1rem;
  font-size: 1.2rem;
`;

render(
  <Rotate>&lt; 💅🏾 &gt;</Rotate>
);

ref

通过传递ref属性,可以获得组件的元素节点。

const Input = styled.input.attrs({
  className: 'my-input',
})`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

const inputRef = React.useRef();
render(
  <Input ref={inputRef}/>
);

组件嵌套选择器用法

如果两个组件定义的作用域一致,则可以直接嵌套使用选择器。

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;
const SubTitle = styled.span`
  text-align: center;
	color: grey;

	${Title}:hover &{
			color: red;
	}
`
render(
  <>
  <Wrapper>
    <Title>
  		Hello World 
  		<SubTitle> yes!</SubTitle>
  	</Title>
  </Wrapper>
  <SubTitle> no...</SubTitle>
  </>
}

对象样式定义

如果样式都是一个对象类型,那也可以之间舍弃模板字符串,直接使用对象定义的方式定义样式。

const Box = styled.div({
  background: 'palevioletred',
  height: '50px',
  width: '50px'
});

const PropsBox = styled.div(props => ({
  background: props.background,
  height: '50px',
  width: '50px'
}));

render(
  <div>
    <Box />
    <PropsBox background="blue" />
  </div>
);

其他

  1. 支持ts
  2. 支持单测
  3. 语法检测
  4. 语法高亮

优劣

优势

  1. 标准CSS语法
  2. 自动注入 CSS

styled-components 根据页面上呈现的组件,自动注入它们的样式,减少多余代码的引入。

  1. 唯一类名

styled-components 为您的样式生成唯一的类名,不用担心重复或拼写错误。

  1. 设置动态样式非常简单

根据组件的 props 或全局主题调整组件的样式简单直观,无需手动管理数十个类。

  1. 维护简单

不必在不同的文件中寻找影响组件的样式。

  1. 自动添加浏览器前缀

我们只需要编写一次css,sc在渲染的时候根据浏览器自动帮我们增加前缀。

劣势

  1. 学习成本高。适合已经有一定开发经验的人使用
  2. 代码可读性差。动态生成的唯一类名阅读困难,不能写z如果是需要开放类组件库适用性差。
  3. 冗余度高。使用props动态生成样式时,不同的props值生成的样式会一直存在。
  4. 性能
    1. 运行时动态生成css
    2. 运行时包高达15.4kb

参考

官网
CSS in JS的好与坏
styled-components 运行原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值