React CSS-In-JS系列(一):styled-components

297 阅读4分钟

styled-components

style-components是针对React写的一套css in js(有利于组件的隔离)框架,简单来讲就是在js中写css。 相对于与预处理器 (sass、less)的好处是,css in js使用的是js语法,不用重新再学习新技术,也不会多一道编译步骤。无疑会加快网页速度。

一、使用方法

1.安装

从 npm 安装 styled-components :

npm install --save styled-components

添加 styled-components 之后就可以访问全局的 window.styled 变量.

import styled from "styled-components" 
const Button = styled.button` 
display: inline-block; 
margin: 1em; 
padding:5px; 
font-size:16px; 
text-decoration:none;
color:#fff; 
border:none;
//基于props属性的适配 
background-color:${props =>props.type==='primary'?"blue":"red" };
`;
export default Button

2.基于props属性的适配与传递

  • 基于属性的适配 我们可以将 props 以插值的方式传递给styled component,以调整组件样式.
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>
);
  • 属性传递

如果添加样式的目标是 DOM 元素 (如styled.div), styled-components 会传递已知的 HTML 属性给 DOM. 如果是一个自定义的 React 组件 (如styled(MyComponent)), styled-components 会传递全部 props.

以下示例展示如何传递 Input 组件的 props 到已装载的 DOM 节点, as with React elements.

// 创建一个给<input>标签添加若干样式的 Input 组件 
const Input = styled.input`
  padding: 0.5em;
  margin: 0.5em;
  color: ${props => props.inputColor || "palevioletred"};
  background: papayawhip;
  border: none;
  border-radius: 3px;
`;

// 渲染两个样式化的 text input,一个标准颜色,一个自定义颜色
render(
  <div>
    <Input defaultValue="@probablyup" type="text" />
    <Input defaultValue="@geelen" type="text" inputColor="rebeccapurple" />
  </div>
);

注意, inputColor prop并没有传递给 DOM, 但是type和defaultValue 都传递了. styled-components足够智能,会自动过滤掉所有非标准 attribute.

3.styled-component样式

  • 样式继承 创建一个继承其它组件样式的新组件,最简单的方式就是用构造函数styled()包裹被继承的组件。
const Button = styled.button`
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

// 一个继承 Button 的新组件, 重载了一部分样式
const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

render(
  <div>
    <Button>Normal Button</Button>
    <TomatoButton>Tomato Button</TomatoButton>
  </div>
);
  • 通过as作为prop,在不改变样式的前提下改变元素(像导航栏同时存在按钮与链接,但它们样式相同)
const Button = styled.button`
  display: inline-block;
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

render(
  <div>
    <Button>Normal Button</Button>
    <Button as="a" href="/">Link with Button styles</Button>
    <TomatoButton as="a" href="/">Link with Tomato Button styles</TomatoButton>
  </div>
);

这也完美适用于自定义组件:

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

const ReversedButton = props => <button {...props} children={props.children.split('').reverse()} />

render(
  <div>
    <Button>Normal Button</Button>
    <Button as={ReversedButton}>Custom Button with Normal Button styles</Button>
  </div>
);
  • 给任何组件添加样式 styled方法适用于任何最终向 DOM 元素传递 className 属性的组件,当然也包括第三方组件.
// 下面是给 react-router-dom  Link 组件添加样式的示例
const Link = ({ className, children }) => (
  <a className={className}>
    {children}
  </a>
);

const StyledLink = styled(Link)`
  color: palevioletred;
  font-weight: bold;
`;

render(
  <div>
    <Link>Unstyled, boring Link</Link>
    <br />
    <StyledLink>Styled, exciting Link</StyledLink>
  </div>
);

注意:在 render 方法之外定义 Styled Components

在 render 方法之外(组件之外)定义 styled component 很重要, 不然 styled component 会在每个渲染过程中重新创建. 这将阻止缓存生效并且大大降低了渲染速度,所以尽量避免这种情况.

推荐通过以下方式创建 styled components :

const StyledWrapper = styled.div`
/* ... */ 
` 
const Wrapper = ({ message }) => { 
return <StyledWrapper>{message}</StyledWrapper> 
}

4.伪元素、伪类选择器和嵌套

伪元素和伪类无需进一步细化,而是自动附加到了组件:

