Styled Components vs Emotion: Lựa Chọn Nào Cho Dự Áp React Của Bạn? (React Roadmap)

Chào mừng các bạn quay trở lại với series “React Roadmap“! Sau khi cùng nhau khám phá React là gì, làm quen với JSX, hiểu về Functional Components, Props và State, cách tái sử dụng component, và tìm hiểu các hooks cơ bản như useState, useEffect, cũng như các khái niệm nâng cao như Custom Hooks, useCallback/useMemo, và Context API cho quản lý state, chúng ta đã xây dựng được một nền tảng vững chắc để phát triển ứng dụng React.

Tuy nhiên, một phần không thể thiếu của bất kỳ ứng dụng web nào chính là giao diện người dùng đẹp mắt. Trước đây, chúng ta thường viết CSS trong các file riêng biệt (.css, .scss) và import vào component. Cách này có ưu điểm là quen thuộc, nhưng cũng tồn tại nhược điểm như xung đột tên class (scope global), khó quản lý các style động dựa trên state/props, và việc loại bỏ CSS không dùng đến (dead code) khá phức tạp.

Đó là lý do các thư viện CSS-in-JS ra đời và trở nên phổ biến trong hệ sinh thái React. Thay vì viết CSS tách rời, chúng ta viết style ngay trong file JavaScript/JSX, tận dụng sức mạnh của JavaScript để quản lý, tạo style động và đóng gói style theo từng component. Điều này mang lại nhiều lợi ích về tính mô đun, khả năng tái sử dụng và dễ dàng quản lý.

Trong thế giới CSS-in-JS, hai cái tên nổi bật và được sử dụng rộng rãi nhất hiện nay chính là Styled ComponentsEmotion. Cả hai đều cung cấp giải pháp hiệu quả cho việc styling trong React, nhưng lại có những cách tiếp cận và đặc điểm riêng. Vậy, khi đứng trước lựa chọn, bạn nên dùng thư viện nào? Bài viết này sẽ giúp bạn làm rõ điều đó.

CSS-in-JS Là Gì và Tại Sao Nó Phổ Biến?

Trước khi đi sâu vào Styled Components và Emotion, hãy nhắc lại nhanh tại sao CSS-in-JS lại “lên ngôi” trong React:

  • Scoped Styles: Mỗi component có style riêng, tránh xung đột class name toàn cục. Thư viện sẽ tự động tạo các class name unique.
  • Dynamic Styling: Dễ dàng áp dụng style dựa trên props hoặc state của component. Đây là lợi thế lớn so với CSS truyền thống.
  • Maintainability: Style nằm cùng file component giúp dễ dàng quản lý, tìm kiếm và chỉnh sửa. Khi component bị xóa, style của nó cũng được xóa theo.
  • Dead Code Elimination: Chỉ những style được sử dụng bởi component đang render mới được đưa vào bundle cuối cùng.
  • Theming: Cả hai thư viện đều hỗ trợ mạnh mẽ việc tạo theme (chủ đề) cho ứng dụng.

CSS-in-JS không phải là không có nhược điểm (ví dụ: runtime overhead, bundle size tăng nhẹ, learning curve ban đầu), nhưng đối với nhiều dự án React hiện đại, lợi ích mà nó mang lại thường vượt trội hơn.

Styled Components: Component Là Style, Style Là Component

Styled Components có lẽ là thư viện CSS-in-JS phổ biến nhất trong cộng đồng React, đặc biệt là ở thời điểm đỉnh cao của nó. Triết lý cốt lõi của Styled Components là “visual primitives” – các component trực quan có sẵn style bên trong.

Cách hoạt động cơ bản

Styled Components sử dụng cú pháp tagged template literals của JavaScript để định nghĩa các component React với style đi kèm.

import styled from 'styled-components';

// Tạo một component Button đã được styled
const StyledButton = styled.button`
  background-color: #673ab7;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;

  &:hover {
    background-color: #512da8;
  }
`;

// Sử dụng component này trong ứng dụng của bạn
function MyApp() {
  return <StyledButton>Click Me</StyledButton>;
}

