css-in-js 探究(二)

781 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

主要讲emotion/react、styles里面的一些使用和源码实现,还有介绍一下emotion的其他用法。

emotion/react

如果是vite环境下,需要在vite.config.js中配置

jsx运行时,要经典的。

export default defineConfig({
  plugins: [react({
    jsxRuntime: 'classic',
  })]
})

使用

/** @jsx jsx */
import { jsx, css } from '@emotion/react';
const theme = css({
  color:red;
})
function App() {
  return (
      <div css={theme}>
        some other text
      </div>
  )
}
---渲染成  这里用jsx劫持了babel的编译,原本是class=》className
<div className='css-hash-app'>

源码

  • jsx.jsx 自定义生成节点
原本是 React.createElement(type, [props], [...children]);
这是emotion自己实现的React.createElement 生成React元素或说虚拟DOM的方法
// type:div,	props:{css:{name:'hash',styles:'color:red;'}}
function jsx(type, props, ...children) {
  return (
    <Emotion {...props} type={type}>{children}</Emotion>
  )
}
function Emotion(props) {
  const serialized = props.css; //{name:'hash',styles:'color:red;'}
  const { css, ...newProps } = props;
  newProps.className = 'css-' + serialized.name;
  const WrappedComponent = props.type;//div
    // 将css这个属性改成className、
  return (
    <>
      // 插入style
      <Insertion serialized={serialized} />
      // 被改造的div
      <WrappedComponent {...newProps} />
    </>
  )
}

import { useLayoutEffect } from 'react';
import insertStyles from './insertStyles';
function Insertion({ serialized }) {
    //渲染前运行的钩子、useLayoutEffect
    //布局前:有useInsertionEffect,主是专门为了CSS-IN-JS来添加的一个钩子
    //渲染后 useEffect
  useLayoutEffect(() => {
      //之前有这个代码
    insertStyles(serialized);
  }, []);
  return null;
}
export default Insertion;

emotion/styled

一种创建附加样式的react组件的方法

import styled from '@emotion/styled'
const Button = styled.button({
	color:'red'
})
return <Button>按钮</Button>

源码

import { serializeStyles } from "../serialize";
import { Insertion } from "../utils";
function createStyled(tag) { //tag :button
    //返回一个函数,接收参数 {color:'red'}
  return function (...args) { // Button 
      //返回一个 函数组件
    function Styled(props) {
        // 之前讲过,不赘述
      const serialized = serializeStyles(args);
      const className = `css-` + serialized.name;
      const newProps = { ...props, className };
      const FinalTag = props.as || tag;
      return (
        <>
          <Insertion serialized={serialized} />
          <FinalTag {...newProps} />
        </>
      );
    }
    return Styled;
  };
}
const newStyled = createStyled.bind();
const tags = ["div", "button"];
tags.forEach((tagName) => {
    //导出一个style函数对象,里面有"button"等属性。  style.button = newStyled('button')
  newStyled[tagName] = newStyled(tagName);
});
export default newStyled;

//我觉得用对象应该也没问题 但是有styled(Hello)`color:red` 这种形式的,所以需要用到bind,下面有讲
// let a = {}
// tags.forEach(tagName => {
//   a[tagName] = createStyled(tagName);
// });

加props属性

import styled from '@emotion/styled'
const Button = styled.button({
	color:'red'
},(props)=>({color:props.color}))
return <Button color={'green'}>按钮</Button>
// 后面的也green加进去

源码

把上面的 const serialized = serializeStyles(args);
改成 const serialized = serializeStyles(args, props);

function serializeStyles(args, props) {
  // if (typeof args === 'object' && args.styles !== undefined) {
  //   return args;
  // }
  // let styles = '';
  // const strings = args[0];
  // if (strings.raw === undefined) {//说明传的是对象
  //   styles += handleInterpolation(strings, props); //这里防止第一个参数就是props函数
  // } else {
  //   styles += strings[0];//styles="color:red"
  // }
    // 加了这个代码
    // 传了两个参数,在styles后面加 props样式
  for (let i = 1; i < args.length; i++) {
      //args[1]:(props)=>({color:props.color}),props:color={'green'}
    styles += handleInterpolation(args[i], props);
  }
  // const name = hashString(styles);
  // return {
  //   name,
  //   styles
  // };
}
// 把 props:color={'green'} 传入 (props)=>({color:props.color})去执行,得到 {color:green},然后解析成字符串返回。
function handleInterpolation(interpolation, props) {
  switch (typeof interpolation) {
    case "object": {
      return createStringFromObject(interpolation);
    }
    case "function": {
      var result = interpolation(props);
      return handleInterpolation(result);
    }
  }
}
function createStringFromObject(obj) {
  let string = "";
  for (const key in obj) {
    string += key + ":" + obj[key] + ";";
  }
  return string;
}
  • 这个方案并不是很受认可。

给组件添加样式

上面的style代码就能满足

import styled from '@emotion/styled'
function Hello({className}){
    return <button className={className}>ll</button>
}
const Button = styled(Hello)`
	color:red;
`
...
return <Button>按钮</Button>

源码

// 就是用上面的代码
function createStyled(tag) { //传入组件 
  return function (...args) { // 模板字符串 ,最开始的版本
    function Styled(props) {
      const serialized = serializeStyles(args, props);
      const className = `css-` + serialized.name;
      const newProps = { ...props, className };
      const FinalTag = props.as || tag;
      return (
        <>
          <Insertion serialized={serialized} />
      //把className赋值给 传入组件使用。
          <FinalTag {...newProps} />
        </>
      );
    }
    return Styled;
  };
}
const newStyled = createStyled.bind();
export default newStyled;

其他用法

先装插件 @emotion/babel-plugin

export default defineConfig({
  plugins: [react({
    jsxRuntime: 'classic',
    babel: {
      plugins: ["@emotion/babel-plugin"]
    }
  })]
})
  • 父子组件
import styled from '@emotion/styled'
const C = styled.div({color:'red'})
const P = Style.div({
    background:'green',
    [C]:{
        color:'blue'
    }
})
return (
	<P><C></C></P>
)
渲染 
<style>.p-hash{background:'green'}  .p-hash .c-hash{color:'blue'} </style>
<div className='p-hash'><div className='c-hash'></div></div>
  • 也可以直接用sass/less嵌套
const Button = styled.div`
	color:red;
	&:hover{
		color:green;
	}
`
  • 可以组合css样式
<Button css={[a,b]}>
  • 全局样式
import { jsx, css, Global, keyframes, ThemeProvider } from "@emotion/react";
const reset = css`
	body{margin:0};
`
return (
	<>
    	<Global styles={reset}/>
		...
    </>
)
  • 动画
import { jsx, css, Global, keyframes, ThemeProvider } from "@emotion/react";
const bounce = keyframes`
	from{
		transform....
	}
	to{...}
`
const animation = css`
	...
	animation:${bounce} 1s ...
`
return <div css={animation}>
  • 主题
/** @jsx jsx */
import { jsx, ThemeProvider } from '@emotion/react'

const theme = {
  colors: {
    primary: 'hotpink'
  }
}

render(
  <ThemeProvider theme={theme}>
    <div css={theme => ({ color: theme.colors.primary })}>
      some other text
    </div>
  </ThemeProvider>
)

vscode插件

vscode-styled-component,可以敲代码时 提示行内样式选择