1. CSS模块化方案 #

1.1 CSS 命名方法 #

1.2 CSS Modules #

1.3 CSS-in-JS #

1.3.1 案例 #

1.3.2 CSS-IN-JS优缺点 #

1.3.3 选型 #

2. emotion #

3.@emotion/css #

3.1 模板字符串 #

3.1.1 src\main.jsx #

src\main.jsx

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
  <App />,
  document.getElementById('root')
)

3.1.2 src\App.jsx #

src\App.jsx

import { css } from './@emotion/css'
const className = css`
 color: red;
`;
function App() {
  return (
    <div className={className}>
      App
    </div>
  )
}
export default App

3.1.3 css\index.js #

src\@emotion\css\index.js

export { default as css } from './css';

3.1.4 css.js #

src\@emotion\css\css.js

import { serializeStyles } from '../serialize';
import { insertStyles } from '../utils';
function css(...args) {
  const serialized = serializeStyles(args);
  insertStyles(serialized);
  return 'css' + "-" + serialized.name;
}
export default css; 

3.1.5 serialize\index.jsx #

src\@emotion\serialize\index.jsx

export { default as serializeStyles } from './serializeStyles';

3.1.6 serializeStyles.jsx #

src\@emotion\serialize\serializeStyles.jsx

import { hashString } from '../utils';
function serializeStyles(args) {
  let styles = '';
  const strings = args[0];
  styles += strings[0];
  const name = hashString(styles);
  return { name, styles }
}
export default serializeStyles;

3.1.7 utils\index.jsx #

src\@emotion\utils\index.jsx

export { default as insertStyles } from './insertStyles'
export { default as hashString } from './hashString';

3.1.8 hashString.jsx #

src\@emotion\utils\hashString.jsx

function hashString(keys) {
  let val = 10000000;
  for (let i = 0; i < keys.length; i++) {
    val += keys.charCodeAt(i);
  }
  return val.toString(16).slice(0, 6);
}
export default hashString;

3.1.9 insertStyles.jsx #

src\@emotion\utils\insertStyles.jsx

function insertStyles(serialized) {
  const className = 'css' + "-" + serialized.name;
  const rule = "." + className + "{" + serialized.styles + "}";
  const tag = document.createElement('style');
  tag.setAttribute('data-emotion', 'css');
  tag.appendChild(document.createTextNode(rule));
  document.head.appendChild(tag);
}
export default insertStyles;

3.2 对象 #

3.2.1 src\App.jsx #

src\App.jsx

import { css } from './@emotion/css'
+const className = css(
+  {
+    color: 'red'
+  }
+);
function App() {
  return (
    <div className={className}>
      App
    </div>
  )
}
export default App

3.2.2 serializeStyles.jsx #

src\@emotion\serialize\serializeStyles.jsx

import { hashString } from '../utils';
function serializeStyles(args) {
  var styles = '';
  var strings = args[0];
+ if (strings.raw === undefined) {
+   styles += handleInterpolation(strings);
+ } else {
+   styles += strings[0];
+ }
  var name = hashString(styles);
  return { name, styles }
}

+function handleInterpolation(interpolation) {
+  switch (typeof interpolation) {
+    case 'object': {
+      return createStringFromObject(interpolation);
+    }
+  }
+}
+function createStringFromObject(obj) {
+  var string = '';
+  for (var key in obj) {
+    var value = obj[key];
+    string += key + ":" + value + ";";
+  }
+  return string;
+}
export default serializeStyles;

4.@emotion/react #

npm install @emotion/react --save

4.1 vite.config.js #

vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
  plugins: [react(
    {
+     jsxRuntime: 'classic'
    }
  )]
})

4.2 src\App.jsx #

src\App.jsx

+/** @jsx jsx */
+import { css, jsx } from './@emotion/react'
+const styles = css(
+  {
+    color: 'red'
+  }
+);
function App() {
  return (
+   <div css={styles}>
      App
    </div>
  )
}
export default App

4.3 react\index.jsx #

src\@emotion\react\index.jsx

export { default as css } from './css';
export { default as jsx } from './jsx';

4.4 css.jsx #

src\@emotion\react\css.jsx

import { serializeStyles } from '../serialize';
function css(...args) {
  return serializeStyles(args);
}
export default css; 

4.5 jsx.jsx #

src\@emotion\react\jsx.jsx

