일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 추상화
- 투두앱만들기
- REACT
- 자바스크립트
- 상속
- frontend
- 자바스트립트
- typeScript
- 부트캠프
- Fetch
- 객체지향프로그래밍
- 패스트캠퍼스
- 캡슐화
- 프론트엔드
- github
- 불변성
- 웹개발
- Props
- OOP
- 타입스크립트
- 리액트
- js
- webdevelopment
- 노마드코더
- JavaScript
- CSS
- Hooks
- 논리연산자
- Zustand
- 클래스
- Today
- Total
connecting dots
React & TS 5회차 (7/22) | todo 리스트 만들기 실습, prop drilling, Flux 아키텍쳐, Redux, Storybook 본문
React & TS 5회차 (7/22) | todo 리스트 만들기 실습, prop drilling, Flux 아키텍쳐, Redux, Storybook
dearsuhyun 2024. 7. 31. 09:47todo 리스트 만들기 실습
input에 할 일 써서 추가버튼 누르면 리스트에 할 일 추가
import { useState } from "react";
function App() {
const [todos, setTodos] = useState([]);
const [todoText, setTodoText] = useState("");
}
const onChangeTodoText = (e) => {
setTodoText(e.target.value);
// target은 실제로 이벤트가 발생한 요소 = input 태그
// input type="text"는 value 속성을 가지고 있어서 value 속성을 통해 입력된 값을 가져올 수 있음
};
const onAddTodo = () => {
// 새로운 배열 만들고 원소들을 다 펼쳐서 다시 집어넣어 + 새롭게 추가할 원소를 추가
// 객체에서도 사용 가능
setTodos([...todos], {
id: todo.length + 1,
title: todoText,
done: false
});
setTodoText(""); // 추가하면 인풋 부분 비워주는 역할
};
return (
<div>
<h1>context & Flux 아키텍쳐</h1>
<div>
<label>할 일: </label>
<input type="text" value={todoText} onChange={onChangeTodoText} />
<button onChange={onAddTodo}>추가</button>
</div>
<div>
<ul>
{
// 배열 안의 값을 다른 값으로 바꿔줄 때는 항상 map 사용
todos.map((todo, index) => (
<li key={index}>
<input type="checkbox" checked={todo.done} />
<span>{todo.title}</span>
<button>삭제</button>
</li>
))
}
</ul>
</div>
</div>
);
삭제 기능 구현
const onDeleteTodo = (id) => {
// filter = 배열을 순회함(배열을 한 번만 읽어서 무언가를 하고 끝내는 것)
setTodos(todos.filter((todo) => todo.id !== id)); // id가 같지 않은 것만 남기고 나머지는 버림 (삭제효과)
};
<button onClick={() => onDeleteTodo(todo.id)}>삭제</button>
{/* 이벤트 시스템은 이벤트 객체를 줌. 우리는 id를 가진 호출, onDeleteTodo를 함수로 감싸줘야 함 */}
이벤트 핸들러와 함수 호출
1. 직접 호출
<button onClick={onDeleteTodo(todo.id)}>삭제</button>
• 이 코드는 onDeleteTodo(todo.id) 함수 호출의 반환값을 onClick 속성에 할당하는 것입니다.
• 함수 호출은 즉시 실행되며, 그 반환값이 onClick 속성에 할당됩니다.
• 예를 들어, onDeleteTodo(todo.id)가 undefined를 반환한다면, onClick={undefined}가 됩니다. 이는 이벤트 핸들러로써 작동하지 않습니다.
• 이 방식은 컴포넌트가 렌더링될 때 onDeleteTodo 함수가 즉시 실행됩니다.
• 즉, 버튼을 클릭하지 않아도 컴포넌트가 렌더링될 때마다 onDeleteTodo 함수가 호출됩니다.
• 이는 우리가 의도한 바가 아닙니다. 우리는 버튼을 클릭했을 때만 함수가 실행되기를 원합니다.
렌더링 시점에 함수 호출이 일어나는 이유
1. 즉시 호출: 함수 이름 뒤에 괄호 ()를 붙이면, 함수가 즉시 실행됩니다.
• 예: onDeleteTodo(todo.id)는 함수 호출입니다. 이 코드는 즉시 실행되고, 반환값이 onClick 속성에 할당됩니다.
2. 함수 정의 전달: 함수 이름 뒤에 괄호를 붙이지 않으면, 함수 그 자체를 전달합니다.
• 예: () => onDeleteTodo(todo.id)는 화살표 함수로, 새로운 함수를 정의합니다. 이 함수는 나중에 이벤트가 발생할 때 호출됩니다.
React에서 이벤트 핸들러 설정
기본구조
import React from 'react'; function App() { function handleClick() { alert('Button was clicked!'); } return ( <button onClick={handleClick}>Click me</button> // <button onClick={handleClick()}>Click me</button> // 이렇게 호출하면 즉시 호출 ! ); } export default App;
• 이벤트 핸들러로 함수 호출이 아닌 함수 정의를 전달해야 합니다. 즉, handleClick()이 아니라 handleClick을 전달합니다.
• 이렇게 하면 React는 이벤트가 발생할 때 전달된 함수를 호출합니다.
이벤트 핸들러에 매개변수 전달
이벤트 핸들러에 매개변수를 전달해야 하는 경우, 화살표 함수를 사용하여 함수 호출을 감싸는 방법을 사용합니다.
import React from 'react'; function App() { function handleClick(name) { alert(`Hello, ${name}!`); } return ( <button onClick={() => handleClick('Alice')}>Click me</button> ); } export default App;
위 코드에서 handleClick('Alice')는 버튼이 클릭될 때만 호출됩니다. 화살표 함수 () => handleClick('Alice')가 onClick 속성에 전달됩니다.
2. 함수로 감싸기
<button onClick={() => onDeleteTodo(todo.id)}>삭제</button>
• 이 방식은 버튼을 클릭했을 때만 onDeleteTodo 함수가 실행됩니다.
• 따라서 onClick 속성에는 화살표 함수 자체가 할당됩니다. 이 함수는 버튼이 클릭될 때 실행되며, 그 안에서 onDeleteTodo(todo.id)를 호출합니다.
토글 버튼으로 체크하는 기능구현
const onToggleTodo = (id) => {
if (todo.id === id) {
return {
...todo,
done: !todo.done,
// 거짓 -> 참, 참 -> 거짓
};
} else {
return todo;
}
};
<button onClick={() => onDeleteTodo(todo.id)}>삭제</button>
// 이벤트 시스템은 이벤트 객체를 줌. 우리는 id를 가진 호출, onDeleteTodo를 함수로 감싸줘야 함
return {
id: todo.id,
title: todo.title,
done: todo.done,
}
return {...todo}
// 같은 코드 !어떠한 객체의 key를 기반으로 새로운 객체를 만들고 있음(복사하는 형식으로)
// 요소 key가 많아지면 번거롭기 때문에 전개 연산자 사용
컴포넌트화
// App.jsx
import {} from "./item";
<div>
<ul>
{
// 배열 안의 값을 다른 값으로 바꿔줄 때는 항상 map 사용
todos.map((todo, index) => (
<Item
key={index}
id={todo.id}
title={todo.title}
done={todo.done}
onDelete={onDeleteTodo}
onDone={onToggleTodo}
/>
))
}
</ul>
</div>
</div>
export const Item = ({ id, title, done, onDelete, onDone }) => {
// props로 받아온 값을 사용할 때는 {}로 감싸줌
<li>
<span>{id}</span>
<input type="checkbox" checked={done} onChange={() => onDone(id)} />
<span>{title}</span>
<button onClick={() => onDelete(id)}>삭제</button>
</li>;
};
--> 컴포넌트화를 하면 App.jsx에서의 코드가 간단해지며 재사용이 가능 ..
but ! 치명적인 단점 --> prop drilling
Prop Drilling은 리액트 애플리케이션에서 상위 컴포넌트의 데이터(혹은 함수)를 여러 중간 컴포넌트를 거쳐 하위 컴포넌트로 전달하는 과정을 의미합니다. 이는 중간 컴포넌트가 실제로는 그 데이터를 사용하지 않지만, 단지 데이터를 전달하기 위해 props를 받는 상황을 발생시킵니다.
왜 Prop Drilling이 발생하나요?
리액트 컴포넌트는 트리 구조로 이루어져 있습니다. 특정 데이터가 트리의 최상위에 있는 컴포넌트에서 최하위 컴포넌트로 전달되어야 할 때, 모든 중간 컴포넌트는 그 데이터를 전달받아야 합니다. 이 과정이 Prop Drilling입니다.
Prop Drilling의 문제점
• 복잡성 증가: 많은 중간 컴포넌트를 거치게 되면 코드가 복잡해지고, 추적하기 어려워집니다.
• 유지보수 어려움: 중간 컴포넌트들이 많아질수록 props 전달 경로를 수정하는 것이 어렵고, 에러 발생 가능성이 높아집니다.
Prop Drilling의 해결책 --> Flux 아키텍쳐
학습 순서
1) flux 구조 학습
2) redux 기본 철학 학습
- 단일 스토어 정책: 앱이 아무리 커도 객체가 하나이고 필요한 데이터들을 key로 뽑아서 쓰는 형태로 이루어져 있음
- 불변 데이터: reducer가 data를 업데이트하는데, 업데이트 방식이 특이함
변수가 없고(=변하는 데이터가 없고) 상수만을 가지고 만듦 --> '데이터가 안 변한다면 ? 한번만 테스트 하면 되잖아' --> 함수형 프로그래밍
데이터는 불변 ! --> 데이터를 계속 새로 만드는 방식 --> UI는 늘 새로운 것을 구현하면 됨
3) react redux
4) 비동기 상태 관리 (ex. Redux Saga)
5) redux toolkit 및 기타 등등
Flux 아키텍쳐
UI를 만드는 데이터의 흐름을 한 방향으로 만들어내는 구조
Redux
Flux 아키텍쳐를 구현한 라이브러리
리액트 뿐만 아니라 js 등 다른 곳에서 쓰는 것도 가능
단일 스토어
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
상태 내의 무언가를 변경하기 위해서는 액션을 디스패치 해야함
액션(action)은 어떤 작업을 할지 설명해주는 자바스크립트 순수 객체
액션에 따라 어떤 리듀서를 연결해줄 것인지가 결정됨
컨벤션: 항상 type이라는 속성을 가지고 있어야 함
타입 문자열을 앱에서 유일해야 함 --> 잘못된 리듀서가 호출되지 않도록
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
리듀서는 그저 상태와 액션을 인자로 하고, 애플리케이션의 다음 단계를 리턴해주는 함수일 뿐
리듀서라는 함수에게 액션 객체를 전달해주면
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
--> 리듀서는 인자를 두 개받음
1) state(store 객체 전체 값)
2) action(전달된 액션 객체)
리듀서는 항상 무엇인가를 리턴함
바꾸기 직전의 현재 상태를 받고 내가 리턴한 걸로 바뀜 --> 다음 리듀서가 받음
내가 바꿀 현재 상태의 전체 데이터를 받아 내가 필요한 것을 바꿔서 새로 리턴하면 단일 스토어 전체가 내가 바꾼 걸로 다 업데이트 됨
동기적인 작업만 가능 --> redux saga(비동기 작업 처리해주는 대표적인 라이브러리)
Storybook
컴포넌트들을 문서처럼 만들 수있고 동작, 테스트 가능하고 prop 임의로 넣어 조작할 수 있음
--> 컴포넌트를 시각화하고 컴포넌트의 구현 코드를 문서화해줌
Next.js
https://velog.io/@rachel28/Prop-Drilling
[React] Prop Drilling 이란 무엇이고, 어떻게 해결할 수 있을까?
Props Drilling 이란 무엇이고, 어떻게 해결할 수 있을까?
velog.io
https://ko.react.dev/learn/managing-state
State 관리하기 – React
The library for web and native user interfaces
ko.react.dev
Redux - 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너. | Redux
자바스크립트 앱을 위한 예측 가능한 상태 컨테이너.
ko.redux.js.org
Redux-Saga - An intuitive Redux side effect manager. | Redux-Saga
An open source Redux middleware library for efficiently handling asynchronous side effects
redux-saga.js.org
https://storybook.js.org/tutorials/intro-to-storybook/react/ko/get-started/
Storybook Tutorials
Learn how to develop UIs with components and design systems. Our in-depth frontend guides are created by Storybook maintainers and peer-reviewed by the open source community.
storybook.js.org
Next.js by Vercel - The React Framework
Next.js by Vercel is the full-stack React framework for the web.
nextjs.org