Ở đây, styled.button là một hàm trả về một component React mới. Bên trong tagged template literal (phần nằm giữa dấu backticks “ ` “), chúng ta viết CSS thuần túy. Styled Components sẽ xử lý cú pháp này, tạo ra một component React thực thụ và tự động thêm các class name unique để đảm bảo style chỉ áp dụng cho component đó.

Dynamic Styling với Props

Điểm mạnh lớn của Styled Components là khả năng áp dụng style động dựa trên props. Bạn có thể truyền props vào styled component và sử dụng chúng trong CSS.

import styled from 'styled-components';

const DynamicButton = styled.button`
  background-color: ${props => (props.primary ? '#007bff' : 'gray')};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;

  &:hover {
    background-color: ${props => (props.primary ? '#0056b3' : 'darkgray')};
  }
`;

function MyApp() {
  return (
    <div>
      <DynamicButton primary>Primary Button</DynamicButton>
      <DynamicButton>Secondary Button</DynamicButton>
    </div>
  );
}

Bên trong backticks, chúng ta sử dụng cú pháp interpolation ${...}. Bên trong đó là một hàm nhận props làm đối số và trả về giá trị CSS mong muốn. Điều này làm cho việc tạo các biến thể style trở nên rất dễ dàng.

Các tính năng nổi bật

  • Theming: Cung cấp ThemeProvider để quản lý các giá trị theme toàn cục và truy cập chúng trong styled components.
  • Extending Styles: Dễ dàng tạo component mới kế thừa và ghi đè (override) style từ một styled component khác.
  • Server-Side Rendering (SSR): Hỗ trợ tốt SSR, đảm bảo style được render cùng với HTML ban đầu.

Styled Components mang đến một cách tiếp cận rất “React-like”: mọi thứ đều là component, kể cả style. Điều này giúp code base nhất quán và dễ hiểu.

Emotion: Mạnh Mẽ và Linh Hoạt

Emotion là một thư viện CSS-in-JS mạnh mẽ khác, ra đời sau Styled Components một chút nhưng nhanh chóng thu hút được cộng đồng nhờ hiệu năng và tính linh hoạt. Emotion ban đầu được phát triển dựa trên những bài học từ thư viện `glamor` và sau đó đã ảnh hưởng ngược lại Styled Components.

Hai cách tiếp cận chính

Emotion cung cấp hai cách chính để viết style:

  1. Sử dụng `css` prop: Đây là cách độc đáo của Emotion và được nhiều người yêu thích vì cảm giác gần gũi với việc viết style inline hoặc sử dụng các thư viện utility-first CSS. Bạn có thể truyền một object hoặc tagged template literal vào prop `css`.

    import { css } from '@emotion/react';
    
    const buttonStyles = css`
      background-color: #673ab7;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      font-size: 16px;
    
      &:hover {
        background-color: #512da8;
      }
    `;
    
    function MyApp() {
      return <button css={buttonStyles}>Click Me</button>;
    }
    

    Hoặc viết trực tiếp:

    import { css } from '@emotion/react';
    
    function MyApp() {
      return (
        <button
          css={css`
            background-color: #673ab7;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
    
            &:hover {
              background-color: #512da8;
            }
          `}
        >
          Click Me
        </button>
      );
    }
    

    Ưu điểm của cách này là bạn có thể áp dụng style cho bất kỳ element hoặc component nào mà không cần phải tạo ra một styled component mới. Điều này rất hữu ích khi bạn muốn thêm style cho một element HTML đơn giản hoặc một component từ thư viện UI khác.

  2. Sử dụng `@emotion/styled`: Emotion cũng cung cấp API tương tự như Styled Components thông qua package `@emotion/styled`. Cách này hoạt động y hệt Styled Components.

    import styled from '@emotion/styled';
    
    const StyledButton = styled.button`
      background-color: #673ab7;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      font-size: 16px;
    
      &:hover {
        background-color: #512da8;
      }
    `;
    
    function MyApp() {
      return <StyledButton>Click Me</StyledButton>;
    }
    

Việc Emotion cung cấp cả hai cách tiếp cận (css prop/function và styled components) mang lại sự linh hoạt cao cho developer.

Dynamic Styling với Emotion

Tương tự Styled Components, Emotion cũng hỗ trợ dynamic styling dựa trên props.

Với @emotion/styled, cách làm giống hệt Styled Components:

import styled from '@emotion/styled';

const DynamicButton = styled.button`
  background-color: ${props => (props.primary ? '#007bff' : 'gray')};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;

  &:hover {
    background-color: ${props => (props.primary ? '#0056b3' : 'darkgray')};
  }
`;

// Cách sử dụng tương tự

Với `css` prop, bạn cũng có thể sử dụng interpolation với hàm nhận props:

import { css } from '@emotion/react';

function DynamicButtonWithCss(props) {
  const buttonStyles = css`
    background-color: ${props.primary ? '#007bff' : 'gray'};
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;

    &:hover {
      background-color: ${props.primary ? '#0056b3' : 'darkgray'};
    }
  `;

  return <button css={buttonStyles}>{props.children}</button>;
}

function MyApp() {
  return (
    <div>
      <DynamicButtonWithCss primary>Primary Button</DynamicButtonWithCss>
      <DynamicButtonWithCss>Secondary Button</DynamicButtonWithCss>
    </div>
  );
}

Hoặc thậm chí dùng object syntax với `css` prop, điều mà Styled Components không hỗ trợ một cách trực tiếp:

import { css } from '@emotion/react';

function DynamicButtonWithCssObject(props) {
  const buttonStyles = css({
    backgroundColor: props.primary ? '#007bff' : 'gray',
    color: 'white',
    padding: '10px 20px',
    border: 'none',
    borderRadius: '5px',
    cursor: 'pointer',
    '&:hover': {
      backgroundColor: props.primary ? '#0056b3' : 'darkgray',
    },
  });

  return <button css={buttonStyles}>{props.children}</button>;
}

Cú pháp object này có thể quen thuộc với những người đã dùng qua các thư viện CSS-in-JS khác hoặc style inline, mang lại một lựa chọn khác cho developer.

Các tính năng nổi bật của Emotion

  • Linh hoạt với `css` prop: Áp dụng style dễ dàng cho bất kỳ element/component nào.
  • Hiệu năng: Thường được đánh giá là có hiệu năng runtime nhỉnh hơn Styled Components một chút, đặc biệt trong các trường hợp phức tạp, nhờ cách xử lý style và cache hiệu quả hơn. (Lưu ý: sự khác biệt này có thể không đáng kể trong hầu hết các ứng dụng).
  • Bundle Size: Package `@emotion/react` (chỉ dùng `css` prop/function) có bundle size nhỏ hơn `@emotion/styled` hoặc Styled Components. Nếu bạn chỉ cần tính năng `css` prop, Emotion là lựa chọn nhẹ hơn.
  • Theming & SSR: Hỗ trợ đầy đủ như Styled Components.

Styled Components vs Emotion: So Sánh Chi Tiết

Bây giờ, hãy đặt hai thư viện này lên bàn cân dựa trên các tiêu chí quan trọng đối với developer.

1. Cú pháp và Cách tiếp cận

  • Styled Components: Độc quyền sử dụng tagged template literals để tạo styled components. Mỗi khối style định nghĩa một component mới. Cách này rất nhất quán với tư tưởng component-based của React.
  • Emotion: Cung cấp cả tagged template literals (qua `@emotion/styled`) và `css` prop/function (qua `@emotion/react`). `css` prop cho phép áp dụng style cho các element/component hiện có mà không tạo ra component wrapper mới.

=> Lựa chọn phụ thuộc vào sở thích. Nếu bạn muốn mọi thứ đều là component được style, Styled Components là tự nhiên hơn. Nếu bạn thích sự linh hoạt của việc áp dụng style trực tiếp qua prop hoặc sử dụng object syntax, Emotion vượt trội hơn.

2. Hiệu năng

  • Cả hai thư viện đều có hiệu năng rất tốt cho hầu hết các ứng dụng. Sự khác biệt thường chỉ rõ rệt ở các ứng dụng quy mô rất lớn với nhiều style động phức tạp.
  • Emotion thường được coi là có hiệu năng runtime nhỉnh hơn một chút nhờ cơ chế cache và inject style khác biệt.

=> Đối với dự án thông thường, hiệu năng không phải là yếu tố quyết định. Nếu bạn đang xây dựng một ứng dụng cực kỳ lớn và hiệu năng runtime là tối quan trọng, Emotion có thể có lợi thế nhỏ.

3. Bundle Size

  • Package `@emotion/react` (chỉ `css` prop) có bundle size nhỏ nhất.
  • `@emotion/styled` và Styled Components có bundle size tương đương, thường lớn hơn `@emotion/react`.

=> Nếu bundle size là yếu tố quan trọng và bạn chủ yếu muốn sử dụng `css` prop, Emotion là lựa chọn tốt hơn.

4. Developer Experience (DX)

  • Cả hai đều có DX tốt, hỗ trợ tốt trong VS Code với syntax highlighting và autocompletion (cần cài thêm extension).
  • Styled Components có cảm giác “React-native” hơn với việc mọi thứ là component.
  • Emotion với `css` prop có thể mang lại cảm giác quen thuộc hơn với style inline hoặc utility-first CSS, và rất tiện khi style các component từ thư viện UI bên thứ ba.

=> Đây là yếu tố mang tính cá nhân và đội nhóm. Hãy thử cả hai để xem team của bạn cảm thấy thoải mái với cú pháp nào hơn.

5. Tính năng và Ecosystem

  • Cả hai đều cung cấp các tính năng thiết yếu như Theming, SSR, critical CSS extraction, hỗ trợ TypeScript,…
  • Cả hai đều có cộng đồng lớn mạnh và được duy trì tốt. Styled Components có thể phổ biến hơn một chút về số lượng cài đặt, nhưng Emotion cũng đang phát triển rất nhanh.

=> Về mặt tính năng cốt lõi, cả hai đều đáp ứng tốt nhu cầu. Hệ sinh thái xung quanh (các thư viện UI sử dụng nó, các plugin,…) cũng khá tương đồng.

Bảng So Sánh Tóm Tắt

Tiêu chí Styled Components Emotion
Cú pháp chính styled.<element>``; (Tagged Template Literals) styled.<element>``; (@emotion/styled)
css={...} prop (@emotion/react)
Cách tiếp cận Component-centric (style đi kèm component mới) Linh hoạt (có thể tạo component mới hoặc áp dụng style qua prop)
Dynamic Styling Dễ dàng với hàm trong interpolation ${props => ...} Dễ dàng với hàm trong interpolation ${props => ...} (cả styled và css prop), hoặc object syntax với css prop
Hiệu năng Runtime Tốt Thường nhỉnh hơn một chút
Bundle Size Trung bình Nhỏ hơn (đặc biệt với @emotion/react)
DX Tốt, nhất quán “mọi thứ là component” Tốt, linh hoạt hơn với css prop/object syntax
Theming, SSR, v.v. Hỗ trợ đầy đủ Hỗ trợ đầy đủ
Phổ biến Rất phổ biến, cộng đồng lớn Phổ biến, đang phát triển nhanh

Khi Nào Nên Chọn Styled Components?

Bạn nên cân nhắc Styled Components nếu:

  • Bạn yêu thích triết lý “mọi thứ là component” của React và muốn áp dụng điều đó cho cả styling.
  • Bạn muốn một cú pháp duy nhất, nhất quán để định nghĩa styled components.
  • Phần lớn nhu cầu styling của bạn là tạo ra các component UI cơ bản đã được style sẵn (ví dụ: StyledButton, StyledInput, StyledCard).
  • Đội nhóm của bạn đã quen thuộc hoặc ưu tiên cú pháp tagged template literals.

Khi Nào Nên Chọn Emotion?

Bạn nên cân nhắc Emotion nếu:

  • Bạn cần sự linh hoạt để áp dụng style cho cả các element HTML hoặc component từ thư viện bên thứ ba mà không tạo component wrapper.
  • Bạn thích cú pháp object syntax cho CSS hoặc cảm thấy thoải mái với việc viết style trực tiếp trên element qua prop (`css` prop).
  • Hiệu năng runtime hoặc bundle size là yếu tố quan trọng và bạn muốn tối ưu ở mức cao nhất có thể.
  • Bạn đang di chuyển từ một dự án sử dụng các phương pháp styling khác (như style inline, CSS Modules) và muốn một thư viện có khả năng thích ứng cao.

Lời Khuyên cho Developer Juniors

Nếu bạn là một developer junior đang học React Roadmap, đừng quá lo lắng về việc phải chọn cái nào là “tốt nhất”. Cả Styled Components và Emotion đều là những công cụ xuất sắc và sẽ giúp bạn viết style hiệu quả trong React. Quan trọng là hiểu được ý tưởng cốt lõi của CSS-in-JS và cách nó giải quyết các vấn đề của CSS truyền thống trong bối cảnh component-based.

Hãy thử tìm hiểu cú pháp cơ bản của cả hai, có thể làm một vài ví dụ nhỏ với mỗi thư viện. Xem cách chúng tương tác với propsstate để tạo style động. Trong một dự án thực tế, quyết định thường phụ thuộc vào sở thích của lead developer, kiến trúc dự án, hoặc công nghệ đã được chọn sẵn. Khi bạn làm việc trong một đội, điều quan trọng nhất là tuân thủ quy ước chung để code base nhất quán.

Việc hiểu cách sử dụng các thư viện này sẽ trang bị cho bạn kỹ năng cần thiết để làm việc trong hầu hết các dự án React hiện đại, bất kể họ chọn Styled Components hay Emotion.

Kết Luận

Styled Components và Emotion là hai lựa chọn hàng đầu cho việc styling trong React bằng phương pháp CSS-in-JS. Styled Components đi theo hướng “mọi thứ là component”, mang lại sự nhất quán và tư duy component mạnh mẽ. Emotion cung cấp sự linh hoạt cao hơn với `css` prop và object syntax, cùng với lợi thế nhỏ về hiệu năng/bundle size trong một số trường hợp.

Không có câu trả lời tuyệt đối cho việc “cái nào tốt hơn”. Lựa chọn phù hợp nhất phụ thuộc vào yêu cầu cụ thể của dự án, kinh nghiệm và sở thích của đội ngũ phát triển. Cả hai đều là những công cụ trưởng thành, được hỗ trợ tốt và có khả năng giúp bạn xây dựng giao diện đẹp, dễ quản lý trong ứng dụng React của mình.

Tiếp theo trên hành trình React Roadmap, chúng ta sẽ cùng nhau tìm hiểu sâu hơn về cách tối ưu hiệu năng cho ứng dụng React. Hẹn gặp lại các bạn ở bài viết sau!

Chỉ mục