안녕하세요, 개발자 Sean입니다.
퇴근 시간 버스를 놓쳐서 블로그 글을 읽던 와중 꽤 괜찮은 글이 있어 번역해 옮겨 봅니다.
더 나은 리엑트 코드를 위한 팁
코드를 잘 작성하는 것이 왜 중요할까요?
이유는 코드는 가능한 깔끔할수록 유지보수에 용이하기 때문입니다.
좋은 개발자가 되기 위해서, 작동하는 코드가 아닌 작동하는 좋은 코드를 작성할 줄 아는 것은 중요합니다.
그래서 아래에 리엑트를 처음 배울 때 알았으면 좋았을 몇 가지 팁을 공유해 드립니다.
Use fragments
리엑트에선 하나의 컴포넌트에 여러 개의 자식 컴포넌트를 리턴할 수 없습니다. 만약에 그런 시도를 하게 된다면 이런 에러 메시지를 보게 될 것입니다.
"Adjacent JSX elements must be wrapped in an enclosing tag"
const LoginForm = () => {
return (
<UserInput />
<PasswordInput />
);
};
흔히 아래 코드처럼 div 태그를 감싸서 문제를 해결하지만 최선의 방법은 아닙니다.
const LoginForm = () => {
return (
<div>
<UserInput />
<PasswordInput />
</div>
);
};
필요한 경우가 아니라면 div 사용 대신에 react fragments를 사용하시길 바랍니다.
왜 div 보다 fragments를 사용해야하는 걸까요?
div가 필요한 경우가 아니라면 fragments가 더 나은 방법인 이유는 바로 fragments가 DOM에 아무런 엘리먼트를 생성하지 않기 때문입니다. 따라서 자식 컴포넌트는 감싸는 DOM 노드 없이 렌더링 될 수 있습니다.
Spread operator
만약 자식 컴포넌트에 아래와 같이 여러개의 props를 전달해야 하는 경우라면,
const Parent = (props) => {
return (
<Child
firstName={props.firstName}
lastName={props.lastName}
age={props.age}
email={props.email}
address={props.address}
phone={props.phone}
work={props.work}
/>
);
};
spread operator(...)를 이용하면 코드를 더욱 깔끔하게 할 수 있습니다.
const Parent = (props) => {
return <Child {...props} />;
};
하지만 주의할 점은 spread operator는 코드의 가독성을 약간 떨어뜨릴 수 있습니다. 따라서 전 달해 야만 하는 props가 여러 개일 때만 사용하시길 바랍니다.
Self-closing tags
리엑트에서 닫는 태그는 자식 컴포넌트가 있을 경우에만 사용하는 것이 좋습니다.
<Component> ... </Component>
만약에 전달할 자식 컴포넌트가 없다면 아래와 같이 작성할 수 있습니다.
<Component />
Use curry functions
대부분의 리엑트 개발자들이 커리 함수를 잘 모릅니다. 하지만 한번 알게 된다면 커리함수를 애용하게 될 것입니다.
커리함수가 무엇인지 설명하기 전에 몇 가지 사례를 생각해 보겠습니다.
예를 들어, 사용자 배열에서 각 사용자마다 하나의 버튼을 생성 하고 버튼을 클릭하면 해당 사용자에게 경고를 표시하는 컴포넌트를 만든다고 가정해 보겠습니다.
흔히 사용하는 방식은 아래와 같습니다.
const LoginForm = (props) => {
const selectUser = (user) => {
alert(`You selected ${user}`);
};
return (
<>
{users.map((user) => (
<button onClick={() => selectUser(user)}>Select {user}</button>
))}
</>
);
};
onClick안에서 selectUser 함수를 호출하는 익명의 함수를 정의하고 있네요.
커리함수를 이용하면 더 나은 방식으로 기능 구현을 할 수 있습니다.
커리함수란 x인자 하나만을 받는 x함수로 함수를 잘게 나누는 것을 말합니다.
아래는 커리함수로 덧셈 함수를 만드는 방법입니다.
const add = (a, b) => {
return a + b;
};
// Becomes
const add = (a) => (b) => {
return a + b;
};
그렇다면 이제 위의 예제를 아래와 같이 바꿔 볼 수 있을 겁니다.
const LoginForm = (props) => {
const selectUser = (user) => (e) => {
alert(`You selected ${user}`);
};
return (
<>
{users.map((user) => (
<button onClick={selectUser(user)}>Select {user}</button>
))}
</>
);
};
이러면 onClick 코드가 더욱 간결해졌음을 알 수 있습니다.
이경우 커리 함수는 유저 값을 냉동해 다른 곳에서 호출할 수 있도록 보내주는, 다른 함수를 생성하는 일종의 "공장"과 같은 역할을 하게 됩니다.
실제로 어떤 일이 일어나고 있는지를 이해하고 싶다면 아래 코드를 실행해 보시면 됩니다.
const users = ["kliton", "medium", "devs"];
const LoginForm = (props) => {
const selectUser = (user) => {
console.log(
`I'm creating a selecUser function with the user value: ${user}`
);
return (e) => {
alert(`You selected ${user}`);
};
};
return (
<>
{users.map((user) => (
<button key={`btn-${user}`} onClick={selectUser(user)}>
Select {user}
</button>
))}
</>
);
};
그러면 아웃풋은 아래와 같습니다.
I'm creating a selecUser function with the user value: kliton
I'm creating a selecUser function with the user value: medium
I'm creating a selecUser function with the user value: devs
Eliminate inline functions in JSX
코드의 가독성은 단순히 자바스크립트 코드와 HTML을 분리하는 것만으로도 상당히 높일 수 있습니다.
// ❌❌❌❌❌ Bad one
const LoginForm = (props) => {
return (
<>
<button
onClick={() => {
const result = login();
{
/* ... other code .... */
}
}}
/>
</>
);
};
// ✔️✔️✔️ Good one
const LoginForm = (props) => {
const handleLogin = () => {
const result = login();
{
/* ... other code .... */
}
};
return (
<>
<button onClick={() => handleLogin()} />
</>
);
};
Use unique Key prop for list item
리엑트는 key prop을 컴포넌트와 DOM 엘리먼트와의 관계 생성에 사용합니다.
Key는 리엑트가 어떤 아이템이 추가되었거나 삭제되었는지 알 수 있도록 해줍니다.
예를 들어, 만약 id 프로퍼티를 가지고 있는 데이터를 렌더링 할 때 그 id가 유니크한 경우 해당 아이디를 키로 사용할 수 있습니다.
const users = [
{
id: 1,
first_name: "Janella",
},
{
id: 2,
first_name: "Cleveland",
},
{
id: 3,
first_name: "Joellyn",
},
];
const UserList = (props) => {
return (
<ul>
{users.map((user) => (
<li key={user.id}>User: {user.first_name}</li>
))}
</ul>
);
};
Avoid Using Indexes as Key Props
만약 정적이지 않은 배역이라면 해당 배열의 인덱스를 키의 prop으로 사용하지 마시길 바랍니다.
배열의 아이템의 순서를 바꾼다면 리엑트는 혼란에 빠지고 정확하지 않은 엘리먼트를 리렌더링 할지도 모릅니다.
Use HOCS( higher-order components )
HoC는 코드의 반복을 피할 수 있는 아주 좋은 패턴입니다. HoC는 순수 함수이기 때문에 컴포넌트를 변형시키지 않기 때문입니다.
리엑트 다큐먼트의 정의에 따르면, "정확히 말해, HoC는 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수이다."라고 합니다.
예를 들어, HoC를 이용해 유저가 인증되었을 경우에만 컴포넌트가 보이도록 하는 auth guard를 만들 수 있습니다.
AuthGuard 컴포넌트는 유저가 로그인 했는지 체크하고 만약 로그인을 하지 않았다면 컴포넌트 대신에 null을 렌더링 할 것입니다.
withAuthGuard HOC는 아래와 같이 생겼습니다:
import { AuthGuard } from "src/components/authentication/auth-guard";
export const withAuthGuard = (Component) => (props) =>
(
<AuthGuard>
<Component {...props} />
</AuthGuard>
);
그리고 위 withAuthGuard HoC를 컴포넌트 보호에 아래와 같이 이용할 수 있습니다.
import { withAuthGuard } from "src/hocs/with-auth-guard";
const CustomComponent = (props) => {
return <p> Hi there ;) </p>;
};
export default withAuthGuard(CustomComponent);
Object Destructuring
destructuring은 코드를 깔끔하게하고 가독성을 높여줍니다.
객체 destructuring은 ES6에서 소개된 개념입니다.
Destructuring은 배열로 부터 특정한 아이템만을 받거나 객체로부터 특정 프로퍼티만을 받는 방법을 뜻합니다.
Destructuring에 대한 설명 이전에 객체로부터의 프로퍼티 추출은 보통 아래와 같이 이루어집니다.
const user = {
firstName: "Kliton",
email: "mock@email.com",
profession: "developer",
vat: 29908322239
}
// I want to extract only the firstName and the email
const firstName = user.firstName;
const email = user.email;
Destructuring을 이용하면 firstName과 email을 아래와 같이 추출할 수 있습니다.
const user = {
firstName: "Kliton",
email: "mock@email.com",
profession: "developer",
vat: 29908322239
}
// I want to extract only the firstName and the email
const {firstName, email} = user;
Destructuring은 배열에서도 마찬가지로 동작합니다.
const followers = ["adam", "tom", "kliton"];
// We want only the first two followers
const first = followers[0];
const second = followers[1];
const followers = ["adam", "tom", "kliton"];
// We want only the first two followers
const [first, second] = followers;
그렇다면 왜 destructuring은 코드를 간결하게 해주는 걸까요?
- props나 다른 객체로부터 추출된 프로퍼티에 다른 변수명을 부여할 수 있습니다.
- 코드의 유지보수를 좋게 합니다.
- 어플리케이션에 사용된 코드의 총량을 줄여줍니다.
props를 destructuring 할 수도 있습니다.
const SignupConfirmationAlert = (props) => {
const { user, email } = props; // ✔️ object destructuring
return (
<p>
Dear {user}, an email was sent to address {email} with the confirmation link
</p>
);
};
추가로 useState 훅의 문법에 대해 궁금해하신 적 있으신가요?
const [value, setValue] = useState();
추측하신 대로 useState는 배열을 반환하고, 반환된 배열을 destructuring 해 사용하고 있습니다.
Use useReducer instead of useState when you have complex state logic
const [state, dispatch] = useReducer(reducer, initialState);
useReducer란 무엇일까요?
useReducer는 useState와 마찬가지로 리엑트에서 제공하는 훅입니다.
내부적으로 useState는 useReducer를 이용합니다. 즉 useState로 할 수 있는 기능은 useReducer를 이용해서 모두 구현할 수 있다는 말이죠.
어떤 경우에 사용하면 될까요?
복잡한 state 구조를 가지고 있다면 useReducer가 useState보다 더욱 예측 가능한 상태변화를 제공할 수 있습니다.
좋은 예로는 원시데이터가 아닌 복잡한 객체를 관리하는 경우입니다.
Showcase
import { useReducer } from "react";
import ReactDOM from "react-dom/client";
const initialTodos = [
{
id: 1,
title: "Todo 1",
complete: false,
},
{
id: 2,
title: "Todo 2",
complete: false,
},
];
const reducer = (state, action) => {
switch (action.type) {
case "COMPLETE":
return state.map((todo) => {
if (todo.id === action.id) {
return { ...todo, complete: !todo.complete };
} else {
return todo;
}
});
default:
return state;
}
};
function Todos() {
const [todos, dispatch] = useReducer(reducer, initialTodos);
const handleComplete = (todo) => {
dispatch({ type: "COMPLETE", id: todo.id });
};
return (
<>
{todos.map((todo) => (
<div key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.complete}
onChange={() => handleComplete(todo)}
/>
{todo.title}
</label>
</div>
))}
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Todos />);
// Example got by Kliton on https://www.w3schools.com/REACT/react_usereducer.asp
Boolean variable names
불리언 변수를 만들땐 아래 접두사를 사용하시길 바랍니다.
- is
- has
- can
예를 들어, isVisible은 visible보다 더 나은 변수명이라고 할 수 있습니다.
// Bad names ❌❌❌❌
let human = true;
let authorization = false;
let write = true;
// Good names ✔️✔️✔️✔️✔️✔️✔️
let isHuman = true;
let hasAuthorization = false;
let canWrite = true;
Curly braces aren't needed for string props
아래와 같이 스트링을 인자로 넘길 때에는 따옴표로 충분합니다.
// ❌ No need for curly braces
<Label value={"Bad"} />
// ✔️ Without curly braces
<Label value="Good" />
Use optional chaining operator (?)
optional chaining operator (?.)은 해당하는 체인이 유효한지 확인 없이도 체인 깊숙이 위치한 프로퍼티의 값을 읽어 올 수 있도록 해줍니다.
// ❌ Without optional chaining
if (data && data.user && data.user.contactInfo) {
alert(`User email ${data.user.contactInfo.email}`);
}
// ✔️ With optional chaining
alert(`User email ${data?.user?.contactInfo?.email}`);
출처: https://itnext.io/tips-for-writing-better-react-code-ceb49e929001
Tips for Writing Better React Code
Do you know those tips?
itnext.io
'Front-End > React' 카테고리의 다른 글
[React] 최초 렌더링에서 useEffect 실행 생략하기 (0) | 2022.12.14 |
---|---|
[React] React-dnd 사용방법 (0) | 2022.11.29 |
[React] Single Responsibility Principle 리펙토링 하기 (0) | 2022.11.01 |
[React] 리엑트에 SOLID 원칙 적용시키기 (0) | 2022.10.31 |
[React] 선언형과 리엑트 (0) | 2022.10.20 |