持续创作,加速成长!这是我参与「掘金日新计划 · 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,可以敲代码时 提示行内样式选择