리액트는 자바스크립트로 동적인 ui를 쉽게 제작할 수 있도록 해주는 라이브러리
가장 흔한 특징은 버츄얼돔으로 최적화 업데이트, spa(으)로 새로고침없이 동적인 ui를 보여줌 컴포넌트 기반 아키텍처, 단방향 데이터 흐름, jsx 활용문법등 이런걸 알아보기전 간단한 사용법으로 편리함을 보고감
1. 기존의 작성 방법의 변화
일단 이런 투두리스트에서 투두를 하나씩 추가할 때 자바스크립트를 사용함.
바닐라 자바스크립트 공통 html
<body>
<div id="todo-app">
<h1>To-Do List</h1>
<input type="text" id="new-task" placeholder="Add a new task" />
<button id="add-task-button">Add Task</button>
<ul id="task-list"></ul>
</div>
<script src="script.js"></script>
</body>
자바스크립트로 작성하는 방법에는 두 가지 방식이 있음.
하나는 돔을 만들어서 돔을 추가하는 방식.
//돔 기반 버전
document.addEventListener('DOMContentLoaded', () => {
const newTaskInput = document.getElementById('new-task');
const addTaskButton = document.getElementById('add-task-button');
const taskList = document.getElementById('task-list');
addTaskButton.addEventListener('click', () => {
const taskText = newTaskInput.value.trim();
if (taskText !== '') {
addTask(taskText);
newTaskInput.value = '';
}
});
taskList.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-button')) {
const taskItem = e.target.parentElement;
taskList.removeChild(taskItem);
}
});
function addTask(taskText) {
const taskItem = document.createElement('li');
taskItem.textContent = taskText;
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.className = 'delete-button';
taskItem.appendChild(deleteButton);
taskList.appendChild(taskItem);
}
});
하나는 자바스크립트 배열로 투두 리스트를 보관하고 innerHtml로 업데이트할 떄 마다 다시 렌더링하는 방법
// 스크립트 기반 버전
document.addEventListener('DOMContentLoaded', () => {
let todoList = [];
const newTaskInput = document.getElementById('new-task');
const addTaskButton = document.getElementById('add-task-button');
const taskList = document.getElementById('task-list');
addTaskButton.addEventListener('click', () => {
const taskText = newTaskInput.value.trim();
if (taskText !== '') {
addTask(taskText);
newTaskInput.value = '';
render();
}
});
taskList.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-button')) {
const index = e.target.dataset.index;
todoList = todoList.filter((v, i) => i !== +index);
render();
}
});
function addTask(taskText) {
todoList.push(taskText);
}
function render() {
const taskList = document.getElementById('task-list');
let items = '';
todoList.forEach((v, i) => {
items += `
<li>${v}
<button class="delete-button" data-index=${i}>Delete</button>
</li>
`;
});
taskList.innerHTML = items;
}
});
근데 고작 투두 하나 집어 넣는데 코드가 길어지고 나중에 유지보수 막막, 그리고 html 오타 나면 오류 찾느라 어디서부터 볼지도 막막
(아래 코드는 위의 코드)
//돔 기반 버전
document.addEventListener('DOMContentLoaded', () => {
const newTaskInput = document.getElementById('new-task');
const addTaskButton = document.getElementById('add-task-button');
const taskList = document.getElementById('task-list');
addTaskButton.addEventListener('click', () => {
const taskText = newTaskInput.value.trim();
if (taskText !== '') {
addTask(taskText);
newTaskInput.value = '';
}
});
function addTask(taskText) {
// 리스트 아이템을 어떻게 만들고
const taskItem = document.createElement('li');
taskItem.textContent = taskText;
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.className = 'delete-button';
// 어디에 추가해야 하는지
taskItem.appendChild(deleteButton);
taskList.appendChild(taskItem);
}
});
// 스크립트 기반 버전
addTaskButton.addEventListener('click', () => {
const taskText = newTaskInput.value.trim();
if (taskText !== '') {
addTask(taskText);
newTaskInput.value = '';
render();
}
});
function addTask(taskText) {
todoList.push(taskText);
}
function render() {
const taskList = document.getElementById('task-list');
let items = '';
// 리스트 아이템을 어떻게 만들고
todoList.forEach((v, i) => {
items += `
<li>${v}
<button class="delete-button" data-index=${i}>Delete</button>
</li>
`;
});
// 어디에 추가해야하는지
taskList.innerHTML = items;
}
하지만 리액트를 쓰면 좀더 간편해짐
아래는 리액트 코드인데 jsx는 나중에 설명 예정 (지금은 그냥 html 이랑 비슷한거라 보면됨)
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const handleAddTask = () => {
if (newTask.trim() !== '') {
setTasks([...tasks, newTask]);
setNewTask('');
}
};
const handleDeleteTask = (index) => {
const updatedTasks = tasks.filter((task, taskIndex) => taskIndex !== index);
setTasks(updatedTasks);
};
return (
<>
<h1>To-Do List</h1>
<input
type='text'
id='new-task'
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder='Add a new task'
/>
<button onClick={handleAddTask}>Add Task</button>
<ul id='task-list'>
{tasks.map((task, index) => (
<li key={index}>
{task}
<button
className='delete-button'
onClick={() => handleDeleteTask(index)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
}
위 리액트 코드에서 투두를 하나 추가하는 코드는 이거임. 기존 자바스크립트 코드는 어떻게 화면을 업데이트할지 일일이 작성해야 하는 반면에 리액트는 무엇을 할지만 작성하면됨
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
// 투두하나 추가해주는 함수
const handleAddTask = () => {
if (newTask.trim() !== '') {
// 투두 하나 추가
setTasks([...tasks, newTask]);
setNewTask('');
}
};
// 무엇을 보여줄지만 작성하면 상태(리스트 배열)이 변경될 때마다 알아서 업데이트함.
return (
<>
<h1>To-Do List</h1>
<input
type='text'
id='new-task'
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder='Add a new task'
/>
<button onClick={handleAddTask}>Add Task</button>
<ul id='task-list'>
{tasks.map((task, index) => (
<li key={index}>
{task}
<button
className='delete-button'
onClick={() => handleDeleteTask(index)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
2. 가상 돔
버츄얼돔은 리액트에서 돔(DOM)을 효율적으로 업데이트 하기 위한 간략화 된 e돔 임. 즉 돔에 들어있는 내용을 간략화하여 만든 것이 가상 돔.
리액트는 두 개의 가상 돔을 비교해서 변경된 부분을 확인하여 일괄적으로 실제돔에 업데이트를 해줌 이를 재조정(Reconciliation)과정이라고 함.
간단하게 리액트는 실제 돔을 추상화하고 데이터를 간략화 시켜 가상 돔 트리를 구성함.
아래 그림은 간랸화 한 걸 시각화
가상돔이 빠른건 아니지만, 실제 돔에 변경된 사항만 적용될 수 있게 사용하기 쉽게 최적화를 해줌
아래 그림은 두 개의 가상돔을 비교하여, 변경된 부분만 실제돔에 업데이트 하는 것을 보여주는 그림
- 두 가상돔을 비교해서 변경(추가, 업데이트, 삭제)된 부분을 찾음.
- 해당 부분들을 일괄적으로 실제 DOM에 업데이트시킴.
(이 부분은 가상돔을 사용해서 빠른지에 대해 의문이 가서 작성)
기본적으로 순수 자바스크립트로 DOM을 변경시켜도 느리지 않음. 오히려 더 빠름 (이유는 리액트는 두 개의 가상돔을 비교해서 업데이트 된 부부을 찾는 과정이 있기 때문(재조정 과정)). 하지만 순수자바스크립트로 아래처럼 최적화가 되지 않은 코드를 작성하기 때문에 빠르다고 한거.
결론적으로 가상돔을 사용해서 속도가 빨라지는게 아니라 리액트가 알아서 최적화를 해줘서 빠르다고 한거.
순수 자바스크립트 코드로 최적화를 시켜주는 간단한 코드를 알아보면 아래처럼, list를 렌더링 해주는 자바스크립트 코드가 있음.
function generateList(fruits) {
let ul = document.createElement('ul');
document.getElementByClassName('.fruits').appendChild(ul);
fruits.forEach(function (item) {
let li = document.createElement('li');
ul.appendChild(li);
li.innerHTML += item;
});
return ul
}
let fruits = ['Apple', 'Orange', 'Banana']
document.getElementById('#list').innerHtml = generateList(fruits)
근데 배열이 아이템 중 Apple → Pineapple로 변경되어 다시 렌더링을 해주려면 ul 리스트를 처음부터 다시 렌더링 함.
(이 부분에서 만약 렌더링할 리스트 아이템이 많으면 당연히 느려질 수 밖에 없음)
fruits = ['Pineapple', 'Orange', 'Banana']
document.getElementById('#list').innerHtml = generateList(fruits)
만약 모든 리스트를 다시 렌더링 하기 싫으면, 변경된 아이템만 찾아 해당 요소의 데이터만을 바꿔 줘야함. 이러면 최적화가 돼서 리액트보다 성능이 좋음.
// li 중 첫 번째 li를 찾아 테스트만 변경시켜줌
document.querySelector('li').innerText = fruits[0]
하지만 이런 귀찮은 과정(최적화 과정)을 리액트가 두 개의 가상돔( 하나는 데이터 변경이 일어나기 전 가상돔, 하나는 데이터 변경이 일어난 후 가상돔 )을 이용하여 변경된 부분만 일괄적으로 최적화 시켜, 실제 돔에 업데이트 시킴.
우리는 그 최적화하는 과정을 안 해도 된다는 말. (물론 나중에 리액트에서 간단한 최적화를 시켜줘야함)
3. 단방향 데이터 흐름
리액트는 기본적으로 데이터가 위에서 아래로 흐름. 하위 컴포넌트에서 상위 컴포넌트의 내부 데이터를 접근하거나, 조작 할 수 없음. 상위 데이터를 조작하려면 특정 함수를 만들어 내려주거나, 데이터를 내려줘야함.
여기서 데이터를 내려주는데 이 내려주는 데이터를 프롭(Prop)이라고 부름. 하위 컴포넌트는 이 프롭으로 데이터나, 상위 데이터를 조작하는 함수를 받아 사용할 수 있음.
4. 컴포넌트 기반
컴포넌트는 ui 블록. 독립적이고 재사용이 가능한 블록이라고 보면됨. 자주 사용하거나 중복이 되는 ui들은 컴포넌트로 만들어서 사용함.
export default function Blocks() {
return <div>나는 컴포넌트</div>;
}
//위의 컴포넌트를 여러개 사용
export default function App() {
return <>
<Blocks />
<Blocks />
<Blocks />
</>;
}
'프론트엔드 > React' 카테고리의 다른 글
[형을 위한 리액트] 2. JSX (0) | 2024.10.15 |
---|---|
setState 함수는 Dependency list에 넣어줘야 할까? (0) | 2024.01.17 |
상태(state) 업데이트의 비밀(batch)🤫 (1) | 2023.06.12 |
CRA없이 리액트(타입스크립트) 프로젝트 생성하기 (0) | 2023.03.12 |
Portals (0) | 2023.03.11 |