React 안티패턴에 대해 알아보기 위해 작성하였으며, 안티패턴에 대한 주관적인 해결법을 같이 공유합니다.
Prop drilling은 부모 컴포넌트에서 여러 중간 컴포넌트를 거쳐 깊이 중첩된 자식 컴포넌트로 props를 전달하는 프로세스를 말합니다.
이때 해당 state가 최종적으로 자식 컴포넌트에 도달할 때까지 실제로 state가 불필요한 여러 자식컴포넌트에 해당 state를 전달하는것이 보통입니다.
이를 Props drilling이라고 합니다.
Props drilling 패턴을 피하기 위해선 Context API, Redux, Zustand 와 같은 전역 상태 관리 라이브러리를 사용을 추천합니다.
const data = {
id: 11,
name: Yosua,
age: 10,
avatar: "👍",
bio: "GGWP"
}
// Props Plowing 상황
<AssessmentTable id={data.id} name={data.name} age={data.age} avatar={data.avatar} bio={data.bio}/>
// Spread operator를 사용해 간단하게 나다낸 모습
<AssessmentTable {...data}/>
// lodash.pick 함수를 사용해 명시적으로 데이터를 전달
<AssessmentTable {..._.pick(data, ["id", "name", "age", "avatar", "bio"])} />
Props Drilling은 수직적인 문제라면, Props Plowing은 수평적인 문제입니다.
이 패턴은 하나의 컴포넌트가 너무 많은 props를 가지게 되고, 결국 각 props가 전달되는 변수와 동일한 이름을 가지는 길고 반복적인 코드가 생성되는 상황을 말합니다.
Props Plowing 패턴을 피하기 위해선 JavaScript의 Spread Operator을 사용하여 props의 모든 key-value 쌍을 한번에 다 넘겨줄 수 있습니다. 다만 이 방법을 사용 시 불필요한 데이터까지 전달되거나 코드의 가독성이 떨어질 수 있습니다. 이럴땐 Lodash의 pick 함수를 통해 개선할 수 있습니다.
const Parent = () => {
const SecondChild = () => {
return <div> <SecondChild/> </div>
}
const Child = () => {
return <div> <SecondChild/> </div>
}
return (
<div>
<Child/>
</div>
)
중첩 컴포넌트는 자식 컴포넌트를 부모 컴포넌트 내부에 선언했을 때 문제가 생길 수 있는 상황입니다.
중첩 컴포넌트의 가장 큰 문제는 부모 컴포넌트가 렌더링될 때마다 자식 컴포넌트도 재정의되어 새로운 메모리 주소를 갖게 되고, 이로 인해 성능 문제와 예측할 수 없는 동작이 발생할 수 있다는 것입니다.
Component Nesting 패턴을 피하기 위해선 컴포넌트를 별도의 파일로 분리하는 것이 가장 좋은 방법입니다.
만약 꼭 중첩 선언해야 한다면 useMemo나 useCallback을 활용해 메모이제이션할 수 있지만,
부모 컴포넌트의 props가 변경되면 여전히 자식이 재정의되므로 가능하면 분리하는 것이 최선입니다.
const UserProfile = () => {
const [user, setUser] = useState({
name: "John Doe",
age: 30,
email: "john@example.com"
});
const updateName = () => {
setUser(prev => {...prev, name: "Jane Doe"});
};
return (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>Email: {user.email}</p>
<button onClick={updateName}>Update Name</button>
</div>
);
};
React에서 useState를 사용할 때, 여러 개의 상태 값을 하나의 객체에 넣고 관리하고 싶을 때가 있습니다.
이렇게 하면 코드가 더 깔끔해 보이고, setState를 한 번만 호출할 수 있기 때문에 성능도 좋아 보일 수 있습니다.
하지만 모든 상태를 하나의 객체로 묶는 방식은 유지보수와 가독성을 해칠 수 있습니다. 또한 지금은 성능에 영향이 없지만 React 18 이전에는 setState를 여러 번 호출하면 리렌더링이 여러 번 발생하는 문제가 있었습니다.
Coupled state 패턴을 피하려면, 상태를 독립적으로 선언하여 개별적으로 관리하는 것이 좋습니다. 이를 통해 Single Source of Truth(신뢰할 수 있는 단일 출처) 원칙을 유지하고, 데이터 무결성을 보장하며, 불필요한 사이드 이펙트를 방지할 수 있습니다.
{items.map((item, index) => (
<Item key={index} data={item} />
))}
Instead, use a unique identifier for each item as the key.
배열 형식의 데이터를 렌더링할 때 key 값으로 배열의 index를 사용하는 경우가 종종 있습니다. 이는 데이터가 변경될 경우 React의 가상 DOM이 기존 요소와 새로운 요소를 올바르게 매칭하지 못하면서 불필요한 렌더링이 발생하는 문제를 일으킬 수 있습니다.
위의 패턴을 피하기 위해선 리스트의 각 항목에 unique한 데이터를 사용하여 key 값으로 지정하는것이 좋습니다. 데이터에 unique한 데이터가 없다면 uuid 같은 라이브러리를 활용해 고유 식별자를 생성하는 것을 권장합니다.
리액트는 기본적으로 가상 DOM을 사용하여 UI를 업데이트합니다. 그러나 때때로 리액트 외부에서 직접 DOM을 조작하는 경우가 있습니다. document.getElementById()와 같은 방법을 사용하여 DOM을 직접 수정하면 리액트의 상태 관리 시스템과 충돌할 수 있습니다.
위 패턴을 피하기 위해선 DOM 조작을 리액트의 상태 관리와 결합하여 사용하고, 가능하면 ref를 활용하여 DOM에 접근하는 것을 권장합니다.