import { serializeStyles } from '../serialize';
import { Insertion } from '../utils';
function Emotion(props) {
  const serialized = serializeStyles(props.css);
  const { css, ...newProps } = props
  newProps.className = 'css' + "-" + serialized.name;
  const WrappedComponent = props.type;
  return (
    <>
      <Insertion serialized={serialized} />
      <WrappedComponent {...newProps} />
    </>
  )
}

function jsx(type, props, ...children) {
  return (
    <Emotion {...props} type={type}>
      {children}
    </Emotion >
  )
}
export default jsx;

4.6 serializeStyles.jsx #

src\@emotion\serialize\serializeStyles.jsx

import { hashString } from '../utils';
function serializeStyles(args) {
+  if (typeof args === 'object' && args.styles !== undefined) {
+    return args;
+  }
  var styles = '';
  var strings = args[0];
  if (strings == null || strings.raw === undefined) {
    styles += handleInterpolation(strings);
  } else {
    styles += strings[0];
  }
  var name = hashString(styles);
  return { name, styles }
}

function handleInterpolation(interpolation) {
  switch (typeof interpolation) {
    case 'object': {
      return createStringFromObject(interpolation);
    }
  }
}
function createStringFromObject(obj) {
  var string = '';
  for (var key in obj) {
    var value = obj[key];
    string += key + ":" + value + ";";
  }
  return string;
}
export default serializeStyles;

4.7 Insertion.jsx #

src\@emotion\utils\Insertion.jsx

import { useLayoutEffect } from 'react';
import insertStyles from './insertStyles';
function Insertion({ serialized }) {
  useLayoutEffect(() => {
    insertStyles(serialized);
  });
  return null;
};
export default Insertion;

4.8 utils\index.jsx #

src\@emotion\utils\index.jsx

export { default as insertStyles } from './insertStyles';
export { default as hashString } from './hashString';
+export { default as Insertion } from './Insertion';

5. @emotion/styled #

npm install @emotion/styled --save

5.1 src\App.jsx #

src\App.jsx

+import styled from './@emotion/styled'
+const Button = styled.button(
  {
    color: 'red'
  }
+);
function App() {
  return (
+   <Button>Button</Button>
  )
}
export default App

5.2 styled\index.jsx #

src\@emotion\styled\index.jsx

import { serializeStyles } from '../serialize';
import { Insertion } from '../utils';
function createStyled(tag) {
  return function (...args) {
    function Styled(props) {
      const serialized = serializeStyles(args);
      const className = 'css' + "-" + serialized.name;
      const newProps = { ...props };
      newProps.className = className;
      const FinalTag = tag;
      return (
        <>
          <Insertion serialized={serialized} />
          <FinalTag {...newProps} />
        </>
      )
    }
    return Styled;
  }
}
const newStyled = createStyled.bind();
const tags = ['button', 'div'];
tags.forEach(function (tagName) {
  newStyled[tagName] = newStyled(tagName);
});
export default newStyled;

6. 根据props属性覆盖样式 #

6.1 src\App.jsx #

src\App.jsx

import styled from './@emotion/styled'
const Button = styled.button(
  {
    background: 'green',
    color: 'red'
+ }, props => ({
+   color: props.color
+ })
);
function App() {
  return (
+   <Button color="white">Button</Button>
  )
}
export default App

6.2 styled\index.jsx #

src\@emotion\styled\index.jsx

import { serializeStyles } from '../serialize';
import { Insertion } from '../utils';
function createStyled(tag) {
  return function (...args) {
    function Styled(props) {
+     const serialized = serializeStyles(args, props);
      const className = 'css' + "-" + serialized.name;
      const newProps = { ...props };
      newProps.className = className;
      const FinalTag = tag;
      return (
        <>
          <Insertion serialized={serialized} />
          <FinalTag {...newProps} />
        </>
      )
    }
    return Styled;
  }
}
const newStyled = createStyled.bind();
const tags = ['button', 'div'];
tags.forEach(function (tagName) {
  newStyled[tagName] = newStyled(tagName);
});
export default newStyled;

6.3 serializeStyles.jsx #

src\@emotion\serialize\serializeStyles.jsx

