为什么会有css-in-js

它是一种将css代码捆绑在JavaScript 代码中的解决方案。旨在解决css的局限性,例如缺乏动态功能,作用域和可移植性。

  • 主要是体现在开发方式上,因为是组件化的开发方式,区别于以前的页面开发,对于css文件的引入,我们更多的是希望对应组件开发时所写的css代码仅在当前组件范围内引进使用生效,这就需要css模拟实现作用域概念。

  • 可移植性主要是体现在我们希望在不同组件当中共用公共部分的css样式,所以可以把公共部分的代码抽离出来写到一个js文件内,在需要的地方引入使用即可,而不必复制文件内容,减少操作错误。

  • css本身缺乏动态功能,不能根据条件来决定给某一个元素添加什么样的样式。如果我们将css代码写在了JavaScript文件当中,我们就可以利用js的动态功能为元素去动态添加样式。

css-in-js 方案的优缺点

优点:

  • 让css代码拥有独立的作用域,防止css代码泄漏到组件的外部,防止样式冲突。

  • 让组件更具可移植性,实现开箱即用,轻松创建松耦合的应用程序。

  • 让组件更具可重用性,只需编写一次即可,可以在任何地方运行。不仅可以在同一应用程序中重用组件,而且可以在使用相同框架构建的其他应用程序中重用。

  • 让样式具有动态功能,可以将复杂的逻辑应用于样式规则,如果需要创建动态功能的复杂UI,它是理想的解决方案。

缺点:

  • 为项目增加额外的复杂性;

  • 自动生成的选择器大大降低了代码的可阅读性。主要是在于调试时,自动生成的都是随机的字符串。

综合而言,css-in-js方案的优点大于缺点。

babel配置以支持css属性的两种方式

  1. Emotion 库

emotion 库是css-in-js方案具体实施的一个库,它是一个旨在使用JavaScript编写css样式的库。

安装:

npm install @emotion/core @emotion/styled

npm install @emotion/core @emotion/styled

注意:@emotion/core 在新版本中已经更名为 @emotion/react

  1. css属性支持

  1. JSX Pragma

通知babel不再需要将jsx语法转换为 React.createElement() 方法,而是转换为jsx() 方法。

使用emotion

Input

Output

使用前

<img src="avatar.png"/>
React.createElement('img', {src: 'avatar.png'})

使用后

<img src="avatar.png"/>
jsx('img', {src: 'avatar.png'})

导入emotion库并配置使用。

/** @jsx jsx */  // 这个是提供给babel做解释emotion相关代码时配置的固定的特殊格注释
import jsx from '@emotion/core'; //`@emotion/core` 现在更名为`@emotion/react`了, 所以要这样写:`import { jsx } from '@emotion/react'`.

  1. Babel Preset (推荐使用的方式)

  1. npm run eject 弹射出react app项目的底层配置。

  1. 安装@emotion/babel-preset-css-prop 预设模块

npm install @emotion/babel-preset-css-prop
  1. 在package.json文件中找到babel属性,加入如下内容:

"presets": ["react-app","@emotion/babel-preset-css-prop"
]

配置emotion的 babel预设集就不需要在代码中引入jsx方法以及对应的特殊注释了。

  1. css方法的两种调用方式

  1. String styles

const style = css`width: 100px;height: 30px;background: skyblue;
`<div css={style}>App works...</div>
  1. object styles

const style = css({width: "100px",height: "30px",background: "skyblue"
})<div css={style}>App works...</div>

此时的css方法会帮我们把css样式转换成js模块嵌入到组件代码中。

