1. 개요
상단의 이미지는 필자가 예전에 만들었던 To Do List App의 모습이다.
처음 일정을 만들고, 일정 아이템에서 '완료' 버튼을 클릭하면 해당 일정이 완료 처리가 되는 식이다.
Microsoft의 To Do라는 프로그램을 실행한 모습을 가져와봤다.
이제 위의 이미지처럼 특정 일정의 체크박스를 클릭 시 해당 일정이 완료되게 해보려고 한다.
버튼을 클릭해서 일정을 완료하는 것이 아니라, 좀 더 일반적인 To Do List에 가깝게 말이다.
실제로 구현 완료한 예제는 바로 다음 장에서 다루도록 하겠다.
2. To Do List
① Version 1 / To Do Item 한 개만 사용
예제 소스코드는 다음과 같이 작성하였다.
import {useState] from "react";
import {styled] from "styled-components";
interface I_ToDoItem {
ischecked: boolean;
}
const Wrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
`;
const ToDoItem = styled.div<I_ToDoItem>`
display: flex;
border: 2px solid black;
border-radius: 18px;
width: 200px;
padding: 5px;
margin: 3px 0px;
color: ${(props) => (props.isChecked ? "gray" : "black")};
background-color: ${(props) => props.isChecked ? "rgb(214, 210, 210)" : "rgb(250, 250, 250)"};
text-decoration: ${(props) => (props.isChecked ? "line-through" : "none")};
`;
const CheckBox = styled.input`
width: 25px;
height: 25px;
border: 2px solid black;
border-radius: 20px;
background-clip: content-box;
padding: 1.5px;
appearance: none;
&:checked {
background-color: rgb(135, 135, 148);
}
`;
const TextBox = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 150px;
font-weight: bold;
font-size: 18px;
`;
export function ToDoList(){
const [Checked, setChecked] => useState(false);
const onChange = () => setChecked((prev) => !prev);
return (
<Wrapper>
<ToDoItem>
<CheckBox type-"checkbox" onChange={onChange} />
<TextBox>아침 일정</TextBox>
</ToDoItem>
</Wrapper>
);
}
checkbox 클릭 여부를 boolean type의 state로 관리하고
checkbox 클릭 시, onChange Event Handler를 통해 해당 state의 값을 변경한다.
그리고 이를 styled-components인 <ToDoItem />의 props로 전달
props로 전달한 state의 값에 따라 color, background-color, text-decoration을 다르게 설정하였다.
여기서 checkbox의 Style을 기본 형태가 아닌 내가 원하는 형태로 적용하기 위해서
'appearance' 속성을 'none'으로 설정하였고, 'background-clip' 속성은 'content-box'로 설정하였다.
둘 다 처음 사용해 보는 CSS 속성이기 때문에 별도의 메모를 남겨두도록 하겠다.
CSS 'appearance' 속성
- OS나 브라우저의 기본 테마를 기반으로 요소를 표현하는 속성
- 위의 예제에서는 <input type='checkbox'/>의 기본 값, 기본 모양을 해제하려고 할 때 사용했다.
<style> input { appearance: none; }; </style> <body> <input type="checkbox" /> </body>
CSS background-clip 속성
- 요소의 배경이 테두리, 안쪽 여백, 콘텐츠 상자 중, 어느 부분까지 차지할 지를 결정하는 속성
- 예제에서는 체크박스 check 여부를 알려주는 원의 크기를 설정할 때 사용하였다.background-clip: border-box; /*배경이 테두리, border의 바깥 경계까지 차지*/ background-clip: padding-box; /*배경이 안쪽 여백, padding의 바깥 경계까지 차지*/ background-clip: content-box; /*배경을 content box에 맞춰서 설정된다.*/ background-clip: text; /*배경을 content box 내부, text 부분만 적용되게 설정.*/
② Version 2 / To Do Item 여러 개 사용
사실 앞의 예제는 맛보기 같은 느낌으로 메인은 이쪽이라고 봐도 무방하다.
예제 소스코드와 실행 모습은 다음과 같다. (CSS 설정은 건드리지 않았으니 첨부하지는 않겠다.)
import {useState} from "react";
import {styled} from "styled-components";
interface I_ToDoItem {/*...*/};
interface I_Items {
itemId: string;
itemNm: string;
isChecked: boolean;
};
const Wrapper = styled.div`...`;
const ToDoItem = styled.div<I_ToDoItem>`...`;
const CheckBox = styled.input`...`;
const TextBox = styled.div`...`;
function ToDoList() {
const [Items, setItems] = useState<I_Items[]>([
{
itemId: "todo01",
itemNm: "아침 일정",
isChecked: false,
},
{
itemId: "todo02",
itemNm: "점심 일정",
isChecked: false,
},
{
itemId: "todo03",
itemNm: "저녁 일정",
isChecked: false,
},
]);
const itemChecked = (event: React.ChangeEvent<HTMLInputElement>) => {
const {
currentTarget: { value },
} = event;
setItems((oldItems) => {
const targetIdx = oldItems.findIndex((item) => value === item.itemId);
const targets = oldItems[targetIdx];
const newItems: I_Items = {
itemId: targets.itemId,
itemNm: targets.itemNm,
isChecked: !targets.isChecked,
};
return [
...oldItems.slice(0, targetIdx),
newItems,
...oldItems.slice(targetIdx + 1),
];
});
};
return (
<Wrapper>
{Items.map((item) => {
return (
<ToDoItem key={item.itemId} isChecked={item.isChecked}>
<CheckBox
type="checkbox"
value={item.itemId}
onChange={itemChecked}
/>
<TextBox>{item.itemNm}</TextBox>
</ToDoItem>
);
})}
</Wrapper>
);
}
기본적인 틀은 Checkbox의 체크 여부를 boolean type의 state로 관리하고
이를 <styled />의 props로 전달해서 이에 맞게 CSS style을 바꾸는 것은 똑같다.
다만 To Do Item의 개수가 늘어났기 때문에, 각 To Do Item에 isChecked라는 property 추가해서
해당 Property를 통해서 Checkbox의 체크 여부를 개별적으로 관리하게 하였다.
이제 onChange Event Handler를 수정해야 하는데이 부분에서 애를 좀 먹었다.
우선 전체 Item을 저장해둔 state에서 클릭한 Item과 동일한 Item을 찾아야 하는데
이를 위해서 array.filter(), array.find() 등 여러 배열 메서드를 활용해서 가져오려고 했지만
번번이 실패하였고 머리를 조금 식히고 나서 예전에 개발했던 To Do List App의 코드를 보다가
혹시나 하는 마음에 아래 방법을 시도해 봤고, 결과는 성공적이었다.
setItems((oldItems) => {
const targetIdx = oldItems.findIndex((item) => value === item.itemId);
const targets = oldItems[targetIdx];
const newItems: I_Items = {
itemId: targets.itemId,
itemNm: targets.itemNm,
isChecked: !targets.isChecked,
};
return [
...oldItems.slice(0, targetIdx),
newItems,
...oldItems.slice(targetIdx + 1)
]
);
'array.findIndex()' 메서드를 통해서 클릭한 Item과 동일한 Id 가진 Item의 Index 받고
배열 값을 사용할 때 쓰는 대괄호 표기법 (array[index]) 통해서 해당 Item 값을 가져왔고
그리고 가져온 Item의 isChecked props의 값을 수정한다. (true ↔ false)
이하의 방법을 통해서 각 To Do Item의 checkbox가 개별적으로 동작할 수 있게 됐다.
3. 마치며
원래대로면 사이드 프로젝트를 다 끝내고 나서 이걸 올릴 생각이었지만
그때쯤이면 실기 시험을 준비하고 있을 테고, 실기가 끝난 뒤에 올린다고 해도
빨라봐야 5월일 것 같아서 그냥 지금 올려두기로 했다.
목표는 실기 접수 전까지 사이드 프로젝트를 완성하는 것이다.
부지런히 달려보자 🏃♂️
'React > React' 카테고리의 다른 글
[React] Hook의 규칙 (0) | 2025.02.15 |
---|---|
[React] Toggle Button 구현하기 (0) | 2024.08.07 |
[React Query] useQuery() (0) | 2024.08.03 |
[React] Plugin "react" was conflicted between "package.json (0) | 2024.06.03 |
[React] state (0) | 2024.01.29 |