import { hashString } from '../utils';
+function serializeStyles(args, props) {
  if (typeof args === 'object' && args.styles !== undefined) {
    return args;
  }
  var styles = '';
  var strings = args[0];
  if (strings.raw === undefined) {
+   styles += handleInterpolation(props, strings);
  } else {
    styles += strings[0];
  }
  for (var i = 1; i < args.length; i++) {
    styles += handleInterpolation(props, args[i]);
  }
  var name = hashString(styles);
  return { name, styles }
}

+function handleInterpolation(props, interpolation) {
  switch (typeof interpolation) {
    case 'object': {
+     return createStringFromObject(props, interpolation);
    }
+   case 'function': {
+     if (props !== undefined) {
+       var result = interpolation(props);
+       return handleInterpolation(props, result);
+     }
+   }
  }
}
function createStringFromObject(obj) {
  var string = '';
  for (var key in obj) {
    var value = obj[key];
    string += key + ":" + value + ";";
  }
  return string;
}
export default serializeStyles;

7. 为组件添加样式 #

7.1 src\App.jsx #

src\App.jsx

import styled from './@emotion/styled'
+function Hello({ className }) {
+  return <button className={className}>Hello</button>
+}
+const RedHello = styled(Hello)`
+  color:red;
+`
function App() {
  return (
+   <RedHello>Button</RedHello>
  )
}
export default App;

8. 父组件设置子组件 #

.css-1wvgi8y-Parent{background:green;}
.css-10c6c3i-Child{color:red;}
.css-1wvgi8y-Parent .eesff8e1{color:blue;}
<div class="css-1wvgi8y-Parent eesff8e0">
  <div class="css-10c6c3i-Child eesff8e1">App</div>
</div>

8.1 vite.config.js #

npm install @emotion/babel-plugin --save

vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
  plugins: [react(
    {
      jsxRuntime: 'classic',
      babel: {
+       plugins: ['@emotion/babel-plugin']
      }
    }
  )]
})

8.2 src\App.jsx #

src\App.jsx

import styled from '@emotion/styled'
const Child = styled.div({
  color: 'red'
})
const Parent = styled.div({
  background: 'green',
  [Child]: {
    color: 'blue'
  }
})
function App() {
  return (
    <>
      <Parent>
        <Child>Child</Child>
      </Parent>
      <Child>Child</Child>
    </>
  )
}
export default App

9. 嵌套选择器 #

9.1 src\App.jsx #

src\App.jsx

import styled from '@emotion/styled'
const Container = styled.div`
  width:200px;
  height:200px;
  background:lightgray;
  &:hover{
    background:pink;
  }
  & > p {
    color:green;
  }
`
function App() {
  return (
    <Container>
      Container
      <p>span</p>
    </Container>
  )
}
export default App

10. as属性 #

10.1 src\App.jsx #

src\App.jsx

import styled from '@emotion/styled'
const Button = styled.button`
 color:red
`

function App() {
  return (
    <Button as="a">
      Button
    </Button>
  )
}
export default App

11.组合样式 #

11.1 src\App.jsx #

src\App.jsx

/** @jsx jsx */
import { jsx, css } from '@emotion/react'
const base = css`
  color:white;
`
const warning = css`
  background:orange;
`
function App() {
  return (
    <button css={[base, warning]}>Button</button>
  )
}
export default App

12.全局样式 #

12.1 src\App.jsx #

src\App.jsx

/** @jsx jsx */
import { jsx, css, Global } from '@emotion/react'
const reset = css`
  body{
    margin:0;
  }
  a{
    color:red;
  }
`
function App() {
  return (
    <>
      <Global styles={reset} />
      <a>我是a标记</a>
    </>
  )
}
export default App

13.关键帧动画 #

13.1 src\App.jsx #

src\App.jsx

/** @jsx jsx */
import { jsx, css, keyframes } from '@emotion/react'

const bounce = keyframes`
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(100px);
  }
`
const base = css`
  width:100px;
  height:100px;
  background: green;
  position: absolute;
  animation: ${bounce} 1s ease infinite alternate;
`;
function App() {
  return (
    <div css={[base]}>
      文本
    </div>
  )
}
export default App

14.模板字符串 #

function tag(stringArr, ...values) {
  console.log(stringArr.raw);
  let output = "";
  let index;
  for (index = 0; index < values.length; index++) {
    output += stringArr[index] + values[index];
  }
  output += stringArr[index]
  return output;
}
let v1 = 1;
let v2 = 2;

let result = tag`a${v1}b${v2}c`;
console.log(result);