리액트 리팩토링 가이드, react refactoring guide

라용·2022년 9월 5일
12

위코드 - 스터디로그

목록 보기
31/100

위코드 파운데이션 과정을 들으며 정리한 내용입니다.

좋은 코드란, 기본적인 것을 잘 지켜서 누가 읽어도 쉽게 이해할 수 있는 코드입니다. 서로의 코드를 이해하기 위해 약속한 리액트 컨벤션들을 아래 소개합니다. 해당 컨벤션이 정답은 아니니 참고만 하세요.

상단 import 순서는 이렇게

라이브러리
ㄴ 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 를 적절하게 사용해야 합니다.

전달받은 props 를 바로 사용할 수 있다면 별도 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>
	);
};

시간이 지나도 변하지 않는 값, UI 변화에 관여하지 않는 값은 State 로 관리할 필요가 없습니다.

// 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: '홍보 센터' },
];

컴포넌트 안의 다른 State 나 Props 로 계산 가능하다면, 불필요한 State 를 만들지 않습니다. 아래 예시의 useEffect 사용은 불필요합니다.

// 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>
		</>
	);
};

계산된 속성명을 사용하면 input Handler 함수를 합칠 수 있습니다. input 태그의 name 태그를 사용합니다. (name 속성은 input 태그에서만 사용 가능)

// 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;

조건문을 작성할 경우 Boolean 데이터 타입의 특성을 이용하면 조금 더 간결하게 코드를 작성할 수 있습니다.

// 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);
};

JSX 에서 인라인 스타일을 적용하는 것은 지양하는 게 좋습니다.

인라인 스타일은 가장 높은 우선순위를 가지므로, 스타일이 중복되거나 유지 보수가 어렵고, 재사용성이 좋지 않습니다. 그리고 보편적으로 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;

반복되는 UI 는 Array.map() 을 활용해서 축약할 수 있습니다.

// 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 & variables.scss

공통으로 적용하는 reset.scss, common.scss 파일은 index.js 에서 한 번만 import 해주면 됩니다. reset 파일은 브라우저 간 요소들의 스타일 차이를 없애기 위해 디폴트 값을 초기화 해주고(padding, margin 등) common 파일은 모든 컴포넌트에 공통적으로 적용하는 코드를 작성합니다. 반복적으로 사용하는 색 변수나 mixin 은 variables.scss 파일로 별도 분리해 관리해야 이후 유지 보수에 좋습니다. Sass를 더 알아보고 싶다면 클릭

profile
Today I Learned

0개의 댓글