컴퍼넌트와 관련 된 클린코드 정리
함수의 위치
- 컴포넌트 내부에 있어야 할 함수
- 이벤트 핸들러
- 이유 : 함수 내부에서 상태를 직접 사용하는 경우(set 함수를 사용하는 경우 포함) 리렌더링을 통해서 새로운 값을 전달받아야 하기 때문에 컴포넌트 내부에 둔다
- 컴포넌트 외부에 있어야 할 함수
- 로직을 담당하는 함수
- 이유 : 상태를 직접사용하지 않고 파라미터를 통해서 전달받아서 값을 반환해주는 함수의 경우 다시 재생성 될 필요가 없기 때문에 컴포넌트 밖에 두어 메모리에 계속 누적되지 않도록 관리해준다(파생 된 값을 생성할때 사용하는 함수 등).
예제
컴포넌트 내부에 있어야 하는 함수
handleChange 와 같은 경우 setForm을 통해 상태를 변경시켜주어야 하기 때문에 컴포넌트 내부에 존재하는 것이 좋다.
const Form = ({ onSubmit }) => {
const [form, setForm] = useState({ id: '', email: '' });
const handleChange = (e) => {
setForm((form) => ({
...form,
[e.target.name]: e.target.value
}));
};
// ...
}
이러한 경우에도 밖으로 뺄 수 있긴하지만 코드가 복잡해지기 때문에 내부에 두는 것이 좋다.
컴포넌트 밖에 존재하는 함수
함수 내부에서 직접 상태의 값을 변경하지는 않고 상태를 전달받아 계산 결과를 반환하는 경우 밖에 두어 메모리에 함수가 계속 생성되는 것을 방지한다.
plus100 함수는 직접 상태를 변경하는 것이 아닌 계산만 하는 함수이기 때문에 밖에 둔다.
const plus100 = (count) => count + 100;
const Form = () => {
const [count, setCount] = useState(0);
const [result, setResult] = useState(0);
const handleChange = (e) => {
const newCount = e.target.value;
setResult(plus100(+newCount));
setCount(newCount);
};
return (
<form>
<input value={count} onChange={handleChange} />
<div>{result}</div>
</form>
)
}
모든 것은 상황에 따라 맞춰서 작성하는 것이 좋다. React.memo 를 사용하여 자식에서 사용되지 않는 상태가 변경되었을때 자식이 리렌더링 되지 않도록 하는 방법도 있기 때문이다.
이벤트 핸들러
정리 : 여러개의 input의 값을 변경하는 하나의 이벤트 핸들러에서 만들어보자.
이유 : 하나로 관리하면 코드가 짧아지고, input이 늘어나거나 줄어들어도 최소한의 코드만 수정해서 관리하기 편하다.
아래와 같이 이벤트 핸들러를 생성할 때 event를 함수의 파라미터로 전달받는 경우가 있다. 이 경우 input에 name 속성을 설정하여 여러개의 input을 한번에 통제할 수 있다.
아래의 경우 단점은 이벤트를 파라미터로 받기 때문에 값을 변경 시 input으로만 가능하다. 하지만 대부분의 input으로 변경 되는 경우가 대부분이기 때문에 아래와 같은 코드가 유용하다.
function handleChange(event) {
setValue((prev) => ({
...prev,
[event.target.name]: event.target.value
}));
}
값을 변경하는 경우가 input으로만 하는 경우 해당 방법이 좋다.
정리 : 이벤트 객체를 직접 전달받지 않는 함수를 활용하자.
이유 : 이벤트 객체를 직접 전달받지 않으면 값을 유연하게 변경할 수 있다. 이벤트 객체를 직접 받는 경우에는 이벤트 핸들러로써밖에 값을 변경할 수 없다.
두번째로는 익명함수를 사용하는 방법이다. 아래와 같이 이벤트 핸들러로 익명함수를 전달한다. 아래와 같이 이벤트 핸들러를 작성하게 되면 이벤트가 아니기 때문에 좀더 범용적으로 사용할 수 있다. 다른 요소에 의해서 값이 변경될 수 있다면 아래와 같이 사용하면 좋다.
function handleChange(inputIdentifier, newValue) {
setValue((prev) => ({
...prev,
[inputIdentifier]: newValue
}));
}
return (
<input value={form.name} onChange={(e) => handleChange('name', e.target.value)} />
)
값을 변경하는 경우가 input 이외에 여러경우인 경우 위의 방법이 좋다.
Fragment 사용
결론 : 최상위 태그가 2개 이상인 경우 Fragment를 사용해보자.
이유 : 의미없는 태그로 감싸는 행위를 없앨 수 있다.
리액트에서 컴포넌트의 부모요소는 오직 하나만 가질 수 있다. 이로인하여 구조상 의미없는 부모 태그를 추가하기도 한다.
<div className="불필요한 태그">
<div></div>
<div></div>
</div>
이러한 경우를 위해 리액트에서는 Fragment 컴포넌트를 제공하고 있다. 불필요한 태그 대신 Fragment 를 사용하자.
불필요한 Frament 사용 자제
결론 : 습관적인 Fragment 사용을 자제하자.
이유 : 코드 줄 수가 늘어나고 들여쓰기 한칸 늘어나 조금이지만 코드 읽기가 불편하고 용량이 늘어나게 된다.
컴포넌트를 작성할 때 먼저 Fragment 로 먼저 감싸고 시작하는 경우가 많다. 실제로는 자식 태그가 여러개인 경우를 위해서 제공되는 기능인데 습관적으로 Fragment 로 감싸게 된다.
아래의 코드를 보면 Fragment 를 제외하고 최상단 부모 태그는 오직 하나이기 때문에 Fragment 를 사용하지 않아도 문제가 없다.
<>
<div>
<div>
<div>
</div>
</>
의미없는 Fragment 를 제외하고 사용하자.
<div>
<div>
<div>
</div>
성능상 큰 문제는 없지만 코드가 더 길어지기 때문에 자제하자.
displayName 사용
결론 : HOC와 같은 동적 컴포넌트 사용시 displayName을 사용해보자.
이유 : 동적 컴포넌트 사용 시 디버깅할때 이름이 출력되어 편리하다.
일반적인 컴포넌트를 사용하는 경우에는 불필요할 수도 있다. 리액트에서 제공하는 devTools를 사용하는 경우 일반적인 방법으로 컴포넌트를 사용하면 트리구조에서 컴포넌트 이름이 정확하게 잘 보인다.
하지만 HOC와 같은 동적인 컴포넌트를 사용하게 되면 디버깅 시 컴포넌트 이름이 안보인다. 이때 displayName을 활용하면 HOC를 사용해도 정상적으로 보인다.
function InnerComponent() {
return <div>Inner component</div>
}
InnerComponent.displayName = 'InnerComponent';
컴포넌트는 도메인보다 primitive하게 만들어보자
이유 : 재사용 가능성이 높은 컴포넌트를 만들 수 있게 해준다.
우리가 로그인 페이지를 만든다고 생각해보자. form 부분을 컴포넌트로 만들려고 한다. 이때 이름은 로그인 기능을 담당하기 위해 LoginForm 컴포넌트를 만들었다.
해당 LoginForm 은 로그인이라는 도메인기능을 담당한다. 이로인해 재사용하기에 어렵게 되었다.
도메인 보다는 UI 생김새를 묘사한 컴포넌트를 만들면 좀 더 재사용이 용이하고 기능 분리도 쉬워진다.
- Box, Circle, List, Square 등등
도메인을 담당하는 컴포넌트, 기본적인 컴포넌트 모두 필요하다. 적절한 경우에 둘 모두 활용하자.
'React' 카테고리의 다른 글
[React] Context API 성능개선 (0) | 2025.02.15 |
---|---|
리액트 팁 및 클린코드 - 4. 기타 (0) | 2024.11.02 |
리액트 팁 및 클린코드 - 3. Props (0) | 2024.10.26 |
리액트 팁 및 클린코드 - 2. 상태 (0) | 2024.10.25 |
[Next.js] Next.js 시작하기 (0) | 2023.04.18 |