가상 DOM(Document Object Model)
: React는 가상 DOM을 사용하여 빠른 UI 업데이트를 가능하게 함. 가상 DOM은 React가 변경된 부분만 실제 DOM에 적용하여 불필요한 렌더링을 방지 ES6
클래스로 생성되어 있음.index.js > App.js > > InputSample.js > UserList.js
얕은 복사
(Shallow Copy): 객체의 참조(reference)만을 복사. 즉, 원본 객체와 복사본 객체가 같은 객체를 참조하게됨, 따라서 원본 객체를 변경하면 복사본 객체도 영향을 받게 됨
const user = inputs → inputs
객체를 변경하면 user
객체도 변경
깊은 복사
(Deep Copy): 객체의 값(value)를 복사. 원본 객체와 복사본 객체가 다른 객체를 참조하게 됨, 따라서 원본 객체를 변경해도 복사본 객체는 영향을 받지 않음.
const user ={
...inputs
}
위에서 제시한 코드에서는 객체 전개 연산자(...)를 사용하여 깊은 복사를 수행
React에서는 상태(state)를 변경할 때 얕은 복사와 깊은 복사를 구분하여 사용해야함
상태가 간단한 객체나 배열이면 얕은 복사를 사용해도 문제가 없지만, 중첩된 객체나 배열이 포함된 상태를 변경할 때는 깊은 복사를 사용해야 함. ❗️ 이를 사용하지 않으면 예기치 않은 오류가 발생할 수 도 있음
...
배열에서 전개 연산자를 사용하면 배열 요소를 하나씩 꺼내서 전개함
예를들어 [1,2,3]
배열을 [...[1,2,],3]
으로 펼치면 [1,2,3]
이 됨
이는 concat
메서드를 사용하는 것과 동일한 결과를 반환함
객체에서 전개 연산자를 사용하면 객체의 프로퍼티(속성)을 하나씩 꺼내서 전개함
전개 연산자는 불변성을 유지하면서 객체 또는 배열을 수정할 때 유용하게 사용됨
객체나 배열의 내용을 새로운 배열이나 객체로 복사하고, 해당 새로운 객체나 배열에서 수정 작업을 수행하면 원본 객체나 배열에는 영행을 주지 않으면서 안전하게 작업을 수행할 수 있음
TodoList.js
/** @jsxImportSource @emotion/react */
import React, { useState, useEffect, useRef } from 'react';
import * as S from './style';
const TodoList = () => {
const [todo, setTodo] = useState([]);
const [inputValue, setInputValue] = useState('');
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
const addTodo = () => {
if (inputValue.trim() !== '') {
setTodo([...todo, inputValue.trim()]);
setInputValue('');
}
};
const deleteTodo = (index) => {
setTodo(todo.filter((_, i) => i !== index));
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
addTodo();
}
};
return (
<div css={S.Container}>
<h2>Todo List</h2>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Enter todo"
ref={inputRef}
/>
<button type="button" onClick={addTodo}>
Add
</button>
</div>
<ul>
{todo.map((todo, index) => (
<li css={S.TodoItem} key={index}>
<div css={S.TodoText}>{todo}</div>
<button css={S.DeleteButton} onClick={() => deleteTodo(index)}>
Delete
</button>
</li>
))}
</ul>
</div>
);
};
export default TodoList;
state
를 설정, useState
함수를 사용하여 todo
와 inputValue
라는 두 가지 상태를 설정todo
는 배열로서 사용자가 추가한 할 일 목록을 저장하며, inputValue
는 사용자가 할 일을 추가할 때 입력한 값을 저장useRef
를 이용하여 inputRef
변수를 설정, 이 변수는 JSX의 input 요소와 연결됨useEffect
Hook을 이용하여 렌더링 후 input 요소에 focus를 설정addTodo
함수는 사용자가 입력한 할 일을 todo
배열에 추가trim()
함수를 이용하여 입력한 문자열 앞 뒤 공백을 제거deleteTodo
함수는 인덱스에 해당하는 할 일 항목을 배열에서 삭제handleKeyPress
함수는 사용자가 Enter 키를 누르면 addTodo
함수를 호출-JSX를 이용하여 TodoList를 렌더링함
input
요소와 button
요소는 addTodo
함수와 inputValue
상태를 이용하여 할 일 항목을추가하는 기능을 구현ul
과li
요소는 todo
배열을 이용하여 할 일 목록을 나타냄deleteTodo
함수를 이용하여 해당 항목을 삭제TodoItem
,TodoText
,DeleteButton
과 같은 스타일을 가진 요소들은 style.js
파일에 작성된 CSS 스타일을 이용style.js
import { css } from '@emotion/react';
export const Container = css`
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 20px 10px 20px;
`;
export const TodoItem = css`
display: flex;
justify-content: space-between;
align-items: center;
width: 300px;
padding: 10px;
margin-bottom: 10px;
background-color: #f1f1f1;
border-radius: 5px;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3);
`;
export const TodoText = css`
flex: 1;
margin-right: 10px;
`;
export const DeleteButton = css`
background-color: #ff5c5c;
color: white;
border: none;
border-radius: 5px;
padding: 5px 10px;
cursor: pointer;
`;
export const Input = css`
margin-bottom: 10px;
`;
export const TodoList = css`
margin-bottom: 10px;
`;
Todo
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { FcPlus } from 'react-icons/fc';
import { BiPen } from 'react-icons/bi';
import { TiTrash } from 'react-icons/ti';
import React, { useState,useRef } from 'react';
const TodoContainer = css`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 100px;
width: 100%;
`;
const TodoAddition = css`
position: sticky;
top: 0px;
box-sizing: border-box;
margin-bottom: 20px;
border-radius: 7px;
padding: 10px;
width: 600px;
height: 60px;
background-color: #eee;
`;
const AdditionInput = css`
box-sizing: border-box;
border: none;
outline: none;
border-bottom: 1px solid white;
padding: 0px 50px 0px 10px;
width: 100%;
height: 100%;
font-size: 1.2rem;
background-color: #eee;
`;
const TodoAddButton = css`
position: absolute;
transform: translateY(-50%);
top: 50%;
right: 15px;
display: flex;
justify-content: center;
align-items: center;
border: none;
padding: 0;
width: 35PX;
height: 35px;
font-size: 1.3rem;
background-color: #ffffff00;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
transform: translateY(-50%) rotate(180deg) scale(1.5);
}
`;
const TodoList = css`
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
border-radius: 7px;
padding: 10px;
width: 600px;
background-color: #fafafa;
`;
const TodoContent = css`
width: 85%;
height: 40px;
`;
const ItemGroup = css`
display: flex;
align-items: center;
height: 40px;
`;
const ItemButton = css`
display: flex;
align-items: center;
border: none;
height: 100%;
color: #999;
background-color: #ffffff00;
cursor: pointer;
&:hover {
color: #121212;
}
`;
const Todo = () => {
const[input, setInput] = useState({
id:0,
content:''
});
const [todoList, setTodoList] = useState([]);
const todoId = useRef(1);
const onChange = (e) => {
setInput({
...input,
content: e.target.value
});
}
const onKeyUp = (e) => {
if(e.keyCode === 13) {
onClick();
}
}
const onClick = () => {
const todo ={
...input,
id: todoId.current++
}
setTodoList([...todoList, todo]);
setInput({...input,content: ''});
}
const onRemove = (id) => {
setTodoList(todoList.filter(
todo => {
return todo.id !== id;
}
))
}
return (
<div css={TodoContainer}>
<div css={TodoAddition}>
<input css={AdditionInput} type="text" placeholder="Add your new Todo" onChange={onChange} onKeyUp={onKeyUp} value={input.content} />
<button css={TodoAddButton} onClick={onClick}><FcPlus /></button>
</div>
{todoList.map(
todo => {
return(
<div css={TodoList} key={todo.id}>
<div css={TodoContent}>{todo.content}</div>
<div css={ItemGroup}>
<button css={ItemButton}><BiPen /></button>
<button css={ItemButton} onClick={() => onRemove(todo.id)}><TiTrash /></button>
</div>
</div>
);
}
)}
</div>
);
};
export default Todo;