해당 포스팅에는 코드가 포함되어 있습니다. 유의하여 봐주시길 바라겠습니다.
2주차 과제는 React를 이용하여 만드는 것이었다.
컴포넌트별로 관심사를 분리하면 재사용성과 확장성을 높여서 개발을 더 쉽게 해준다는 장점이 있고, 코드를 이해하고 보수하기 훨씬 더 쉬워진다.
2주차에는 관심사별로 분리를 하면서 Counter 앱을 만들고, Todo 앱을 만드는 작업을 하였습니다.
// index.jsx 파일
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('app'));
import { useState } from 'react';
import ClickableButton from './components/Button/ClickableButton';
import NumberButton from './components/Button/NumberButton';
function App() {
const [state, setState] = useState({
count: 0,
});
const { count } = state;
const handleClick = (increasingValue) => {
setState({
count: count + increasingValue,
});
};
return (
<div>
<p>Counter</p>
<ClickableButton
count={count}
onClick={handleClick}
/>
<NumberButton onClick={handleClick} />
</div>
);
}
export default App;
처음에는 Counter Page
파일을 생성했었다. App 파일에서는 상태관리를 하고, 해당 파일에 Props를 내려줬었다
이렇게 구현한 결과, Props drilling에 대해서 공부해보는 게 좋겠다는 피드백을 얻을 수 있었다
props drilling에 대해서 알아보니, props가 많아질수록 해당 props를 추적하기 어렵다는 단점이 있다고 한다. 그렇기 때문에 children
을 이용하는 것을 권장하는 글을 보게 되었다.
처음에 props drilling에 대해서 보고, state와 핸들러 함수를 Counter Page로 옮겨야 되겠다는 생각이 들었었다. 그러나 App
파일에서 상태관리를 하는 편이 더 나았다. Page는 상태관리를 하는 곳은 아니니까...😅 그래서 Counter Page를 없애고, App 파일에서 상태관리와 함수를 각 컴포넌트에 전달하도록 수정하였다.
function NumberButton({ onClick }) {
const numberList = [1, 2, 3, 4, 5];
return (
<div>
{
numberList.map((value) => (
<button
type="button"
onClick={() => onClick(value)}
key={value}
>
{value}
</button>
))
}
</div>
);
}
export default NumberButton;
function ClickableButton({ count, onClick }) {
return (
<div>
<button type="button" onClick={() => onClick(1)}>
Click me!
(
{ count }
)
</button>
</div>
);
}
export default ClickableButton;
Clickable Button
을 눌렀을 때, 증가하는 숫자를 어떤 숫자일지 잘 모를 수 있을 거라는 생각이 들어, 처음에는 변수화하였다. 이런 의도라면 변수화를 하는 것도 괜찮겠다는 답변을 들었다. 하지만, 더 복잡한 프로젝트라면 변수화를 하는 게 맞을 수 있겠다는 생각이 들어서 그냥 인자에 숫자를 전달하는 것으로 수정하였다.React 17 버전 이후에는 업그레이드가 되어서, React를 importing 없이 JSX를 사용할 수 있다.
import React from 'react
는 더 이상 가져올 필요가 없다. 하지만 React가 제공하는 Hook 또는 리액트가 제공하는 다른 exports를 하기 위해서는 import React
가 필요하다
@babel/plugin-transform-react-jsx
플러그인은 필요할 때 새로운 React package에서 speacial function을 자동으로 import 하기 때문에 수동으로 포함할 필요가 없다.babel.config.js
파일에서 { "presets": [ ["@babel/preset-react", { "runtime": "automatic" }] ] }
을 추가해주면 된다. (기본값이 runtime: 'classic'
이다.)module.exports = {
},
},
],
'@babel/preset-react',
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
],
};
React의 이벤트 핸들러 함수 & Props에 대한 이름 규칙
handle
네이밍을 사용on
네이밍을 사용// 직접적으로 로직을 처리하는 이벤트 핸들러 함수의 경우 handle 네이밍을 사용한다.
const handleClick = (increasingValue) => {
setState({
count: count + increasingValue,
});
};
```jsx
// props로 내려줄 때는 이렇게 on 네이밍을 사용한다
<ClickableButton count={count} onClick={handleClick} />
// App.jsx
import { useState } from 'react';
import TodoEditor from './TodoEditor';
import TodoList from './TodoList';
function App() {
const [todo, setTodo] = useState({
id: null,
content: '',
});
const [todoList, setTodoList] = useState([]);
const handleChange = (e) => {
setTodo({
id: Date.now(),
content: e.target.value,
});
};
const handleClick = () => {
if (todo.content === '') {
return;
}
setTodoList([...todoList, { id: todo.id, content: todo.content }]);
setTodo({
id: null,
content: '',
});
};
const handleDelete = (id) => {
setTodoList([...todoList].filter((item) => item.id !== id));
};
return (
<div>
<h1>To-do</h1>
<TodoEditor
todo={todo}
onChange={handleChange}
onClick={handleClick}
/>
<TodoList todoList={todoList} onDelete={handleDelete} />
</div>
);
}
export default App;
// TodoEditor.jsx
function TodoEditor({ todo, onChange, onClick }) {
const { content } = todo;
return (
<div>
<input
type="text"
value={content}
placeholder="할 일을 입력해주세요"
onChange={onChange}
/>
<button type="button" onClick={() => onClick(content)}>추가</button>
</div>
);
}
export default TodoEditor;
TodoEditor
라고 파일명을 짓는 것보다는 TodoForm
이 좀 더 적절하다고 할 수 있다.
<div>
태그가 아닌 <form>
태그를 사용하는 편이 웹접근성 측면에서 더 좋다고 할 수 있다.
function TodoEditor({ todo, onChange, onClick }) {
const { content } = todo;
return (
<form>
<input
type="text"
value={content}
placeholder="할 일을 입력해주세요"
onChange={onChange}
/>
<button type="submit" onClick={() => onClick(content)}>추가</button>
</form>
);
}
export default TodoEditor;
button type="submit
으로 바꿔주면 key event
를 추가하지 않고도 사용자 관점에서 보았을 때 더 편리하게 만들 수 있다. 대신 이렇게 바꿀 경우에는 handleClick
함수와 onClick
props 이름을 handleSubmit, onSubmit
으로 바꿔줘야 된다.// TodoList.jsx
import TodoItem from './TodoItem';
function TodoList({ todoList, onDelete }) {
return (
<div>
{todoList.length === 0
? <p>할 일이 없어요!</p>
: todoList.map((item, i) => (
<TodoItem
todoItem={item}
index={i}
todoList={todoList}
key={item.id}
onDelete={onDelete}
/>
))}
</div>
);
}
export default TodoList;
삼항연산자를 이용해서, todo list가 없을 경우에는 '할 일이 없어요' 문구를 보여주고, todo list가 있을 경우에는 리스트를 보여주도록 구현했었다.
하지만, 삼항연산자를 사용한 이 코드를 보았을 때, 가독성도 떨어지고, 할 일이 있어도 계속 저 조건을 검사하기 때문에 이런 부분에서는 수정을 하는 편이 더 좋다.
import TodoItem from './TodoItem';
function TodoList({ todoList, onDelete }) {
if (todoList.length === 0) {
return <p>할 일이 없어요!</p>;
}
return (
<div>
todoList.map((item, i) => (
<TodoItem
todoItem={item}
index={i}
todoList={todoList}
key={item.id}
onDelete={onDelete}
/>
))}
</div>
);
}
export default TodoList;
// 여기서는 arr length를 검사하는 함수를 컴포넌트 바깥으로 빼줄 수 있다.
GuardClauses
, early return
에 대해서 공부하는 게 좋다.
Guard Clauses
는 깨끗하고 읽기 쉬운 코드를 만드는데 도움이 된다. 특정 조건이 충족되거나 충족되지 않을 경우 로직의 흐름이 계속되지 않도록 하기 때문이다. 함수의 실행 속도를 높이고, 코드의 양을 줄일 수 있기 때문에 유용하다.
function TodoItem({ todoItem, index, onDelete }) {
const { id, content } = todoItem;
return (
<div>
<span>
{index + 1}
.
</span>
<span>{content}</span>
<button type="button" onClick={() => onDelete(id)}>완료</button>
</div>
);
}
export default TodoItem;
function TodoItem({ todoItem, index, onDelete }) {
const { id, content } = todoItem;
return (
<div>
<span>
{index + 1}
.
</span>
<span>{content}</span>
<button type="button" onClick={() => onDelete(id)}>완료</button>
</div>
);
}
export default TodoItem;
항상 기능 구현할 때, 기본적으로 하는 Todo 만들기는 볼 때마다 새로운 기분이다.
아직 실력이 부족하기 때문에 한번 구현을 해도, 다시 마주하면 새로운 기분인거겠지?
배웠던 것들 정리를 잘 하고, 다음번에 Todo를 마주했을 때는 덜 새로운 기분이길 바란다.
❗️ 혹시 틀린 부분이 있거나, 조금 더 알려주고 싶은 내용이 있으시다면, 댓글로 남겨주시면 감사하겠습니다!!