2023. 9. 5. 13:43ㆍ기술 창고/React
App.jsx
import React, { useState } from "react";
function App() {
const [inputText, setInputText] = useState("");
const [items, setItems] = useState([]);
function handleChange(event) {
const newValue = event.target.value;
setInputText(newValue);
}
function addItem() {
setItems(prevItems => {
return [...prevItems, inputText];
});
setInputText("");
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input onChange={handleChange} type="text" value={inputText} />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{items.map(todoItem => (
<li>{todoItem}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
이전에 정리한 내용 중에 React 앱을 만들면서 HTML 구성 요소마다 따로 컴포넌트들을 분리하여 관리하는 방식이 일반적이고 효율적이라고 정리하였습니다.
위의 예시 코드는 App.jsx 에 필요한 HTML 구성 요소들을 한번에 몰아넣어 관리하는 형식으로 단순하게 앱 내용을 만들기 위해 만든 코드입니다.
이것을 이제 React 앱에 맞게끔 분리하여 Tree 형식으로 관리하게끔 만들어 보겠습니다.
우선 아이템 입력 후 버튼을 누르면 li 태그로 나오게끔 하는 부분을 분리하도록 하겠습니다.
App.jsx
import React, { useState } from "react";
import ToDoItem from "./ToDoItem";
function App() {
const [inputText, setInputText] = useState("");
const [items, setItems] = useState([]);
function handleChange(event) {
const newValue = event.target.value;
setInputText(newValue);
}
function addItem() {
setItems(prevItems => {
return [...prevItems, inputText];
});
setInputText("");
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input onChange={handleChange} type="text" value={inputText} />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{items.map(todoItem => (
<ToDoItem text={todoItem}/>
))}
</ul>
</div>
</div>
);
}
export default App;
ToDoItem.jsx
import React from "react"
function toDoitems(props){
return (
<li>
{props.text}
</li>
)
}
export default toDoitems;
(1)
우선 li 태그를 만들어줄 ToDoItem 컴포넌트를 만들어줍니다.
호출되서 return 할 시 li 태그를 반환하며, 입력한 아이템 명이 props 매개변수를 통해 들어가 적용되어 li 태그가 반환될 것입니다.
(2)
App.jsx 에서 만들어준 ToDoItem.jsx 를 import 받고, 입력받은 아이템들을 가지고있는 useState items변수를 map 함수를 통해 import한 ToDoItem 컴포넌트에 props에 넣어 호출합니다.
(3)
ToDoItem 컴포넌트가 호출되면서 text props를 같이 넣어주어 li 태그가 정상적으로 호출되게 됩니다.
이번에는 추가적으로 리스트업된 목록에서 아이템을 클릭하면 완료되었다는 의미로 선을 긋고, 다시 클릭하면 선이 사라지게끔 CSS를 적용시켜보겠습니다.
ToDoItem.jsx
import React, { useState } from "react"
function ToDoitems(props){
const [value, setValue] = useState(false);
function valueChange(){
value ? setValue(false) : setValue(true);
}
return (
<div onClick={valueChange}>
<li style={{ textDecoration: value ? "line-through" : "none" }}>
{props.text}
</li>
</div>
)
}
export default ToDoitems;
시나리오는 이렇습니다.
생성된 아이템을 클릭하면 true, false 변수값이 부여되고 해당 변수값에 따라 아이템 텍스트에 스타일이 적용되는 것입니다.
(1)
useState 변수를 통해 true, false 값을 가질 value 변수를 만들어줍니다.
(2)
li 태그 상위에 div 태그를 추가하여 아이템을 클릭 시 특정 함수가 동작되게끔 만들어줍니다.
(3)
특정 함수에는 현재 value 변수의 값에 따라 true면 다시 false로, false면 다시 true 로 바꿔지게끔 하는 로직이 들어갑니다.
저는 삼항 연산자를 이용하였습니다.
(4)
li 태그에 style 속성을 추가하여 textDecoration 스타일을 적용합니다.
삼항 연산자를 활용하여 value 가 true 면 line-through 스타일이 적용되어 선이 그어지게 되고, 다시 누르면 선이 사라지게 됩니다.
지금과 같은 방식은 로컬라이즈한 코드 동작 방식이라고 할 수 있습니다.
상위에 있는 요소를 하위 요소에서 추가로 스타일을 적용시키거나 하는 로컬한 동작 방식이라고 볼 수 있습니다.
이번에는 아이템을 클릭하면 아예 리스트에서 사라지게끔 만들어주겠습니다.
App.jsx
import React, { useState } from "react";
import ToDoItem from "./ToDoItem";
function App() {
const [inputText, setInputText] = useState("");
const [items, setItems] = useState([]);
function handleChange(event) {
const newValue = event.target.value;
setInputText(newValue);
}
function addItem() {
setItems(prevItems => {
return [...prevItems, inputText];
});
setInputText("");
}
function deteleItem(id){
setItems(prevItems => {
return prevItems.filter(
(item, index) => {
return index !== id;
}
)
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input onChange={handleChange} type="text" value={inputText} />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{items.map((todoItem, index) => (
<ToDoItem
key={index}
id={index}
text={todoItem}
onChecked={deteleItem}
/>
))}
</ul>
</div>
</div>
);
}
export default App;
ToDoItem.jsx
import React, { useState } from "react"
function ToDoitems(props){
return (
<div
onClick={() => {props.onChecked(props.id)}
}>
<li>
{props.text}
</li>
</div>
)
}
export default ToDoitems;
(1)
App.jsx 에서 ToDoItem을 호출할 부분에 props로 넘길 속성을 key와 id, 그리고 onChecked를 만들어주었습니다.
key는 고유한 key값, id는 데이터 구분값을 의미합니다.
같은 index 값을 공유하도록 하였습니다.
(2)
배열 변수를 사용하게 될 때 기본적으로 배열을 구성하는 정보들이 순서대로 부여가 되어있는데, 첫번째 정보는 값, 두번째 정보는 index(위치)값 입니다.
따라서 아이템들이 담겨있는 items 배열 변수에 map 함수를 적용하는데 이 때 Arrow function을 통해 ToDoItem을 호출하게 될 시 넘겨줄 key와 id 값을 이 index 값으로 넣어주겠다는 의미입니다.
컴포넌트를 호출할 때 전달할 props 속성에는 단순히 변수 뿐만이 아니라 함수 또한 넣어서 보내줄 수 있는데 이러한 특성을 이용하여 onChecked 속성에는 deleteItem 함수를 넣어주었습니다.
(3)
deleteItem 함수는 선택한 id 정보를 받아 기존에 아이템들이 있는 배열 변수에 filter 함수를 통해 선택한 id를 가진 아이템을 제외한 나머지 아이템들만이 존재하게끔 만들어주었습니다.
즉, 삭제를 한다기 보다는 필터링을 통해 선택한 아이템을 제외한 나머지 아이템들만을 보여주게끔 바꿔주겠다는 것입니다.
(4)
호출한 ToDoItem.jsx 의 클릭 영역에는 전달받은 props 의 onChecked 속성에 적용된 deleteItem 함수를 받아왔고, 동시에 넘어온 해당 아이템의 id 속성을 그대로 전달하여 삭제 함수가 동작되게끔 하였습니다.
즉, 실제 동작될 함수 부분과 클릭 영역을 이어주는 중간 컴포넌트라고 보면 될 것입니다.
여기서 onClick 속성에 그냥 함수를 사용하게끔 만들어준 것이 아니라 Arrow function으로 해당 함수를 실행하게끔 만들어주었는데
onClick={props.onChecked(props.id)}
-> onClick에 전달받은 props 함수를 그대로 넣게 되면 렌더링이 되고 난 이후에 함수가 전체적으로 수행됨.
따라서 Add 버튼을 눌렀을 때 아이템이 추가되는 것 뿐만 아니라 아이템 삭제 함수도 같이 실행됨.
onClick={() => {props.onChecked(props.id)}}
-> Arrow function을 사용하면 렌더링이 되고나서 해당 동작 함수를 실행시켰을 때 비로소 그 함수만이 수행됨.
따라서 Add 버튼을 눌렀을 때는 아이템 추가만 수행되고, 삭제 클릭하면 삭제만 수행됨.
위와 같은 이유로 인해 Arrow function을 이용해주었습니다.
'기술 창고 > React' 카테고리의 다른 글
[React] Spread 연산자 (0) | 2023.08.31 |
---|---|
[React] Complex한 state 관리 (0) | 2023.08.31 |
[React] 클래스 Components VS 기능 Components (0) | 2023.08.29 |
[React] React Form (추가 이벤트 핸들링) (0) | 2023.08.29 |
[React] React 이벤트 핸들링 (0) | 2023.08.28 |