Categories
程式開發

CSS-in-JS性能成本緩解策略


作為一種將組件邏輯鏈接到其樣式的方式,CSS-in-JS在某些場景中變得流行起來。Aggelos Arvanitakis提醒開發人員,在某些情況下,不再能忽略CSS-in-JS的成本,並且提供了緩解策略。

Arvanitakis在一篇文章中指出,儘管CSS-in-JS帶來了好處,但是,它仍可能在某些應用程序中造成性能問題。 Arvanitakis把重點放在React和兩個流行的CSS-in-JS庫(styled-componentsemotion)上,他比較了相同代碼的兩個版本,其中只有一個版本使用了CSS-in-JS樣式。無樣式版本如下所示:

import React from 'react';
const NormalDiv = props => 
const App = () => { const [randomValue, setRandomValue] = React.useState(0); return ( {new Array(50).fill(null).map((__, i) => ( Hello World ))} ); };

樣式版本如下所示:

import styled from '@emotion/styled';
const StyledDiv = styled.div``;
const App = () => {
  const [randomValue, setRandomValue] = React.useState(0);
  return (
    
      {new Array(50).fill(null).map((__, i) => (
        Hello World
      ))}
      
    
  );
};

樣式化的CSS-in-JS的實現比無樣式版本要多花50%的時間在渲染上。儘管在很多情況下,很難察覺與CSS-in-JS相關的性能成本,但在其他情況下(如具有大型組件樹),它的成本是很難忽略的。 Arvantitakis猜測使用某些庫觀察到的性能成本可能要歸因於它們修改組件樹(使用Context並添加Context.Consumer以讀取樣式值)以及動態地應用樣式(標籤包含動態注入的CSS)。 Arvanitakis解釋道:

一切都非常正常,直到我實現了一個Table。我開始注意到渲染的速度很慢,尤其是行數超過50行之後。因此,我打開了devtools嘗試研究它。

(……)
因此,總結來說,多個Context的使用者(這意味著React必須協調其他元素)以及動態樣式附帶的固有清理工作組合起來可能讓應用程序的運行速度變慢了。

因此,Arvanitakis得出瞭如下建議:

1.不要過度組合樣式化的組件
2.首選“靜態”組件
3.避免不必要的React重渲染
4.研究零運行時CSS-in-JS庫是否適合我們的項目
(……)如果我們的應用程序不需要支持主題化,並且也沒有使用大量複雜的 csspro,那麼,零運行時CSS-in-JS庫可能是個不錯的選擇。我們可以得到的好處是,整個包的大小將縮減大約12KB,這是因為大多數CSS-in-JS庫的大小在10KB到15KB之間,而零運行時庫(如linaria)的大小不到1KB 。

但是,Arvanitakis警告大家,性能重構只應該發生在遇到或測量到性能問題之後。JSS CSS-in-JS庫的作者Oleg Isonen解釋了4種常用的CSS-in-JS策略的權衡,並對比了CSS-in-JS庫性能基準(截至2019年3月)。用選定的庫進行基準測試得到的結果如下:

CSS-in-JS性能成本緩解策略 1

CSS-in-JS可能僅限於類似React這樣的基於組件的框架。其他流行的框架,如Vue、Svelte或Angular使用其他的託管策略,為開發人員提供類似的好處(像作用域CSS和搖樹優化CSS)。例如,Angular的開發人員可以用類似html的模板文件、CSS文件和JavaScript文件來定義其組件。然後,.js的文件將引用其他兩個文件:

// ./app.component.js
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export  class AppComponent {
  title = 'angular programming.';
}

後綴名為.js、.css和.html的文件應位於同一個目錄中。另外,在模板和样式足夠簡短的情況下,Angular開發人員可能更喜歡直接在@Component定義中以字符串的形式將模板和样式放到一起。這兩種方法在語法上是不同的,但都提供了相同的作用域優勢。

Vue鼓勵採用單個.vue文件,文件中包含標記,其中含有CSS樣式信息、類似html的模板和處理組件行為的JavaScript方法。類似的,Svelte讀取.svelte文件中的組件定義,.svelte文件也包含同一文件中的樣式、模板和邏輯信息。 CSS-in-JS還是React等框架使用託管(colocation)的另一種形式,這些框架使用JavaScript渲染函數而不是模板。

正如Elm的創建者Evan Czaplicki在推文中這樣寫道,組件就是對象。託管和封裝樣式以及模板屬性,並將它們與處理組件邏輯的方法處理放到一起是對單一職責原則(Single Responsibility Principle)的一種反應,就像Robert C. Martin所解釋的:

換句話說,單一職責原則就是:
把基於相同原因而改變的事物放在一起。把出於不同原因而改變的事物分開來。

如果仔細想想,那麼我們就會明白,這只是定義內聚和耦合的另一種方式。我們希望提高因相同原因而改變的事物之間的內聚性,並且,我們希望降低因不同原因而改變的事物之間的耦合性。

Arvanitakis的全文包括其他個人資料,讀者可以上網查看。

原文鏈接:

CSS-in-JS Performance Cost – Mitigating Strategies