{"name": "2h52qn-objectStyle","styles": "width:100px;height:30px;background:skyblue;label:objectStyle;","map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9odWlxdWFuZGVuZy9wcm9qZWN0cy9sZy1mZWQtbHAvbXktbW9kdWxlL3JlYWN0LXRlc3Qvc3JjL2NvbXBvbmVudHMvQ291bnRlci9pbmRleC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFlc0IiLCJmaWxlIjoiL1VzZXJzL2h1aXF1YW5kZW5nL3Byb2plY3RzL2xnLWZlZC1scC9teS1tb2R1bGUvcmVhY3QtdGVzdC9zcmMvY29tcG9uZW50cy9Db3VudGVyL2luZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlYWN0IGZyb20gJ3JlYWN0J1xuLy8gLyoqIEBqc3gganN4ICovXG4vLyBpbXBvcnQgeyBqc3ggfSBmcm9tICdAZW1vdGlvbi9yZWFjdCdcbmltcG9ydCB7IG9ic2VydmVyIH0gZnJvbSAnbW9ieC1yZWFjdC1saXRlJ1xuaW1wb3J0IHsgdXNlUm9vdFN0b3JlIH0gZnJvbSAnLi4vLi4vc3RvcmUnXG5pbXBvcnQgeyBjc3MgfSBmcm9tICdAZW1vdGlvbi9yZWFjdCdcblxuZnVuY3Rpb24gQ291bnRlciAocHJvcHMpIHtcbiAgY29uc3QgeyBjb3VudGVyU3RvcmUgfSA9IHVzZVJvb3RTdG9yZSgpXG4gIGNvbnN0IHsgY291bnQsIGluY3JlbWVudCwgZGVjcmVtZW50IH0gPSBjb3VudGVyU3RvcmVcbiAgY29uc3Qgc3RyaW5nU3R5bGUgPSBjc3NgXG4gICAgd2lkdGg6IDEwMHB4O1xuICAgIGhlaWdodDogMzBweDtcbiAgICBiYWNrZ3JvdW5kOiBza3libHVlO1xuICBgXG4gIGNvbnN0IG9iamVjdFN0eWxlID0gY3NzKHtcbiAgICB3aWR0aDogMTAwLFxuICAgIGhlaWdodDogMzAsXG4gICAgYmFja2dyb3VuZDogJ3NreWJsdWUnXG4gIH0pXG4gIGNvbnNvbGUubG9nKHN0cmluZ1N0eWxlLCBvYmplY3RTdHlsZSlcbiAgcmV0dXJuIChcbiAgICA8c2VjdGlvbj5cbiAgICAgIDxkaXYgY3NzPXtzdHJpbmdTdHlsZX0+QXBwIHdvcmtzLi4ud2l0aCBzdHJpbmcgU3R5bGVzPC9kaXY+XG4gICAgICA8ZGl2IGNzcz17b2JqZWN0U3R5bGV9PkFwcCB3b3Jrcy4uLndpdGggb2JqZWN0IFN0eWxlczwvZGl2PlxuICAgICAgPGg0IHN0eWxlPXt7IHdpZHRoOiAxMDAsIGJhY2tncm91bmQ6ICdibHVlJyB9fT7orqHmlbDlmajmoYjkvos8L2g0PlxuICAgICAgPGJ1dHRvbiBvbkNsaWNrPXsoKSA9PiBkZWNyZW1lbnQoKX0+LTE8L2J1dHRvbj5cbiAgICAgIDxzcGFuXG4gICAgICAgIHN0eWxlPXt7XG4gICAgICAgICAgd2lkdGg6ICcxMDBweCcsXG4gICAgICAgICAgZGlzcGxheTogJ2lubGluZS1ibG9jaycsXG4gICAgICAgICAgdGV4dEFsaWduOiAnY2VudGVyJyxcbiAgICAgICAgICBmb250U2l6ZTogJzIwcHgnXG4gICAgICAgIH19XG4gICAgICA+XG4gICAgICAgIHtjb3VudH1cbiAgICAgIDwvc3Bhbj5cbiAgICAgIDxidXR0b24gb25DbGljaz17KCkgPT4gaW5jcmVtZW50KCl9PisxPC9idXR0b24+XG4gICAgPC9zZWN0aW9uPlxuICApXG59XG5cbmV4cG9ydCBkZWZhdWx0IG9ic2VydmVyKENvdW50ZXIpXG4iXX0= */"
}

