15

styled-components学习记录

 3 years ago
source link: https://www.ruphi.cn/archives/422/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、styled-components有什么用?

我们常常会思考,如何以一种更优雅、更灵活的方式来为React组件编写样式?因而styled-components应运而生。

使用styled-components,它可以带来如下这些好处:

  • 自动提取关键的CSS: styled-components会自动追踪页面上哪些组件得到了渲染,只有那些得到了渲染的组件的样式才会被应用,这样子带来的好处就是结合code splitting的情况下,只有必要的代码才会进行加载
  • 没有类命名的问题: styled-components会为样式创建独一无二的类名,因此不必担心重名、覆盖以及拼写错误的问题
  • 方便移除CSS: 有时候我们很难以追踪一个CSS类在代码仓中的哪些地方被用到了,而styled-components能够使得这个追踪过程变得更容易,我们可以清楚地知道样式在哪里被使用了,如果组件不再使用且被删除的时候,其相关的CSS也很容易被找到移除
  • 简单的动态样式: 我们有时候会基于组件的props以及全局的主题设置来调整组件的样式,那么styled-components使得这件事变得容易得多,避免了我们需要为了实现类似于的效果多去维护大量的类名
  • 无痛维护: 我们无需再去翻遍不同的文件来理清楚组件的样式受到哪些CSS类影响,因此即便是维护一个大的代码仓,样式的维护工作也变得轻松了许多
  • 自动添加厂商前缀: 有了styled-components,编写CSS我们就只需要根据标准语法来写,再也不用管要不要加厂商前缀了(形如-webkit-之类的),因为styled-components会帮我们做完剩下的事情

安装styled-components很简单,只需要用包管理工具把styled-components添加作为运行时依赖就好了,如用npm可以:

npm install -S styled-components

如果是使用类似于yarn这种支持在package.json里添加一个resolutions字段的包管理工具,那么比较推荐加入一个主版本范围,从而避免在项目里被安装了不同版本的styled-components

{
  "resolutions": {
    "styled-components": "^5"
  }
}

三、快速入门

styled-components使用了模板字符串标签字面量(tagged template literals)语法来为组件增添样式,它不再使用映射的方式来关联样式与组件(如写一个类名,然后通过styles.someName的方式作为组件className属性的值),因此这意味着当你定义样式的时候,其实就已经创建了一个正常的React组件,并且这个React组件会被自动带上样式,例子如下:

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

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

// 接下来就可以直接像使用普通组件一样,使用 Title 跟 Wrapper 两个组件了
render(
  <Wrapper>
    <Title>
      Hello, world!
    </Title>
  </Wrapper>
)

其实就类似于原先的下列这种写法产生的效果:

/* style.css */
.title {
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
}
.wrapper {
  padding: 4em;
  background: papayawhip;
}
import styles from './style.css';

render(
  <section className={styles.wrapper}>
    <h1 className={styles.title}>Hello, world</h1>
  </section>
);

四、根据Props调整

有时候,我们希望根据不同的组件属性,来呈现不同的组件样式,比如按钮,分为主按钮、次按钮……这种情况下,可以通过传入一个函数插值styled-components的模板字面量来实现。例子如下:

const Button = styled.button`
  margin: 1em;
  padding: .25em 1em;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
  background: ${props => props.primary ? 'palevioletred' : 'white'};
  color: ${props => props.primary ? 'white' : 'palevioletred'};
`

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

五、继承样式

有许多场景下,你可能会想要使用一个组件,但是仅仅对它的样式作轻微的变更。你可以通过传入一个函数插值来根据组件属性做到这点,但是这有点儿麻烦了。
更方便的创建一个组件来继承另一个组件样式的方式是,仅仅使用styled()包裹起来创建,这里我们使用前面写过的Button来创建另一个样式的Button,其写法如下:

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

render(
  <TomatoButton>Click Me</TomatoButton>
);

运行过后,我们会发现<TomatoButton>组件长得跟<Button>组件差不多,因为我们也仅仅添加了两条样式。
此外,有时候我们在一些场景下可以复用相同的样式,但是标签不同,尤其是在创建导航的时候,这种情况下,我们可以使用as属性,比如我们希望最终渲染出来的<TomatoButton>是带有链接的<a>标签,那么可以这么做:

render(
  <TomatoButton as="a" href="/tomato">Click Me</TomatoButton>
);

甚至,连自定义组件也可以使用as语法,如:

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

render(
  <div>
    <Button>Normal</Button>
    <Button as={ReversedButton}>Reversed</Button>
  </div>
);

那么,最终第二个Button呈现出的文本内容会是:desreveR

六、为任何组件添加样式

styled其实还可以很好地作用于你自己编写的组件或者任何的第三方组件,只要这些组件能够传递好className属性到最终的DOM元素上,如:

const Link = ({ className, children }) => (
  <a className={className}>{children}</a>
);

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

render(
  <div>
    <Link>Normal Link</Link>
    <StyledLink>Styled Link</StyledLink>
  </div>
);

注意: 我们也可以直接给styled()工厂函数传递标签名字,如:styled('div'),而实际上,styled.tagName就是styled(tagName)的别名

