首页 前端知识 React 中 CSS in JS 的最佳实践,详解系列文章

React 中 CSS in JS 的最佳实践,详解系列文章

2024-04-29 12:04:04 前端知识 前端哥 542 94 我要收藏

4-1、emotion效果

首先让我们来看一下emotion做了什么,这是一个使用了emotion的React组件:

import React from ‘react’;

import { css } from ‘emotion’

const color = ‘white’

function App() {

return (

padding: 32px;

background-color: hotpink;

font-size: 24px;

border-radius: 4px;

&:hover {

color: ${color};

}

`}>

This is emotion test

);

}

export default App;

这是渲染出的html:

React App This is React.js test

我们可以看到emotion实际上是做了以下三个事情:

  1. 将样式写入模板字符串,并将其作为参数传入css方法。

  2. 根据模板字符串生成class名,并填入组件的class="xxxx"中。

  3. 将生成的class名以及class内容放到<style>标签中,然后放到html文件的head中。

4-2、emotion初始化

首先我们可以看到,在emotion实例化的时候(也就是我们在组件中import { css } from 'emotion'的时候),首先调用了create-emotion包中的createEmotion方法,这个方法的主要作用是初始化emotion的cache(用于生成样式并将生成的样式放入<head>中,后面会有详细介绍),以及初始化一些常用的方法,其中就有我们最常使用的css方法。

import createEmotion from ‘create-emotion’

export const {

injectGlobal,

keyframes,

css,

cache,

//…

} = createEmotion()

let createEmotion = (options: *): Emotion => {

// 生成emotion cache

let cache = createCache(options)

// 用于普通css

let css = (…args) => {

let serialized = serializeStyles(args, cache.registered, undefined)

insertStyles(cache, serialized, false)

return ${cache.key}-${serialized.name}

}

// 用于css animation

let keyframes = (…args) => {

let serialized = serializeStyles(args, cache.registered)

let animation = animation-${serialized.name}

insertWithoutScoping(cache, {

name: serialized.name,

styles: @keyframes ${animation}{${serialized.styles}}

})

return animation

}

// 注册全局变量

let injectGlobal = (…args) => {

let serialized = serializeStyles(args, cache.registered)

insertWithoutScoping(cache, serialized)

}

return {

css,

injectGlobal,

keyframes,

cache,

//…

}

}

4-3、emotion cache

emotion的cache用于缓存已经注册的样式,也就是已经放入head中的样式。在生成cache的时候,使用一款名为Stylis的CSS预编译器对我们传入的序列化的样式进行编译,同时它还生成了插入样式方法(insert)。

let createCache = (options?: Options): EmotionCache => {

if (options === undefined) options = {}

let key = options.key || ‘css’

let stylisOptions

if (options.prefix !== undefined) {

stylisOptions = {

prefix: options.prefix

}

}

let stylis = new Stylis(stylisOptions)

let inserted = {}

let container: HTMLElement

if (isBrowser) {

container = options.container || document.head

}

let insert: (

selector: string,

serialized: SerializedStyles,

sheet: StyleSheet,

shouldCache: boolean

) => string | void

if (isBrowser) {

stylis.use(options.stylisPlugins)(ruleSheet)

insert = (

selector: string,

serialized: SerializedStyles,

sheet: StyleSheet,

shouldCache: boolean

): void => {

let name = serialized.name

Sheet.current = sheet

stylis(selector, serialized.styles)  // 该方法会在对应的selector中添加对应的styles

if (shouldCache) {

cache.inserted[name] = true

}

}

}

const cache: EmotionCache = {

key,

sheet: new StyleSheet({

key,

container,

nonce: options.nonce,

speedy: options.speedy

}),

nonce: options.nonce,

inserted,

registered: {},

insert

}

return cache

}

4-4、emotion css方法

这是emotion中比较重要的方法,它其实是调用了serializeStyles方法来处理css方法中的参数,然后使用insertStyles方法将其插入html文件中,最后返回class名,然后我们在组件中使用<div className={css('xxxxx')}></div>的时候就能正确指向对应的样式了。

let css = (…args) => {

let serialized = serializeStyles(args, cache.registered, undefined)

insertStyles(cache, serialized, false)

return ${cache.key}-${serialized.name}

}

serializeStyles方法是一个比较复杂的方法,它的主要作用是处理css方法中传入的参数,生成序列化的class。

export const serializeStyles = function(

args: Array,

registered: RegisteredCache | void,

mergedProps: void | Object

): SerializedStyles {

// 如果只传入一个参数,那么直接返回

if (

args.length === 1 &&

typeof args[0] === ‘object’ &&

args[0] !== null &&

args[0].styles !== undefined

) {

return args[0]

}

// 如果传入多个参数,那么就需要merge这些样式

let stringMode = true

let styles = ‘’

let strings = args[0]

if (strings == null || strings.raw === undefined) {

stringMode = false

styles += handleInterpolation(mergedProps, registered, strings, false)

} else {

styles += strings[0]

}

// we start at 1 since we’ve already handled the first arg

for (let i = 1; i < args.length; i++) {

styles += handleInterpolation(

mergedProps,

registered,

args[i],

styles.charCodeAt(styles.length - 1) === 46

)

if (stringMode) {

styles += strings[i]

}

}

// using a global regex with .exec is stateful so lastIndex has to be reset each time

labelPattern.lastIndex = 0

let identifierName = ‘’

let match

while ((match = labelPattern.exec(styles)) !== null) {

identifierName +=

‘-’ +

match[1]

}

let name = hashString(styles) + identifierName

return {

name,

styles

}

}

// 生成对应的样式

function handleInterpolation(

mergedProps: void | Object,

registered: RegisteredCache | void,

interpolation: Interpolation,

couldBeSelectorInterpolation: boolean

): string | number {

// …

}

insertStyles方法其实比较简单,首先读取cache中是否insert了这个style,如果没有,则调用cache中的insert方法,将样式插入到head中。

export const insertStyles = (

cache: EmotionCache,

serialized: SerializedStyles,

isStringTag: boolean

) => {

let className = ${cache.key}-${serialized.name}

if (cache.inserted[serialized.name] === undefined) {

let current = serialized

do {

let maybeStyles = cache.insert(

.${className},

current,

cache.sheet,

true

)

current = current.next

} while (current !== undefined)

}

}

五、一些总结


总体来说,如果是进行基础组件的开发,那么使用“有规范约束”的原生css(比如遵守BEM规范的css),或者less之类的预处理语言会比较合适,这能最大幅度地减小组件库的体积,也能为业务方提供样式覆盖的能力。

如果是进行业务开发,个人比较推荐css-in-js的方案,因为它不仅能够做到在组件中直接编写css,同时也能够直接使用组件中的js变量,能有效解决“组件样式随着数据变化”的问题。另外,在业务开发中,由于迭代速度快,开发人员流动性相对大一些,我们直接使用规范对css进行约束会有一定的风险,当项目规模逐渐变大后代码的可读性会很差,也会出现css互相影响的情况。

另外使用CSS Module在业务开发中也是一种不错的方案,但一般大部分前端开发者会使用“-”来为样式命名,但放到组件中就只能使用style['form-item']这样的方式去引用了,我个人是不太喜欢这种风格的写法的。

不过没有一项技术是能解决全部问题的,针对不同的场景选择最合适的技术才是最优解。如果团队中还未使用这些技术,并且在开发组件的样式时遇到了文中所述的“传统css在组件开发中的痛点”,那么我建议去尝试一下css-in-js或者css module,具体选择何种方案就看团队成员更能接受哪种写法了。如果已经使用了css-in-js或者css module,那么继续使用即可,这些都是能够cover现有组件开发的场景的。

关于React的更多技巧,推荐你学习这篇:6个React Hook最佳实践技巧

六、写在最后


目前主流的前端框架,像vue和angular都针对css的作用域进行了额外的处理,比如vue的scoped,而react这里则是将css作用域处理完全交给了社区,也就出现了各种各样的css-in-js框架,虽说这两种做法其实没什么高下之分,但就个人观感来看,用来用去还是觉得vue sfc中的scoped最香(滑稽)

转载请注明出处或者链接地址:https://www.qianduange.cn//article/6450.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!