并且会通过类的方式来使用。

<div class="css-2h52qn-objectStyle">App works...with object Styles</div>

emotion中css属性优先级

props对象中的css属性优先级高于组件内部的css属性。

在调用组件时可以覆盖

覆盖组件默认样式。

styled components 样式话化组件

样式话化组件就是用来构建用户界面的,是emotion库听过库提供的另一种为元素添加样式的方式。

1. 创建样式化组件(styled.xxx)

使用前需要引入styled 支持库@emotion/styled

  1. string styles

const button = styled.button`width: 300px;height: 30px;background: skyblue;
`;// 拓展:还可以根据props属性覆盖样式
// 例如: background: ${props => props.bgColor || 'skyblue'};
  1. object styles

const button = styled.button(props => ({color: props.color || 'red',width: 300,height: 30,background: 'pink'
}))

2. 为任何组件添加样式

同样有string styles和object styles的区别。

const Demo = ({className}) => <div className={className}> </div>const Fancy = styled(Demo)`color: red;
`

3. 为特定父级下的子组件添加样式

通过父组件设置子组件样式

同样有string styles和object styles的两种方式。

const Child = styled.div`color: red
`const Parent = styled.div`${Child}{color: green}
`<Parent><Child/></Parent>

CSS-in-JS方案及实现库介绍-编程知识网

const Child = styled.div({color: 'red'
})const Parent = styled.div({[Child]:{color: 'yellow'}
})<Parent><Child/></Parent>

CSS-in-JS方案及实现库介绍-编程知识网

4. css选择器&:嵌套选择器

& 表示组件本省。

const Container = styled.div`color: red;& > a {color: pink;}
`

5. 样式化组件的as 属性

要使用组件中的样式,但要改变呈现的元素,可以使用as 属性。

const Button = styled.button`color: red
`<Button as="a" href="/#">Click me</Button> 

6. 样式组合

在样式组合中,后调用的样式优先级高于先调用的样式。

const base = css`color: skyblue;
`
const danger = css`color: red;
`<button css={[base, danger]}>样式组合的button</button> 

7. 全局样式Global

全局样式的应用需要使用到emotion当中的Global组件,及其 styles属性。

const styles = css`body: {margin:0; background: tomato; color: skyblue;}
`
function App() {return <><Global styles={styles}>app is working...</>
}

8. 使用keyframes方法定义关键帧动画

在emotion中定义帧动画需要用到keyframes 方法。


const move = keyframes`0% {background: skyblue;left: 0;top: 0;font-size: 1rem;}100% {background: green;left: 600px;top: 300px;font-size: 2rem;}
`
const box = css`color: red;width: 200px;height: 200px;position: absolute;animation: ${move} 2s ease;
`<div css={box}>animate box</div>

9. emotion 主题

  • 下载模块:

npm install emotion-theming

  • 引入ThemeProvider

import {ThemeProvider} from 'emotion-theming' // 现在也合并到了@emotion/react中
  • 添加主题内容

ThemeProvider应该放在所有组件的最外层。

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
// import { Provider } from 'react-redux'
// import store from './store'import { Global } from '@emotion/react'
import styles from './styles'
import { ThemeProvider } from '@emotion/react'const theme = {color: {primary: 'tomato'}
}ReactDOM.render(<ThemeProvider theme={theme}><App /></ThemeProvider>,document.getElementById('root')
)
  • 获取主题内容

在内部组件中可以通过一下方式获取主题theme的内容。

const primaryColor = props => css`color: ${props.color.primary};
`<div css={primaryColor}></div>
  • 也可以使用钩子函数useTheme() 的方式来获取主题内容

import { useTheme } from '@emotion/react'
function Demo() {const theme = useTheme()console.log(theme)
}