七、传递属性

如果被添加样式的目标是一个简单的元素(如:styled.div),那么styled-components会将所有的已知HTML属性传递到DOM上;而如果这是一个自定义的React组件(如:styled(MyComponent)),那么styled-components就会透传所有的props

下面这个例子展示了Input组件的全部属性是如何传递到已挂载的DOM节点上的:

const Input = styled.input`
  padding: .5em;
  margin: .5em;
  color: ${props => props.inputColor || 'palevioletred'};
  background: papayawhip;
  border: none;
  border-radius: 3px;
`;

render(
  <div>
    <Input defaultValue="@probablyup" type="text" />
    <Input defaultValue="@geelen" type="text" inputColor="rebeccapurple" />
  </div>
);

可以注意到,inputColor作为属性没有传递给DOM,但是typedefaultValue传递了,所以styled-components会聪明地帮你筛选掉非标准属性从而作为props传递

八、一些原则

1、styled-components是怎样在一个组件内工作的

如果你对导入CSS到组件中熟悉的话(类似于CSS Modules),那么你就会对如下的写法司空见惯:

import React from 'react'
import styles from './styles.css'

export default class Counter extends React.Component {
  state = { count: 0 }

  increment = () => this.setState({ count: this.state.count + 1 })
  decrement = () => this.setState({ count: this.state.count - 1 })

  render() {
    return (
      <div className={styles.counter}>
        <p className={styles.paragraph}>{this.state.count}</p>
        <button className={styles.button} onClick={this.increment}>+</button>
        <button className={styles.button} onClick={this.decrement}>-</button>
      </div>
    )
  }
}

而一个样式化的组件,就是元素与样式规则的结合,我们可以将上面的Counter改写为:

import React from 'react'
import styled from 'styled-components'

const StyledCounter = styled.div`
  /* ... */
`
const Paragraph = styled.p`
  /* ... */
`
const Button = styled.p`
  /* ... */
`

export default class Counter extends React.Component {
  state = { count: 0 }

  increment = () => this.setState({ count: this.state.count + 1 })
  decrement = () => this.setState({ count: this.state.count - 1 })

  render() {
    return (
      <StyledCounter>
        <Paragraph>{this.state.count}</Paragraph>
        <Button onClick={this.increment}>+</Button>
        <Button onClick={this.decrement}>-</Button>
      </StyledCounter>
    )
  }
}

2、应该在render方法之外定义样式化组件

render方法之外定义样式化组件非常重要,否则每一次的渲染都会进行重复创建的过程,在render方法内部定义样式化组件会阻碍缓存且降低渲染速度,这应该被避免,即要写成如下这种方式:

const StyledWrapper = styled.div`
  /* ... */
`;

而非这种方式:

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

  return <StyledWrapper>{message}</StyledWrapper>
}

3、伪元素、伪类与嵌套

styled-components使用了stylis这种预处理器来实现类scss风格的嵌套语法,且&可以用来指向主组件,下面是用法的示例:

const Thing = styled.div.attrs((/* props */) => ({ tabIndex: 0 }))`
  color: blue;
  &:hover {
    color: red; // <Thing>组件被hover的时候
  }
  & ~ & {
    background: tomato;
  }
  & + & {
    background: lime;
  }
`

如果不使用&,则指向的是子元素:

const Thing = styled.div`
  color: blue;
  
  .something {
    border: 1px solid;
    display: block;
  }
`

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

最后,&可以被用来增强组件规则的CSS特殊性,这可以解决样式冲突时的样式问题,如:

const Thing = styled.div`
  && {
    color: blue;
  }
`
const GlobalStyle = createGlobalStyle`
  div${Thing} {
    color: red;
  }
`

render(
  <>
    <GlobalStyle />
    <Thing>I am blue</Thing>
  </>
)

最终I am blue显示为蓝色

九、自动添加属性

.attrs可以自动给组件带上一些属性,如:

const Input = styled.input.attrs(props => ({
  type: 'password',
  size: props.size || '1em'
}))`
  color: palevioletred;
  font-size: 1em;
  border: 12px solid palevioletred;
  border-radius: 3px;

  margin: ${props => props.size};
  padding: ${props => props.size};
`

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

使用@keyframes的CSS动画一般不局限在单一的组件中,但我们仍然希望这些CSS动画能够不被置于全局语境从而带来命名冲突。在这种场景下,我们可以使用keyframes这个tag,如:

const rotate = keyframes`
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(360deg);
  }
`;

const Rotate = styled.div`
  display: inline-block;
  animation: ${rotate} 2s linear infinite;
  padding: 2rem 1rem;
  font-size: 1.2rem;
`;

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

Keyframes只在被使用时懒注入,这也是为什么能够被code-splitted的原因,所以需要使用css标签来包裹共享片段:

const rotate = keyframes``;
// 以下这么写会报错
const styles = `
  animation: ${rotate} 2s linear infinite;
`

// 这样子写才对
const styles = css`
  animation: ${rotate} 2s linear infinite;
`;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK