CSS-IN-JS 方案 - Emotion 库
2021/5/24 10:30:55
本文主要是介绍CSS-IN-JS 方案 - Emotion 库,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
CSS-IN-JS
"CSS in JS" 是一种在 JavaScript 文件中集成 CSS 代码的解决方案。
这种方案旨在解决 CSS 的局限性,例如缺乏动态功能,作用域和可移植性。
为什么会有 CSS-IN-JS
- 缺乏作用域
- 缺乏可移植性
- 缺乏动态功能
其它参考:阮一峰 - CSS in JS 简介
缺乏作用域
以前开发 web 项目都是以**“页面”为单位,为了将关注点分离**,一般都是将 CSS、JS 以文件的方式引入 HTML 文件。
而现在开发 web 项目都是以**“组件”**为单位,所以很多开发者更倾向于将同一个组件的 HTML、CSS、JS 代码都集成在一起。(例如 React 中已经通过 JS 编写 HTML 代码)
这样组件与组件间的 CSS 就不会产生冲突,这使用的是作用域的概念,而 CSS 没有作用域。
CSS-IN-JS 方案就是通过 JavaScript 的作用域模拟 CSS 的作用域。
缺乏可移植性
将 CSS 文件集中在组件中,这样在使用组件的时候,可以避免遗漏引入必要的 CSS 文件。
缺乏动态功能
CSS 无法通过条件判断,决定给元素设置哪些样式。
如果将 CSS 写在 JavaScript 的文件中,就可以用 JS 的动态功能给元素添加样式了。
CSS-IN-JS 方案的优缺点
CSS-IN-JS 方案的优点大于缺点,是值得在 React 项目中大力推广的解决方案。
优点
- 让 CSS 代码拥有独立的作用域,阻止 CSS 代码泄露到组件外部,防止样式冲突。
- 让组件更具可移植性,实现开箱即用,轻松创建松耦合的应用程序。
- 让组件更具可重用性,只需编写一次即可,可以在任何地方运行。不仅可以在同一个应用程序中重用组件,而且可以在使用相同框架构建的其它应用程序中重用组件。
- 让样式具有动态功能,可以将复杂的逻辑引用于样式规则,如果要创建需要动态功能的复杂 UI,它是理想的解决方案。
缺点
- 为项目增加了额外的复杂性,需要了解学习和应用这种解决方案。
- 自动生成的选择器大大降低了代码的可读性。
Emotion
本文 Emotion 版本:11
介绍
Emotion 是使用 JavaScript 编写 CSS 样式的库,是众多实施 CSS-IN-JS 方案库的其中一个。
安装
React 使用 Emotion 需要安装:
npm install @emotion/react
css 属性
Emotion 提供一个 css
属性,任何可以设置 className
的组件或元素都可以使用 css
属性。
传入 css
属性的样式将被评估,并将计算出的类名应用于 className
。
function App() { return ( <div css={{fontSize: '20px', background: red}}>App work</div> ) }
Babel 配置支持 css 属性
现在 css
属性并没有生效,因为 React.createElement
(JSX 表达式被转换为 React.createElement
方法的调用)并不知道该如何解析 css
属性。
Emotion 提供了 jsx
方法去编译转换 JSX 表达式。
有两种方式可以告诉 Babel 使用 Emiotion 的 jsx
方法替换 React.createElement
方法编译 JSX 表达式:
- JSX Pragma
- Babel Preset(推荐)
Input | Output | |
---|---|---|
Before | <img src="avatar.png" /> | React.createElement('img', { src: 'avatar.png' }) |
After | <img src="avatar.png" /> | jsx('img', { src: 'avatar.png' }) |
官方文档 - The css Prop
JSX Pragma
Pragma 用于替换编译 JSX 表达式时使用的函数。
- 使用 JSX Pragma
- 引入
jsx
方法
// 此注释告诉 Babel 将 jsx 代码转换为 jsx 函数的调用,而不是 React.createElement /** @jsx jsx */ import { jsx } from '@emotion/react' function App() { return ( <div css={{fontSize: '20px', background: 'red'}}>App works</div> ) } export default App
如果使用了 React 17+ 版本,它默认采用了新版自动导入运行时(不需要手动引入 React 模块),则会冲突报错:pragma and pragmaFrag cannot be set when runtime is automatic.
需要指定 Emotion 的自动导入运行时:
/** @jsxImportSource @emotion/react */ function App() { return ( <div css={{fontSize: '20px', background: 'red'}}>App works</div> ) } export default App
不需要再手动导入
jsx
方法。
Babel Preset
#下载 preset npm install @emotion/babel-preset-css-prop
Babel 配置添加预设:
"presets": [ "@emotion/babel-preset-css-prop" ]
使用 Babel Preset 配置,也不需要在组件文件引入 jsx
方法:
function App() { return ( <div css={{fontSize: '20px', background: 'red'}}>App works</div> ) } export default App
eject 方式(不推荐)
React 需要使用 eject
暴露全部配置,然后在 package.json
中配置:
"babel": { "presets": [ "react-app", "@emotion/babel-preset-css-prop" ] }
customize-cra(推荐)
eject
是不可逆的,推荐使用 customize-cra
和 react-app-rewired
进行自定义配置:
# 安装 npm install customize-cra react-app-rewired
修改 package.json
:
/* package.json */ "scripts": { - "start": "react-scripts start", + "start": "react-app-rewired start", - "build": "react-scripts build", + "build": "react-app-rewired build", - "test": "react-scripts test", + "test": "react-app-rewired test", "eject": "react-scripts eject" }
在根目录创建 config-overrides.js
文件:
/* config-overrides.js */ const { override, addBabelPreset } = require('customize-cra') module.exports = { webpack: override( // emotion css props support addBabelPreset('@emotion/babel-preset-css-prop') ) }
设置类型
css
属性有两种设置类型:
- 对象字面量:对象或对象数组
css
方法的返回值
ES6 标签模板
参考 阮一峰 - 标签模板
模板字符串的标签模板功能:在函数后面跟模板字符串(反引号包裹的字符串)。
这样调用的函数,会将模板字符串处理成数组再传递给函数:
alert`Hello` // 等同于 alert(['Hello'])
如果模板字符串中嵌入了变量或表达式,就会将字符串拆分,嵌入的内容作为后续的参数传给函数:
const a = 10, b = 20 foo`${a} + ${b} = ${a + b}` // 等同于 foo(['', ' + ', ' = ', ''], 10, 20, 30)
模拟 Emotion 的 css
方法的模板字符串方式的调用:
function css(style, ...args) { let res = '' let i = 0 while(i < style.length) { res += style[i] if (i<args.length) { res += args[i] } i++ } console.log(res) } const fontSize = 14 const bgColor = 'skyblue' css` color:red; font-size: ${fontSize}px; background:${bgColor}; ` // 打印结果: /* color:red; font-size: 14px; background:skyblue; */
css 方法
css
方法 和 css
属性是配合使用的。
通过 css
方法,可以把原本写在行内的 CSS 样式拿到外面去写,将返回的值传递给 css
属性。
css
方法有两种调用方式:
- String Styles:模板字符串调用方式
- 以字符串形式编写样式
- Object Styles:普通函数调用方式
- 使用对象或对象数组方式编写样式
css
方法的返回值是一个对象,包含定义的样式和代表这个样式的类名。
不论使用哪种方式,都要先引入:
import { css } from '@emotion'
String Styles
使用标签模板的方式调用 css
方法,可以在模板字符串中编写 CSS 语法的样式。
使用模板字符串方式,设置的数值类样式,需加上单位:
width: 200 // 不生效 width: 200px 生效函数方式的对象不需要,单位会自动加上(如果使用的字符串,例如
"2000"
则不会自动附加)。
import { css } from '@emotion/react' const style = css` font-size: 20px; background: red; ` console.log(style) function App() { return ( <div css={style}>App works</div> ) } export default App
打印结果:
Object Styles
const style = css({ fontSize: '20px', background: 'red' })
css 属性优先级
props 对象中的 css 属性优先级高于组件内部的 css 属性。
这样,在调用组件的时候可以覆盖组件的默认样式。
// 下面 `styleA` 计算的 `className` 会覆盖 `styleB` 计算的 `className` // 尽管 css 属性写在 {...props} 后面 // 最终背景颜色是 pink import { css } from '@emotion/react' function Foo(props) { const styleB = css({ background: 'red' }) return <div {...props} css={styleB}>123123123</div> } function App() { const styleA = css({ background: 'pink' }) return <Foo css={styleA} /> } export default App
Styled Components 样式化组件
styled
方法可以创建附加样式的 React 组件,它是 Emotion 提供的为组件或元素添加样式的另一种方式。
安装
styled
由 @emotion/styled
提供
# 安装 npm install @emotion/styled
// 引入 import styled from '@emotion/styled'
创建普通元素的样式化组件
styled
为每个 HTML 元素提供了方法,如果创建 HTML 元素的样式化组件,可以直接调用对应方法:
// 直接调用对应方法 styled.div` width: 100px; ` // 将标签名作为参数传入,调用返回的方法 styled('div')` color: #333; `
使用方式
styled
也有两种使用方式:
- 模板字符串形式
- 普通函数调用
import styled from '@emotion/styled' const Button1 = styled.button` color: blue; ` const Button2 = styled.button({ color: 'red' }) const Button3 = styled('button')` color: pink; ` const Button4 = styled('button')({ color: 'green' }) function App() { return ( <div> <Button1>Button1</Button1> <Button2>Button2</Button2> <Button3>Button3</Button3> <Button4>Button4</Button4> </div> ) } export default App
基于 props 设置样式
样式化组件定义样式时,可以通过 props
获取组件接收的属性,根据属性设置样式。
- 模板字符串中可以在
${}
中使用一个函数,函数接收一个props
参数,最终返回一段 css - 普通函数调用时,可以传递一个函数,函数接收一个
props
参数,最终返回一个对象或数组对象
import styled from '@emotion/styled' const Div = styled.div` width: 200px; background: ${props => props.bgColor || 'skyblue'}; height: 100px; ` const Button = styled.button(props => ({ borderRadius: 10, fontSize: props.fontSize || 20 })) function App() { return ( <div> <Div> <Button>天蓝色 20px</Button> </Div> <Div bgColor="pink"> <Button fontSize={30}>粉色 30px</Button> </Div> </div> ) } export default App
覆盖默认样式
styled
定义样式时,可以向第二个参数传入一个函数,函数接收 props
返回样式对象,用以覆盖第一个参数设置的默认样式。
import styled from '@emotion/styled' const Div = styled.div` width: 200px; background: ${props => props.bgColor || 'skyblue'}; height: 100px; ` const Button = styled.button({ // 默认样式 borderRadius: 10, fontSize: 20 }, props => ({ // 覆盖的样式 fontSize: props.fontSize })) function App() { return ( <div> <Div> <Button>天蓝色 20px</Button> </Div> <Div bgColor="pink"> <Button fontSize={30}>粉色 30px</Button> </Div> </div> ) } export default App
上面的 粉色 30px
的 Button 样式最终为:
border-radius: 10px; font-size: 20px; font-size: 30px;
为组件添加样式
styled
还可以为 React 组件添加样式,只需要这个组件接收 className
。
类似传递普通元素的名称,给组件添加样式,只需将接收 className
的组件传递给 styled
。
import styled from '@emotion/styled' function Basic({ className }) { return <div className={className}>Basic</div> } const Fancy = styled(Basic)` width: 100px; height: 100px; background: pink; ` const Fancy2 = styled(Basic)({ width: 100, height: 100, background: 'skyblue' }) function App() { return ( <div> <Fancy /> <Fancy2 /> </div> ) } export default App
设置子组件样式
styled
创建的组件,应用在定义样式的模板字符串或样式对象中时,Emotion 会将其解析为对应的 className
。
样式模板中嵌套的样式 和 样式对象中嵌套的对象,会追加上级样式的类名
import styled from '@emotion/styled' const Child = styled.span({ color: 'red' }) const Parent = styled.div` ${Child} { color: blue; } ` const Parent2 = styled.div({ // 使用ES6的表达式方式定义属性名 [Child]: { color: 'green' } }) function App() { return ( <div> <Child>红色的字</Child> <Parent> <Child>蓝色的字</Child> </Parent> <Parent2> <Child>绿色的字</Child> </Parent2> </div> ) } export default App
CSS 选择器 - &
&
表示组件或元素本身
import styled from '@emotion/styled' const Div = styled.div` width: 200px; height: 200px; background: skyblue; &:hover { background: pink; } & > span { color: red; } ` function App() { return ( <div> <Div> <span>red</span> </Div> </div> ) } export default App
as 属性
要使用样式化组件的样式,但是想更换渲染的元素,可以使用 as
属性。
import styled from '@emotion/styled' const Span = styled.span` &:hover { color: blue; } ` function App() { return ( <div> <Span as="a" href="http://xxx.com">a标签</Span> </div> ) } export default App
样式组合
css
属性可以接收对象数组。数组中的成员可以是 css
方法创建的样式。
数组后面的样式优先级高于前面的样式。
import { css } from '@emotion/react' const success = css` background: pink; color: green; ` const danger = css` font-size: 20px; color: red; ` function App() { return ( <div> <button css={[success, danger]}>红色</button> <button css={[danger, success]}>绿色</button> </div> ) } export default App
定义全局样式
Emotion 提供一个 Global 组件用于定义全局样式。
Global 组件的 styles
属性接收和 css
属性一样的值,它定义的样式会应用到全局。
- Global 组件不需要包裹任何内容
- Global 组件设置的样式不会生成
className
, 它类似直接在项目中引入样式
import { Global, css } from '@emotion/react' const styles = css` body { margin: 0; background: skyblue; } a { text-decoration: none; color: green; } .main { width: 200px; height: 200px; background: pink; } ` function App() { return ( <div class="main"> <Global styles={styles} /> <a href="http://xxx.com">a标签</a> </div> ) } export default App
定义关键帧动画
Emotion 提供 keyframes
方法创建关键帧动画,同样可以使用字符串或对象方式定义。
然后在定义样式的时候使用即可。
import { keyframes, css } from '@emotion/react' const move = keyframes` 0% { background: skyblue; left: 0; top: 0; } 100%{ background: tomato; left: 600px; top: 300px; } ` const box = css` width: 100px; height: 100px; position: absolute; animation: ${move} 2s ease infinite alternate; ` function App() { return ( <div css={box}>App works</div> ) } export default App
Theming 主题
Emotion 可以将 ThemeProvider
组件放置到视图最外层,通过 theme
属性定义主题样式对象。
这个主题样式对象不是可以直接用的 css 样式,而是用于定义样式的一些
key-value
。
有三种使用方式:
- 在样式化组件中通过
props.theme
访问主题对象。 - 当
css
属性设置为一个函数时,它接收的参数就是主题对象。 - 使用
useTheme
钩子函数获取主题对象。
import styled from '@emotion/styled' import { useTheme } from '@emotion/react' const PrimaryDiv = styled.div` color: ${props => props.theme.colors.primary} ` const primaryColor = theme => ({ color: theme.colors.primary }) function App() { const theme = useTheme() return ( <div> <PrimaryDiv>样式化组件</PrimaryDiv> <div css={primaryColor}>向css属性传递一个函数</div> <div css={{color: theme.colors.primary}}>使用钩子函数</div> </div> ) } export default App
这篇关于CSS-IN-JS 方案 - Emotion 库的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27Ant Design Vue入门指南:轻松搭建美观界面
- 2024-11-27Vue3项目实战:从零开始的完整指南
- 2024-11-27Vue CLI多环境配置资料详解
- 2024-11-27Vue3+Vite资料:新手入门教程
- 2024-11-27Vue3阿里系UI组件资料入门教程
- 2024-11-27如何集成Ant Design Vue的图标资料
- 2024-11-27如何集成Ant Design Vue图标资料
- 2024-11-27Vue CLI多环境配置资料:新手教程
- 2024-11-27Vue3的阿里系UI组件资料入门指南
- 2024-11-27Vue3公共组件资料详解与实战教程