const Thing = styled.button` 
color: blue;
::before { content: '????'; 
} 
:hover { 
color: red; 
} ` 
render( 
<Thing>Hello world!</Thing>
)

对于更复杂的选择器,可以使用与号(&)来指向主组件(Thing).以下是一些示例:

const Thing = styled.div.attrs({ tabIndex: 0 })`
  color: blue;

  &:hover {
    color: red; // <Thing> when hovered
  }

  & ~ & {
    background: tomato; // <Thing> as a sibling of <Thing>, but maybe not directly next to it
  }

  & + & {
    background: lime; // <Thing> next to <Thing>
  }

  &.something {
    background: orange; // <Thing> tagged with an additional CSS class ".something"
  }

  .something-else & {
    border: 1px solid; // <Thing> inside another element labeled ".something-else"
  }
`

render(
  <React.Fragment>
    <Thing>Hello world!</Thing>
    <Thing>How ya doing?</Thing>
    <Thing className="something">The sun is shining...</Thing>
    <div>Pretty nice day today.</div>
    <Thing>Don't you think?</Thing>
    <div className="something-else">
      <Thing>Splendid.</Thing>
    </div>
  </React.Fragment>
)

如果只写选择器而不带&,则指向组件的子节点.

const Thing = styled.div`
  color: blue;

  .something {
    border: 1px solid; // an element labeled ".something" inside <Thing>
    display: block;
  }
`

render(
  <Thing>
    <label htmlFor="foo-button" className="something">Mystery button</label>
    <button id="foo-button">What do I do?</button>
  </Thing>
)

最后,&可以用于增加组件的差异性;在处理混用 styled-components 和纯 CSS 导致的样式冲突时这将会非常有用:

const Thing = styled.div`
  && {
    color: blue;
  }
`

const GlobalStyle = createGlobalStyle`
  div${Thing} {
    color: red;
  }
`

render(
  <React.Fragment>
    <GlobalStyle />
    <Thing>
      I'm blue, da ba dee da ba daa
    </Thing>
  </React.Fragment>
)

5.加额外的属性 .attrs

为了避免仅为传递一些props来渲染组件或元素而使用不必要的wrapper, 可以使用 .attrs constructor. 通过它可以添加额外的 props 或 attributes 到组件.

const Input = styled.input.attrs({
  //  static props
  type: "password",

  //  dynamic props
  margin: props => props.size || "1em",
  padding: props => props.size || "1em"
})`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;

  /* dynamically computed props */
  margin: ${props => props.margin};
  padding: ${props => props.padding};
`;

render(
  <div>
    <Input placeholder="A small text input" size="1em" />
    <br />
    <Input placeholder="A bigger text input" size="2em" />
  </div>
);

6.使用动画

const rotate = keyframes` // 使用keyframes关键字
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

const Rotate = styled.div`

  animation: ${rotate} 2s linear infinite;

`;

render(
  <Rotate>JSS</Rotate>
);

二、styled-component的优缺点

优点

1.css模块化: 尽量降低模块之间的耦合度,利于项目的进一步维护,同时因为每个样式都有其关联的组件. 如果检测到某个组件未使用并且被删除,则其所有的样式也都被删除。比起用原生的CSS,这是它首当其冲的优势。

2.支持预处理器嵌套语法。

3.解决了css类名冲突问题: styled-components 为样式生成唯一的 class name. 开发者不必再担心 class name 重复,覆盖和拼写错误的问题。

4.自动提供前缀: 按照当前标准写 CSS,其余的交给 styled-components handle 处理。

5.让CSS代码也可以处理逻辑:不仅仅是因为里面的模板字符串可以写JS表达式,更重要的是能够拿到组件的上下文信息(state、props);这样我们就可以通过一些动态的props参数,很轻松的实现主题换肤类似的功能。

6.styled-components还能很好的解决服务器渲染的样式问题

缺点

最大的缺点就是模板字符串没有css语法,无法使用代码提示。 可以在vscode安装vscode-styled-components插件,就会有提示功能。

还有一个问题,生成的页面在浏览器里面,如果想通过css的类名查找相应的组件,可以借助Babel插件进行相应的配置,方便我们在开发时调试。

参考资料

W3Cschool ---styled-component文档