Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ export default [
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
"no-unused-vars" : "off",
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },

],
'react/no-unknown-property': ['error', { ignore: ['css'] }],
},
},
]
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
56 changes: 54 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,57 @@
/** @jsxImportSource @emotion/react */
import { useState, useEffect } from 'react';
import { css } from '@emotion/react';
import TodoInput from './components/TodoInput';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

세세한 컴포넌트 분리로 코드가 깔끔합니다 👍

import TodoList from './components/TodoList';
import TodoHeader from './components/TodoHeader';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트 이름도 직관적으로 지어 주셔서 아주 좋습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 동의합니다 ! 항상 이름 짓는건 어려워요 😞



const containerStyle = css`
max-width: 400px;
margin: 100px auto;
padding: 20px;
background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%);
border-radius: 20px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
`;


function App() {
return <div>React Todo</div>;
const [todos, setTodos] = useState(() => {
// 로컬 스토리지에서 저장된 todos 가져오기
const savedTodos = localStorage.getItem('todos');
return savedTodos ? JSON.parse(savedTodos) : []; // 저장된 값이 있으면 파싱하여 사용, 없으면 빈 배열
});

useEffect(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect에서 함수를 사용할 경우를 대비하여 변수, 함수 선언 맨 마지막에 위치하도록 미리 습관을 들여보는 것은 어떨까요 ?!

// todos 상태가 변경될 때마다 로컬 스토리지에 저장
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

// 새 할 일을 추가하는 함수
const addTodo = (text) => {
setTodos(todos.concat({ text, completed: false })); // 새로운 할 일을 기존 할 일 리스트에 추가
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boolean 값을 저장하는 변수는 맞다, 아니다 2가지 상태만 나타내므로 더욱 더 네이밍에 신경써주는 것이 좋습니다! completed 보다 isCompleted 와 같은 의문문 형식의 이름을 더 많이 사용합니다 🤩

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 동의합니다 !! boolean 값을 갖는 변수명은� 보통 is를 접두어로 사용하죠 ~!

};

// 특정 인덱스의 할 일을 삭제하는 함수
const deleteTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index)); // 해당 인덱스의 할 일을 제외하고 새로운 리스트 생성
};

// 특정 인덱스의 할 일의 완료 상태를 토글하는 함수
const toggleTodo = (index) => {
setTodos(todos.map((todo, i) =>
i === index ? { ...todo, completed: !todo.completed } : todo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

특정 아이템을 토글/삭제 하려고 할 때, 다른 아이템이 토글/삭제 되는 경우가 있는데, 이 경우 배열 내 요소가 달라지면서 인덱스 값도 함께 바뀌게 되어서 발생하는 문제 같아요!

)); // 할 일의 완료 상태를 반전시킴
};

return (
<div css={containerStyle}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emotion css 사용 멋져요 👍

<TodoHeader />
<TodoInput addTodo={addTodo} />
<TodoList todos={todos} deleteTodo={deleteTodo} toggleTodo={toggleTodo} />
</div>
);
}

export default App;
export default App;
23 changes: 23 additions & 0 deletions src/components/TodoButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const buttonStyle = css`
padding: 10px;
cursor: pointer;
background-color: #ff7f7f;
border: none;
border-radius: 5px;
color: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
margin-left: 8px;
`;

function TodoButton({ onClick, children }) {
return (
<button css={buttonStyle} onClick={onClick}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onClick 함수 이름으로 동작이 어떤 것인지 예측 가능하게 작성해주시면 더 좋을 것 같습니다 🤩

{children} {/* 버튼 내부의 내용 (아이콘이나 텍스트) */}
</button>
);
}

export default TodoButton;
14 changes: 14 additions & 0 deletions src/components/TodoHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const titleStyle = css`
font-size: 24px;
text-align: center;
margin-bottom: 20px;
`;

function TodoHeader() {
return <h1 css={titleStyle}>📚 TodoList</h1>;
}

export default TodoHeader;
57 changes: 57 additions & 0 deletions src/components/TodoInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//** @jsxImportSource @emotion/react */
import { useState } from 'react';
import { css } from '@emotion/react';

const inputContainerStyle = css`
margin-bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
`;

const inputStyle = css`
padding: 10px;
width: 200px;
margin-right: 10px;
border: none;
border-radius: 5px;
outline: none;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
`;

const buttonStyle = css`
padding: 10px;
cursor: pointer;
background-color: #ff7f7f;
border: none;
border-radius: 5px;
color: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
`;

