jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

SVG from Zero to Senior · Part 14 — SVG in React & JSX

Ship SVG the React way: inline vs SVGR, the JSX attribute gotchas (className, strokeWidth), building typed icon components with props and currentColor, data-driven SVG, and refs for measuring paths. With a live props playground.

Đa số các con ship SVG bên trong một framework component, và React phổ biến nhất. Tin tốt: mọi thứ con học vẫn đúng — SVG giờ chỉ là JSX. Cái bẫy: một nắm khác biệt về thuộc tính và trải nghiệm làm ai cũng vấp đúng một lần. Hãy dẹp chúng đi.

Chỉnh props và xem cả icon render lẫn JSX con sẽ gõ luôn đồng bộ.

Mở demo đầy đủ:

Lưu ý: blog này thuần Astro, nên demo trên là vanilla JS mô phỏng mẫu React. Các khối code dưới là React/TSX thật con dán vào dự án được.

Ba cách đưa SVG vào React

  1. JSX inline — dán markup SVG thẳng vào component. Kiểm soát đủ, style và script được.
  2. Import thành component (SVGR)import Logo from './logo.svg' rồi <Logo />.
  3. Import thành URL<img src={url} />. Cache được, nhưng không style vào trong.

Quy tắc senior: SVGR cho art tĩnh, component viết tay cho hệ icon con kiểm soát.

Các bẫy thuộc tính JSX

Khi con dán SVG thô vào JSX, React sẽ phàn nàn. Các luật:

  • classclassName.
  • Thuộc tính SVG có gạch nối thành camelCase.
  • Vài cái giữ gạch nối; nhưng các cái hiện đại con viết vẫn ổn.
  • Giá trị động dùng ngoặc nhọn.
  • style inline là một object.
// ❌ pasted HTML — React warns
<svg class="icon"><path stroke-width="2" /></svg>

// ✅ JSX
<svg className="icon"><path strokeWidth={2} /></svg>

Một component icon có kiểu

Đây là mẫu mọi design system dùng. Nhận props, mặc định colorcurrentColor, và con có một icon đổi theme và đổi cỡ được:

type IconProps = {
  size?: number;
  color?: string;
  strokeWidth?: number;
  className?: string;
};

export function StarIcon({
  size = 24,
  color = 'currentColor',
  strokeWidth = 2,
  className,
}: IconProps) {
  return (
    <svg
      viewBox="0 0 24 24"
      width={size}
      height={size}
      className={className}
      role="img"
      aria-label="Favorite"
    >
      <path
        d="M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17.8 5.9 21.2l1.5-6.8L2.2 9.7l6.9-.7z"
        fill={color}
      />
    </svg>
  );
}
<StarIcon size={32} className="text-lime-400" />   // color follows CSS via currentColor
<button><StarIcon /> Save</button>                  // inherits button's text color

currentColor lại gánh việc nặng (Phần 10): fill của icon theo color CSS của thứ chứa nó, nên một class utility đổi theme.

SVG theo dữ liệu, kiểu React

Mọi thứ từ Phần 9 thành khai báo — con .map() dữ liệu thành element và để React điều phối DOM:

function BarChart({ data }: { data: number[] }) {
  const W = 320, H = 200, pad = 28;
  const bw = (W - pad * 2) / data.length;
  return (
    <svg viewBox={`0 0 ${W} ${H}`}>
      {data.map((v, i) => (
        <rect
          key={i}
          x={pad + i * bw}
          y={H - pad - (v / 100) * (H - pad * 2)}
          width={bw * 0.6}
          height={(v / 100) * (H - pad * 2)}
          fill="currentColor"
        />
      ))}
    </svg>
  );
}

Không createElementNS thủ công, không innerHTML — React lo việc tạo và cập nhật node. Đó là lý do D3-trong-React thường giao việc thay đổi DOM cho React và chỉ dùng D3 cho phần toán.

Ref để đo path

Khi cần getTotalLength() (vẽ đường Phần 6) hay getScreenCTM() (hit-test Phần 9), lấy element bằng ref trong một effect:

const pathRef = useRef<SVGPathElement>(null);
useEffect(() => {
  const len = pathRef.current?.getTotalLength() ?? 0;
  // set CSS variables / state for the dash animation
}, []);
return <path ref={pathRef} d="…" />;

Đo DOM trong useEffect/useLayoutEffect, không bao giờ trong lúc render.

Lời cảnh báo của sư phụ

  • camelCase mọi thuộc tính có gạch nối nếu không React bỏ nó lặng lẽ.
  • Id gradient/filter duy nhất giữa các instance. Hai icon cùng id nội bộ đụng nhau — sinh id bằng useId().
  • key trên element map, như mọi list React.

Luyện tập, không thì coi như chưa học

  1. Component icon: dựng <Icon> có kiểu với size/color/strokeWidth và mặc định currentColor.
  2. useId cho gradient: render icon tô gradient hai lần và xác nhận không đụng id nhờ useId().
  3. Sparkline phản ứng: một component vẽ lại <polyline> mỗi khi prop data đổi.

Phần tiếp theo

Giờ con ship SVG gọn trong thế giới component. Cho màn kết hoành tráng, ta dựng SVG tương tác ấn tượng nhất. Ở Phần 15 ta làm một bản đồ tương tác có pan và zoom, lái viewBox từ Phần 1 bằng con lăn chuột và kéo — path kiểu địa lý, toán zoom-về-điểm, và sự điều hướng mượt sau mọi bản đồ web.