
interface ScatterProps {
//data
xs: number[];
ys: number[];
//display
xRange: [number, number];
yRange: [number, number];
color: string;
// events
onClick: (x:number, y:number,index:number) => void;
}
위 코드가 필요할 때에만 차트를 다시 그릴 수 있도록 최적화 할 수 있다.
data나 display 속성이 변경되면 다시 그려야 하지만, 이벤트 핸들러가 변경되면 다시 그릴 필요가 없다.
❗이런 최적화는 리액트에서 일반적이며, 렌더링할 때마다 이벤트 핸들러 Prop이 새 화살표 함수로 설정된다. (useCallback 훅, 렌더링할 때마다 새 함수를 생성하지 않도록 하는 또 다른 기법)
➡️ 최적화 첫 번째 방법
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
){
let k: keyof ScatterProps;
for(k in oldProps) {
if(oldProps[k] !== newProps[k]) {
if(k !== 'onClick') return true;
}
}
return false;
}
만약 새로운 속성이 추가되면 위 함수는 값이 변경될 때마다 차트를 다시 그릴 것이다.
이런 처리를 '보수적 접근법' 또는 '실패에 닫힌 접근법' 이라 한다.
이 접근법을 이용하면 차트가 정확하지만 너무 자주 그려질 가능성이 있다.
보수적 접근법
실패에 닫힌 접근법은 오류 발생 시 적극적으로 대처하는 방향이며, 반대로 열린 접근법은 소극적으로 대처하는 방향을 말한다.
(예를 들어, 만약 보안과 관련된 곳이라면 실패에 닫힌 방법을 써야 할 것이고, 기능에 무리가 없고 사용성이 중요한 곳이라면 실패에 열린 방법을 써야 할 것.)
➡️ 최적화 두 번째 방법
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
){
return (
oldProps.xs !== newProps.xs ||
oldProps.ys !== newProps.ys ||
oldProps.xRange !== newProps.xRange ||
oldProps.yRange !== newProps.yRange ||
oldProps.color !== newProps.color
)
// (no check for onClick)
}
최적화 1의 차트를 불필요하게 다시 그리는 단점을 해결했으나, 실제로 차트를 다시 그려야 할 경우에 누락되는 일이 생길 수 있다.
❗ 앞의 두 가지 최적화 방법은 모두 이상적이지 않다.
타입 체커가 동작할 수 있게 하는 것이 좋다.
const REQUIRES_UPDATE : {[k in keyof ScatterProps]: boolean} = {
// boolean 값을 가지는 객체 타입
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false
}
function shouldUpdate(
oldProps : ScatterProps,
newProps: ScatterProps,
) {
let k: keyof ScatterProps;
for(k in oldProps) {
if(oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) {
// 위 조건식은 REQUIRES_UPDATE의 onClick을 false로 설정해놨기 때문에
// onClick에 대해선 통과하지 않는다.
return true;
}
}
return false;
}
매핑된 타입과 객체를 사용하는 것이 핵심이다.
이렇게 사용하면 ScatterProps 인터페이스에 속성이 추가/삭제/변경된다면 REQUIRES_UPDATE의 정의에 오류가 발생할 것을 정확히 잡아낼 것이다.
✔️ 매핑된 타입은 한 객체가 또 다른 객체와 정확히 같은 속성을 가지게 할 때 이상적이다.
예제처럼 매핑된 타입을 사용해 TS가 코드에 제약을 강제하도록 할 수 있다.