当前比较流行的 CSS-in-JS 项目有 styled-jsx 和 styled-components 两个,说实话 styled-jsx 可以通过插件支持 PostCSS 差不多已经完美,使用 styled-components 的话开发思路需要一些转变,相对 styled-jsx 来说有些繁琐(支持 css prop 但有点内联 style 的意思),倒是符合 React 的组件哲学。
create-react-app 工程中支持 styled-jsx 有两种方式,一是使用 babel macro,一是使用 babel plugin,这两种方式都有一些问题,下面分别进行说明。
使用 babel macro
create-react-app 原生支持 babel macro,而 styled-jsx 自身也支持 babel macro,因此不需要对工程做任何配置即可以直接 styled-jsx,示例如下:
import classNames from 'classnames';
import {resolve} from 'styled-jsx/macro';
const example = resolve`
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`;
class App extends Component {
render() {
return (
<div className={classNames(example.className, "App")}>
<header className={classNames(example.className, "App-header")}>
<img src={logo} className={classNames(example.className, "App-logo")} alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className={classNames(example.className, "App-link")}
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
{example.styles}
</div>
);
}
}
使用 bable macro 存在如下几个问题:
- 显然这种使用 resolve 定义样式的方式与我们所熟知的
<style jsx>...</style>
方式不一致; - 使用类选择器时需要显式使用 classNames 进行连接;
- 不能使用 styled-jsx 插件[1],这样使用 styled-jsx 的意义就大大降低了;
- 对 typescript 的支持不好,当前 ‘styled-jsx/macro’ 和 ‘styled-jsx/style’ 两个模块在 @types/styled-jsx 包中均未定义,因此如有需要,需要自己定义
.d.ts
文件;
可以为 CRA 工程简单的定义所需的 .d.ts
文件以解决编译错误:
~$ vi src/example.d.ts
declare module 'styled-jsx/macro'
declare module 'styled-jsx/style'
当然,styled-components 也可以以 babel macro 的方式使用,并使用 polished 来实现类似 PostCSS 提供的功能。
使用 babel plugin
这是熟悉的 styled-jsx 使用方式:
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
{ /*language=CSS*/ }
<style jsx>{`
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`}</style>
</div>
);
}
}
但此时需要 eject 以支持 styled-jsx 配置(注意:eject 要求 git 工程工作目录是 clean 的):
~$ yarn eject
~$ vi config/webpack.config.js
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-prettier,-svgo![path]',
},
},
},
],
['styled-jsx/babel', { "plugins": ["styled-jsx-plugin-postcss"] }],
],
...
},
此处添加了 PostCSS 插件,因此可以使用各种 PostCSS 的特性:
~$ yarn add styled-jsx-plugin-postcss
~$ yarn add postcss-easy-media-query
~$ vi postcss.config.js
const postcssEasyMediaQuery = require(`postcss-easy-media-query`);
module.exports = () => ({
plugins: [
postcssEasyMediaQuery({
breakpoints: {
tablet: 600,
desktop: 1024
}
}),
],
});
可惜的是,在 react-app-rewired 逐渐淡出的情况下,yarn eject 显得有些无奈。
混合使用 babel macro 与 babel plugin
在某些特殊需求下,可能会用到:
import classNames from 'classnames';
import {resolve} from 'styled-jsx/macro';
import _JSXStyle from 'styled-jsx/style';
const example = resolve`
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
`;
class App extends Component {
render() {
return (
<div className={classNames(example.className, "App")}>
<header className="App-header">
<img src={logo} className={classNames(example.className, "App-logo")} alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
{example.styles}
{ /*language=CSS*/ }
<style jsx>{`
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`}</style>
</div>
);
}
}
注意不要删除 import _JSXStyle
这条没有用到的 import 指令,否则会出现类似如下的报错:
Module parse failed: Identifier '_JSXStyle' has already been declared (8:7)
You may need an appropriate loader to handle this file type.
| var _jsxFileName = "/home/runsisi/workingcopy/test/app/src/App.js";
| import _JSXStyle from "styled-jsx/style";
> import _JSXStyle from "styled-jsx/style";
| import React, { Component } from 'react';
参考资料
[1] Can you use Styled JSX plugins with Babel macros?
[2] Static Type Checking
https://reactjs.org/docs/static-type-checking.html#typescript
[3] A starter template for TypeScript and React
https://github.com/Microsoft/TypeScript-React-Starter#typescript-react-starter
[4] Creating TypeScript typings for existing React components
https://templecoding.com/blog/2016/03/31/creating-typescript-typings-for-existing-react-components/
[5] Clarify how to add a new declaration file
https://github.com/Microsoft/TypeScript/issues/21344
[6] Adding Custom Type Definitions to a Third-Party Library
https://www.detroitlabs.com/blog/2018/02/28/adding-custom-type-definitions-to-a-third-party-library/
最后修改于 2019-02-23