function TodoInput({ addTodo }) {
const [input, setInput] = useState(''); // 입력 값을 관리하기 위한 상태 선언
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 것을 입력하여 상태 관리할 것인지 알기 쉽도록 변수의 이름을 더 상세하게 적어주시면 좋을 것 같아요! ex. inputTodo


// 폼 제출 시 호출되는 함수
const handleSubmit = (e) => {
e.preventDefault(); // 기본 폼 제출 동작을 막음
if (!input.trim()) return; // 입력 값이 공백일 경우 아무 작업도 하지 않음
addTodo(input);
setInput('');
};

return (
<form css={inputContainerStyle} onSubmit={handleSubmit}>
<input
css={inputStyle}
type="text"
value={input}
onChange={(e) => setInput(e.target.value)} // 입력 값이 변경될 때마다 상태 업데이트
placeholder="할일을 입력하세요"
/>
<button css={buttonStyle} type="submit">+</button> {/* 할 일을 추가하는 버튼 */}
</form>
);
}

export default TodoInput;
38 changes: 38 additions & 0 deletions src/components/TodoItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import TodoButton from './TodoButton';

// Todo 항목의 기본 스타일 정의
const itemStyle = css`
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
cursor: pointer;
color: #fff;
`;

// 완료된 Todo 항목 스타일 정의
const completedStyle = css`
text-decoration: line-through; // 완료된 항목을 취소선으로 표시
color: #aaa;
`;

// 개별 Todo 항목을 나타내는 컴포넌트
function TodoItem({ todo, index, deleteTodo, toggleTodo }) {
return (
<div css={[itemStyle, todo.completed ? completedStyle : null]}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드와 같은 경우에서도 isCompleted 를 사용하면, "완료 시에는 completeStyle을 사용하고 아직 완료하지 않았다면 아무런 스타일을 적용하지 않는구나" 라고 명확하게 이해하기가 쉬워집니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조건이 false일 때 null을 리턴하기보다는 && 연산자를 사용해 볼 수 있을 것 같아요~

Suggested change
<div css={[itemStyle, todo.completed ? completedStyle : null]}>
<div css={[itemStyle, todo.completed && completedStyle]}>

참고자료 - 단축 평가 논리 계산법

{/* 할 일 텍스트 표시 */}
<span>{todo.text}</span>
<div>
{/* 할 일 완료 상태를 토글하는 버튼 */}
<TodoButton onClick={() => toggleTodo(index)}>✔️</TodoButton>
{/* 할 일을 삭제하는 버튼 */}
<TodoButton onClick={() => deleteTodo(index)}>🗑️</TodoButton>
</div>
</div>
);
}

export default TodoItem;
36 changes: 36 additions & 0 deletions src/components/TodoList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import TodoListSection from './TodoListSection';


const listContainerStyle = css`
width: 300px;
max-width: 100%;
margin: 0 auto;
`;

function TodoList({ todos, deleteTodo, toggleTodo }) {
const pendingTodos = todos.filter((todo) => !todo.completed); // 완료되지 않은 할 일만 필터링
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 isCompleted 마찬가지!

const completedTodos = todos.filter((todo) => todo.completed); // 완료된 할 일만 필터링

return (
<div css={listContainerStyle}>
{/* TO DO 섹션 */}
<TodoListSection
title="🗒️ TO DO"
todos={pendingTodos}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>
{/* DONE 섹션 */}
<TodoListSection
title="💿 DONE"
todos={completedTodos}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>
</div>
);
}

export default TodoList;
26 changes: 26 additions & 0 deletions src/components/TodoListSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import TodoItem from './TodoItem';

const sectionStyle = css`
margin-bottom: 20px;
`;

function TodoListSection({ title, todos, deleteTodo, toggleTodo }) {
return (
<div css={sectionStyle}>
<h3>{title} ({todos.length})</h3>
{todos.map((todo, index) => (
<TodoItem
key={index}
Comment on lines +13 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key 값으로 map()index를 사용하는 방법은 바람직하지 않습니다!

리스트 항목의 순서가 바뀌거나 항목이 추가/삭제될 때, 각 항목에 대응되는 index 값이 변경되기 때문에 이전 key와 새로운 key가 달라지게 됩니다. 이는 곧 React가 해당 항목을 새롭게 렌더링해야 한다고 판단하여 불필요한 리렌더링을 초래할 수 있습니다.

즉, key를 사용하는 이유가 불필요한 리렌더링의 방지인데, 이 경우 오히려 불필요한 리렌더링이 발생할 수 있다는 것입니다!

스크린샷 2024-09-04 오전 12 53 53

참고자료 - React 공식문서 key 규칙
참고자료 - 리액트에서 key에 index를 넣으면 안 되는 '진짜' 이유

index={index}
todo={todo}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>
))}
</div>
);
}

export default TodoListSection;