React+d3 에서 ResizeObserver로 반응형 svg 만들기

React에서 d3를 사용하게 되면 React는 state 기반으로 렌더링하지만 d3는 DOM을 직접 조작한다.

width와 height 또한 .attr 메소드를 이용하여 설정해주기 때문에 반응형으로 너비가 변하는 svg를 작성하기가 까다롭다.

유튜브 영상을 보고 ResizeObserver를 이용하여 이러한 설정을 해줄 수 있는 hook을 작성해보았다.

ResizeObserver

대상으로 하는 HTML 요소의 콘텐츠 또는 테두리의 크기를 관찰하다 변경사항이 생기면 내부의 콜백을 실행한다.

useResizeObserver

위의 ResizeObserver를 이용하여 hook을 작성하였다.
코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useEffect, useState } from "react";
import ResizeObserver from "resize-observer-polyfill";
// IE를 지원하기 위한 폴리필

const useResizeObserver = (ref) => {
const [dimensions, setDimensions] = useState(null);
// 현재 대상되는 요소의 정보를 state로 관리한다.
// 사용하는 컴포넌트에서 이 state가 바뀔 경우 리렌더링 하기 위함이다.
useEffect(() => {
// useEffect는 DOM 요소가 렌더링 되자마자 실행된다.
const observeTarget = ref.current;
// 관찰할 대상을 지정한다.
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
setDimensions(entry.contentRect);
// 변화가 생겼을 경우 내부의 콜백이 실행된다.
// 이 콜백에서 state를 변화시키며 해당 컴포넌트를 리렌더링 한다.
});
});
resizeObserver.observe(observeTarget);
// 관찰을 시작한다.
return () => {
// 컴포넌트가 언마운트 될 경우 관찰을 종료한다.
resizeObserver.unobserve(observeTarget);
};
}, [ref]);
return dimensions;
};

export default useResizeObserver;

컴포넌트에서는 다음과 같이 호출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function BarChart({ data }) {
const svgRef = useRef();
const wrapperRef = useRef();
const dimensions = useResizeObserver(wrapperRef);

useEffect(() => {
const svg = select(svgRef.current);

if (!dimensions) return;

const { width, height } = dimensions;

// width와 height를 사용하여 설정한다.
// ...
}, [data, dimensions]);
// 바뀔때마다 호출하기 위함이다.

return (
<div ref={wrapperRef} style={{ marginBottom: "2rem" }}>
<svg ref={svgRef}>
<g className="x-axis" />
<g className="y-axis" />
</svg>
</div>
);
}

export default BarChart;

Ref

useResizeObserver
ResizeObserver

댓글