위코드 파운데이션 과정을 들으며 정리한 내용입니다.
좋은 코드란, 기본적인 것을 잘 지켜서 누가 읽어도 쉽게 이해할 수 있는 코드입니다. 서로의 코드를 이해하기 위해 약속한 리액트 컨벤션들을 아래 소개합니다. 해당 컨벤션이 정답은 아니니 참고만 하세요.
라이브러리
ㄴ React 관련 패키지
ㄴ 외부 라이브러리
컴포넌트
ㄴ 공통 컴포넌트
ㄴ 현재 컴포넌트 기준 상대적으로 먼 컴포넌트
ㄴ 현재 컴포넌트 기준 상대적으로 가까운 컴포넌트
함수, 변수 및 설정 파일
사진 등 미디어 파일
CSS 파일
예시)
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Nav from './component/Nav/Nav';
import { APIs } from './config';
import logo from './assets/logo.png';
import './Main.scss';
// Bad
const handleAgeInputAndSubmit = () => { ... };
// Good
const handleAgeInput = () => { ... };
const handleSubmit = () => { ... };
리액트는 state 가 업데이트 시 리랜더링 되므로, 불필요한 렌더링이 발생하지 않게 State 를 적절하게 사용해야 합니다.
// Bad
import React, { useState } from 'react';
const Child = props => {
const [childAge, setChildAge] = useState(props.age);
return (
<div>
<span>나이는: {childAge}</span>
</div>
);
};
// Good
import React, { useState } from 'react';
const Child = props => {
return (
<div>
<span>나이는: {props.age}</span>
</div>
);
};
// Bad
import React, { useState } from 'react';
const Footer = () => {
const [footerInfo, setFooterInfo] = useState([
{ id: 1, content: '도움말' },
{ id: 2, content: '홍보 센터' },
]);
return (
<div>
{footerInfo.map( info => {
return (
<li key={ info.id }>
<a href="">{ info.content }</a>
</li>
);
})}
</div>
);
};
export default Footer;
// Good
import React, { useState } from 'react';
const Footer = () => {
return (
<div>
{FOOTER_MAP.map( info => {
return (
<li key={ info.id }>
<a href="">{ info.content }</a>
</li>
);
})}
</div>
);
};
export default Footer;
const FOOTER_INFO = [
{ id: 1, content: '도움말' },
{ id: 2, content: '홍보 센터' },
];
// Bad
import React, { useState, useEffect } from 'react';
const TotalAge = () => {
const [catAge, setCatAge] = useState(0);
const [dogAge, setDogAge] = useState(0);
const [totalAge, setTotalAge] = useState(0);
// 두 값의 합을 선언하는 불필요한 state
const handleCatAgeInput = event => {
const { value } = event.target;
setCatAge(Number(value));
};
const handleDogAgeInput = event => {
const { value } = event.target;
setDogAge(Number(value));
};
useEffect(() => {
setTotalAge(catAge + dogAge);
}, [catAge, dogAge]);
// 의존성 배열에 고양이, 강아지 나이를 넣어서 상태값 변화 감지하고 더해주는 상황
return (
<div>
<input onChange={handleCatAgeInput} placeholder="고양이 나이" />
<input onChange={handleDogAgeInput} placeholder="강아지 나이" />
<div>
나이의 함: <span>{totalAge}</span>
</div>
</div>
);
}
export default TotalAge;
// Good
import React, { useState } from 'react';
const TotalAge = () => {
const [catAge, setCatAge] = useState(0);
const [dogAge, setDogAge] = useState(0);
const handleCatAgeInput = event => {
const { value } = event.target;
setCatAge(Number(value));
};
const handleDogAgeInput = event => {
const { value } = event.target;
setDogAge(Number(value));
};
const totalAge = catAge + dogAge;
// 변한 값을 바로 더해줌
return (
<div>
<input onChange={handleCatAgeInput} placeholder="고양이 나이" />
<input onChange={handleDogAgeInput} placeholder="강아지 나이" />
<div>
나이의 함: <span>{totalAge}</span>
</div>
</div>
);
}
export default TotalAge;
// 일반 함수
const user = {
name : '김코드',
age : '30',
job : '프로그래머',
};
const getUserInfo = ({ name, age, job }) => {
return `제 이름은 ${name}이고 나이는 ${age}살이며 직업은 ${job} 입니다. `;
}
getUserInfo(user);
// 컴포넌트 함수
// ProductList.js
const ProductList = () => {
return (
<ul>
{PRODUCT_LIST.map(product => (
<li key={product.id}>
<ProductItem product={product} />
</li>
)))}
</ul>
);
}
// ProductItem.js
const ProductItem = ({ product: { title, price, quantity}}) => {
return (
<>
<span>{ title }</span>
<span>{ price }</span>
<span>{ quantity }</span>
</>
);
};
// before
import React, { useState } from 'react';
const Login = () => {
const [inputValues, setInputValues] = useState({
email : '',
password: '',
});
const handleEmail = event => {
const { value } = event.target;
setInputValues({...inputValues, email: value});
};
const handlePassword = event => {
const { value } = event.target;
setInputValues({...inputValues, password: value});
};
return (
<div>
<input type="text" onChange={handleEmail} />
<input type="password" onChange={handlePassword} />
</div>
);
}
export default Login;
// After
import React, { useState } from 'react';
const Login = () => {
const [inputValues, setInputValues] = useState({
email : '',
password: '',
});
const handleInput = event => {
const { name, value } = event.target;
setInputValues({...inputValues, [name] : value});
// 인풋에서 받은 값으로 key 와 value 구성
};
return (
<div>
<input name="email" type="text" onChange={handleInput} />
<input name="password" type="password" onChange={handleInput} />
</div>
);
}
export default Login;
// Before
const changeButtonColor = () => {
if (input.id.includes('@') && input.pw.length >= 5) {
setIsValid(true);
} else {
setIsValid(false);
}
};
// After - 삼항 연산자 활용
const changeButtonColor = () => {
input.id.includes('@') && input.pw.length >= 5
? setIsValid(true)
: setIsValid(false);
};
// After - 논리 연산자 활용
const changeButtonColor = () => {
const isValid = input.id.includes('@') && input.pw.length >= 5;
setIsValid(isValid);
};
인라인 스타일은 가장 높은 우선순위를 가지므로, 스타일이 중복되거나 유지 보수가 어렵고, 재사용성이 좋지 않습니다. 그리고 보편적으로 CSS 클래스가 인라인 스타일보다 더 나은 성능을 보입니다.
// Bad
<button style={{color : isCommentButtonActive ? "blue" : "red"}}>
로그인
</button>
// Good
import 'style.css';
<button className={isCommentButtonActive ? 'activated' : 'deactivated'}>
로그인
</button>
a 태그를 사용하면 페이지를 새로 불러오면서 앱이 지닌 상태를 초기화하고 렌더링 된 컴포넌트들이 모두 사라져 새로 렌더링 해야 합니다. Link 를 사용하면 브라우저 주소만 바뀔 뿐 해당 컴포넌트만 새로 렌더링 합니다. 렌더링 최적화를 고려한다면 Link 를 사용합니다.
import React from 'react';
import { Link } from 'react-router-dom'
const Login = () => {
return <Link to="/signup">회원가입</Link>;
};
export default Login;
// Before
import React from 'react';
import { Link } from 'react-router-dom'
const Footer = () => {
ruturn (
<div>
<li>
<Link to = "/help">도움말</Link>
</li>
<li>
<Link to = "/help">홍보 센터</Link>
</li>
<li>
<Link to = "/help">API</Link>
</li>
<li>
<Link to = "/help">채용정보</Link>
</li>
<li>
<Link to = "/help">개인정보처리방침</Link>
</li>
</div>
);
};
export default Footer;
// After
// footerDate.js
export const INFO_LIST = [
{ id : 1, content : '도움말', url : '/help' },
{ id : 2, content : '홍보 센터', url : '/promotion' },
{ id : 3, content : 'API', url : '/api' },
{ id : 4, content : '채용정보', url : '/recruitment' },
{ id : 5, content : '개인정보처리방침', url : '/privacy' },
];
// 위와 같이 변하지 않는 데이터는 컴포넌트 바디 안에서 선언할 경우 리렌더링 될 때마다 새로운 변수로 계속 선언됩니다. 바디 밖에서 선언하거나 따로 파일을 생성해 분류하는 것이 좋습니다.
// Footer.js
import React from 'react';
import { Link } from 'react-router-dom'
import { INFO_LIST } from './footerData';
const Footer = () => {
return (
<div>
{INFO_LIST.map(info => {
return (
<li key={info.id}>
<Link to={info.url}>{info.content}</Link>
</li>
);
})}
</div>
);
};
export default Footer;
공통으로 적용하는 reset.scss, common.scss 파일은 index.js 에서 한 번만 import 해주면 됩니다. reset 파일은 브라우저 간 요소들의 스타일 차이를 없애기 위해 디폴트 값을 초기화 해주고(padding, margin 등) common 파일은 모든 컴포넌트에 공통적으로 적용하는 코드를 작성합니다. 반복적으로 사용하는 색 변수나 mixin 은 variables.scss 파일로 별도 분리해 관리해야 이후 유지 보수에 좋습니다. Sass를 더 알아보고 싶다면 클릭