React 样式解决方案 styled-components

前置

在 react 中解决组件样式冲突的方案中,如果您喜欢将 css 与 js 分离,可能更习惯于 CSS-Modules;如果习惯了 Vue.js 那样的单文件组件,可能习惯于使用 styled-components 来解决这个问题。使用 CSS-Modules 从老项目迁移过来可能更容易。

安装

npm i styled-components

基本用法

import React from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'

const items = [
  {
    title: 'title1',
    type: 'primary',
    desc: 'Lorem ipsum dolor sit amet consectetur '
  },
  {
    title: 'title2',
    type: 'other',
    desc: 'Lorem ipsum dolor sit amet consectetur ',
  },
]

function App() {
  return (
    <div>
      {items.map(renderItem)}
    </div>
  )
}

function renderItem(item) {
    return (
      <Wrap>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Wrap>
    )
}

const Wrap = styled.div`
  margin: 10px auto 10px;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
  background: #eee
`

render(<App />, document.getElementById('app'))

实际渲染结果:


我们需要在 JavaScript 模板字符串内部书写 css 样式,为了得到 css 语法高亮,可以使用 vscode 扩展。

嵌套

const Wrap = styled.div`
  margin: 10px auto;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
  background: #eee;

+ h2 {
+   color: red
+ }
`

props

function renderItem(item) {
    return (
+     <Wrap type={item.type} >
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Wrap>
    )
}

const Wrap = styled.div`
  margin: 10px auto;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
+ background: ${props => props.type === 'primary' ? '#202234' : '#eee'};
`

在模板括号中可使用任意 JavaScript 表达式,这里使用箭头函数,通过 props 接收参数。

继承

使用继承实现上文功能:

import React from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'

const items = [...]

function App() {
  return (...)
}

function renderItem(item) {
+ const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
+     <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

const Wrap = styled.div`
  margin: 10px auto 10px;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
- background: ${props => props.type === 'primary' ? '#202234' : '#eee'};
`

+ const OrdinaryContainer = styled(Wrap)`
+   background: #eee;
+ `

+ const primaryContainer = styled(Wrap)` 
+   background: #202234;
+ `

render(<App />, document.getElementById('app'))

我们得到同样的效果:

继承的语法

// 如上所示,您应该这样写来实现继承
const OrdinaryContainer = styled(Wrap)``
// 现在已经不支持extend关键字
const OrdinaryContainer = Wrap.extend``

下面给它们共同继承的 Wrap 添加一个border:

const Wrap = styled.div`
  margin: 10px auto 10px;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
+ border: 2px solid red;
`

将影响所有继承过 Wrap 的样式变量:

attrs

封装一个文本输入框组件 src/components/Input.jsx

import styled from "styled-components";

const Input = styled.input.attrs({
    type: 'text',
    padding: props => props.size || "0.5em",
    margin: props => props.size || "0.5em"
})`
 border: 2px solid #eee;
 color: #555;
 border-radius: 4px;
 margin: ${props=>props.margin};
 padding: ${props=>props.padding};
`

export default Input

使用

// ..
import Input from './components/Input'
 
function App() {
  return (
    <div>
      <Input></Input>
      <Input size="2em"></Input>
    </div>
  )
}

render(<App />, document.getElementById('app'))

createGlobalStyle

在 V4 版本已经将 injectGlobal 移除,使用 createGlobalStyle 代替它设置全局样式,

import React from 'react'
import { render } from 'react-dom'
import styled, {createGlobalStyle} from 'styled-components'

// ...

function App() {
  return (
    <div>
      <GlobalStyle></GlobalStyle>
      {items.map(renderItem)}
    </div>
  )
}

function renderItem(item) {
  const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
      <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

const GlobalStyle = createGlobalStyle`
  *{
    margin: 0;
    padding: 0;
   }
   body {
     min-height: 100%;
     background: #ffb3cc;
   }
`
// ...

render(<App />, document.getElementById('app'))

ThemeProvider

通过上下文 API 将主题注入组件树中位于其下方任何位置的所有样式组件中。

import React from 'react'
import { render } from 'react-dom'
import styled, {createGlobalStyle, ThemeProvider} from 'styled-components'
import Input from './components/Input'

const items = [...]

function App() {
  return (
    <div>
      <GlobalStyle></GlobalStyle>
      {items.map(renderItem)}
      <Input></Input>
      <Input size="2em"></Input>
    </div>
  )
}

function renderItem(item) {
  const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
      <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

// ...

const primaryContainer = styled(Wrap)` 
  background: ${props=>props.theme.primary};
`

const theme = {
  primary: '#202234'
}

render(<ThemeProvider theme={theme}><App /></ThemeProvider>, document.getElementById('app'))

我们需要先导入 ThemeProvider,然后用标签将 <App /> 包裹,需要注意的是必须为 ThemeProvider 标签提供一个 theme 属性,接下来在所有子组件中都可以通过 props.theme.xxx 获取 theme 下的属性。我们将 Input 组件的背景色同样改为 theme 下的 primary

import styled from "styled-components";

const Input = styled.input.attrs({
    // ...
})`
 // ...
 background: ${props=>props.theme.primary};
`

export default Input

keyframes

使用 styled-components 时,无法直接在模板字符串中创建 keyframes,需要先导入 styled-components 下的 keframes 对象来创建它。下面看一个简单的实例:

import React from 'react'
import styled, { keyframes } from 'styled-components'
import { render } from 'react-dom'

const items = [...]

function App() {
  return (
    <div>
      {items.map(renderItem)}
    </div>
  )
}

function renderItem(item) {
  const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
      <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

// ...

const fadeIn = keyframes`
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`

const Wrap = styled.div`
  // ...
  animation: 1.5s ${fadeIn} ease-out;
`
// ..

render(<App />, document.getElementById('app'))

重新刷新页面,就能看见下面的效果:

posted @ 2020-08-06 19:18  guangzan  阅读(1781)  评论(0编辑  收藏  举报