먼저 리액트 앱을 타입스크립트와 같이 사용하려면,
$ npx create-react-app ts-react-tutorial --typescript
명령어를 사용하면 됩니다. (ts-react-tutorial부분은 앱 이름입니다.)
전체 폴더 구조
스타일링은 styled-components를 사용하였습니다.
styled-components는 자바스크립트 파일 안에서 CSS를 사용할 수 있도록 해주는
(CSS-In-JS)라이브러리입니다.
styled-components 설치
npm i styled-components
타입스크립트와 함께 사용하려면, 다음의 명령어를 추가로 실행해야 합니다.
npm i -D @types/styled-components
🌕Todo의 타입을 지정해보자
할 일 목록(Todo)에는 고유한 id, 실제 할일, 선택되었는지 아닌지
3가지가 필요하다고 생각했습니다.
Todo의 타입을 constants.ts파일에서 새로 생성합니다.
src/constants.ts
type Todo = {
id: string;
text: string;
selected: false;
};
export default Todo;
🌕고유한 ID를 어떻게 만들까?
Todo의 id는 고유해야 하고, 중복이 없게 만들고 싶었습니다.
uuid라이브러리를 사용하면, 고유한 id값을 만들 수 있습니다.
UUID
uuid란 Universal Unique Identifier(범용 단일 식별자)의 약자입니다.
uuid 함수를 호출하면, 랜덤으로 생성된 문자열이 만들어집니다.
UUID를 설치해줍니다
npm install uuid
위의 명령어로 에러가 발생한다면, 다음의 명령어를 통해 설치할 수 있습니다.
npm install uuid --legacy-peer-deps
타입스크립트 사용시 추가로 설치해줍니다.
npm install --save @types/uuid --legacy-peer-deps
https://www.npmjs.com/package/uuid
🌕컴포넌트 : Todotemplate.tsx
Todotemplate.tsx : 위의 그림에서 일정관리 부분에 해당합니다.
할 일 입력부분과, 할 일을 리스트 랜더링 하는 부분을 하위 컴포넌트로 설정했습니다.
일정 관리부분에 styled-components를 적용해보겠습니다.
먼저 제일 상단에 styled-components를 가져오기 위한 코드를 추가합니다.
import styled from 'styled-components';
먼저 전체 소스 코드입니다. (Components/Todotemplate.tsx)
import styled from 'styled-components';
const TodoTemplate = ({ children }: { children: JSX.Element[] }) => {
return (
<StyledTodoTemplate>
<StyledTodoTitle>일정 관리</StyledTodoTitle>
<StyledTodoContent>{children}</StyledTodoContent>
</StyledTodoTemplate>
);
};
const StyledTodoTemplate = styled.article`
@media (max-width: 1920px) {
width: 512px;
margin: 6rem auto;
border-radius: 4px;
}
@media (max-width: 767px) {
width: 300px;
margin: 3rem auto;
border-radius: 2px;
}
`;
const StyledTodoTitle = styled.section`
background: #22b8cf;
color: white;
font-size: 1.5rem;
display: flex;
justify-content: center;
align-items: center;
height: 4rem;
font-weight: 700;
`;
const StyledTodoContent = styled.section`
background-color: white;
`;
export default TodoTemplate;
모든 컴포넌트를 감싸는 부분입니다.
styled-components에서 반응형 웹을 만들떄, 밑처럼 적용할 수 있었습니다.
const StyledTodoTemplate = styled.article`
@media (max-width: 1920px) {
width: 512px;
margin: 6rem auto;
border-radius: 4px;
}
@media (max-width: 767px) {
width: 300px;
margin: 3rem auto;
border-radius: 2px;
}
`;
다음으로 실제 일정 관리영역입니다.
전체 부분에서 아래의 사진 부분
const StyledTodoTitle = styled.section`
background: #22b8cf;
color: white;
font-size: 1.5rem;
display: flex;
justify-content: center;
align-items: center;
height: 4rem;
font-weight: 700;
`;
그리고, 하위 컴포넌트들의 배경색을 정해주는 부분입니다.
배경색은 흰색으로 하였습니다.
const StyledTodoContent = styled.section`
background-color: white;
`;
🌕컴포넌트 : TodoInsert.tsx
TodoInsert.tsx : 할 일을 입력받는 컴포넌트입니다.
아래 그림의 일정 관리 밑 '할 일을 입력하세요'부분이 이에 해당합니다.
전체 소스 코드
import { MdAdd } from 'react-icons/md';
import styled, { keyframes } from 'styled-components';
export default function TodoInsert({ newtodoRef, onsubmit }:
{ newtodoRef: React.RefObject<HTMLInputElement>; onsubmit: Function }) {
return (
<StyledDiv>
<StyledInput ref={newtodoRef} placeholder="할 일을 입력하세요" />
<StyledButton type="submit" onClick={() => onsubmit(newtodoRef)}>
<MdAdd />
</StyledButton>
</StyledDiv>
);
};
const StyledDiv = styled.div`
display: flex;
background: #495057;
`;
const fadein = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
transform: none;
}
`;
const StyledInput = styled.input`
background: none;
animation: ${fadein} 1.5s linear;
outline: none;
border: none;
width: 100%;
display: flex;
font-size: 1rem;
&::placeholder {
color: #dee2e6;
}
`;
const StyledButton = styled.button`
background: none;
outline: none;
background: #868e96;
cursor: pointer;
border: none;
display: flex;
justify-content: center;
align-items: center;
&:hover {
background: #adb5bd;
}
`;
🌕CSS부분
전체 TodoInsert컴포넌트를 감싸는 부분입니다.
배경색과 flex를 지정했습니다.
const StyledDiv = styled.div`
display: flex;
background: #495057;
`;
styled-components에서 keyframe으로 애니메이션을 사용할 수 있습니다.
먼저 keyframe을 styled-components에서 가져옵니다.
import styled, { keyframes } from 'styled-components';
그리고, 애니메이션을 나타내는 변수를 생성해줍니다.
fadein이라는 변수로 애니메이션을 다음과 같이 만들 수 있습니다.
const 변수명 = keyframes`
--이부분에 애니메이션 적용--
`
const fadein = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
transform: none;
}
`;
다음으로 입력칸을 꾸며주는 부분입니다.
앞서 선언한 애니메이션 변수를 ${변수이름}으로 사용하였고,
Input의 placeholder색상을 지정해주었습니다.
const StyledInput = styled.input`
background: none;
animation: ${fadein} 1.5s linear;
outline: none;
border: none;
width: 100%;
display: flex;
font-size: 1rem;
&::placeholder {
color: #dee2e6;
}
`;
마지막으로, 입력을 다하고 추가를 할떄, 추가 버튼입니다.
const StyledButton = styled.button`
background: none;
outline: none;
background: #868e96;
cursor: pointer;
border: none;
display: flex;
justify-content: center;
align-items: center;
&:hover {
background: #adb5bd;
}
`;
TodoInsert컴포넌트는, Input형식의 Ref객체를 받아오고, onsubmit이라는 함수를 받아옵니다.
입력부분에 ref를 달아주고, 추가(+버튼)을 누르면 onsubmit함수가 실행됩니다.
export default function TodoInsert({ newtodoRef, onsubmit }:
{ newtodoRef: React.RefObject<HTMLInputElement>; onsubmit: Function }) {
return (
<StyledDiv>
<StyledInput ref={newtodoRef} placeholder="할 일을 입력하세요" />
<StyledButton type="submit" onClick={() => onsubmit(newtodoRef)}>
<MdAdd />
</StyledButton>
</StyledDiv>
);
}
이제, 실제 할 일을 나타내는 컴포넌트를 만듭니다.
react-icons: 여러 아이콘을 리액트에서 사용하기
npm install react-icons --save // npm
yarn add react-icons // yarn
🌕컴포넌트 : TodoListitem.tsx
TodoListitem.tsx : 실제 할 일 하나하나를 나타내는 컴포넌트입니다.
먼저 전체 소스 코드입니다.
import { useState } from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank, MdRemoveCircleOutline } from 'react-icons/md';
import styled, { css, keyframes } from 'styled-components';
import Todo from '../constants';
const TodoListItem = ({ todo, onremove }: { todo: Todo; onremove: Function }) => {
const [selected, setselected] = useState<Boolean>(todo.selected);
return (
<StyledArticle id={todo.id}>
<StyledSection state={selected}>
{selected ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<StyledText onClick={() => setselected(!selected)}>{todo.text}</StyledText>
</StyledSection>
<StyledSelectedSection onClick={() => onremove()}>
<MdRemoveCircleOutline />
</StyledSelectedSection>
</StyledArticle>
);
};
const fadein = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
transform: none;
}
`;
const StyledArticle = styled.article`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
&:nth-child(even) {
background-color: #f8f9fa;
}
gap: 10px;
`;
const StyledText = styled.div`
font-weight: 700;
cursor: pointer;
`;
const StyledSection = styled.section<{ state: Boolean }>`
display: flex;
width: 100%;
align-items: center;
animation: ${fadein} 2s linear;
svg {
font-size: 1.5rem;
cursor: pointer;
}
border-bottom: 1px solid #dee2e6;
${({ state }) => state === true && selectedStyle}
`;
const selectedStyle = css`
svg {
color: #22b8cf;
}
color: #adb5bd;
text-decoration: line-through;
`;
const StyledSelectedSection = styled.section`
display: flex;
align-items: center;
color: #ff6b6b;
font-size: 1.5rem;
cursor: pointer;
&:hover {
color: #ff8787;
}
`;
export default TodoListItem;
실제 할일에 해당하는 컴포넌트이므로, 위에서 선언했던, Todo타입을 가져왔고,
styled-components도 마찬가지로 가져왔습니다.
할일 선택시, 체크박스 아이콘, 제거를 표현하는 아이콘도 가져옵니다.(react-icons)
import { useState } from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank, MdRemoveCircleOutline } from 'react-icons/md';
//아이콘 가져오기
import styled, { css, keyframes } from 'styled-components';
//styled-components
import Todo from '../constants';
//Todo타입
TodoListitem은, todo와 onremove를 받습니다.(TodoList컴포넌트에서 받아옴)
todo는 실제 할 일을나타내고, onremove는 todo를 제거하는 함수입니다.
실제 할 일은 todo타입이고, onremove는 함수이므로, Function을 타입으로 지정했습니다.
const TodoListItem = ({ todo, onremove }: { todo: Todo; onremove: Function })
🌕CSS부분
할 일을 감싸는 부분입니다.
const StyledArticle = styled.article`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
&:nth-child(even) {
background-color: #f8f9fa;
}
gap: 10px;
`;
todo를 선택하면, 밑의 그림과 같이 선택된 표시가 나와야합니다.
할 일이 선택되었는지 아닌지 알기위해 Booelan타입의 useState를 사용합니다.
const [selected, setselected] = useState<Boolean>(todo.selected);
todo타입의 selected의 기본값은 false입니다.
type Todo = {
id: string;
text: string;
selected: false;
};
만약, 할 일을 누른다면, selected의 상태가 바뀌어야 합니다.
이를 위해, 먼저 새로운 styled-components를 하나 생성합니다.(section태그)
그리고, 이 section태그는 할 일이 선택되었는지, 선택되지 않았는지를 받아옵니다
선택여부는 Boolean타입으로 설정하였습니다.
(위의 Todo의 selected가 Boolean타입이기 떄문입니다).
const StyledSection = styled.section<{ state: Boolean }>
이제 이 state값이 true라면, 즉 선택된 상태라면, 다음의 코드가 유효합니다.
${({ state }) => state === true && selectedStyle}
state가 참이라면, selectedStyle을, 그렇지 않다면 false가 평가 결과가 될 것입니다.
const StyledSection = styled.section<{ state: Boolean }>`
display: flex;
width: 100%;
align-items: center;
animation: ${fadein} 2s linear;
svg {
font-size: 1.5rem;
cursor: pointer;
}
border-bottom: 1px solid #dee2e6;
${({ state }) => state === true && selectedStyle}
`;
이제 selectedStyle을 만들겠습니다. (할 일이 선택된다면 추가로 적용될 스타일)
styled-components에서 css변수를 사용할 수 있습니다.
const selectedStyle = css`
svg {
color: #22b8cf;
}
color: #adb5bd;
text-decoration: line-through;
`;
selectedStyle은 css변수이고, svg의 색상을 지정해주었고, 취소선을 그어줍니다.
글로 하니까, 복잡한데..그림으로 나타내면 다음과 같습니다.
const TodoListItem = ({ todo, onremove }: { todo: Todo; onremove: Function }) => {
const [selected, setselected] = useState<Boolean>(todo.selected);
return (
<StyledArticle id={todo.id}>
<StyledSection state={selected}>
{selected ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<StyledText onClick={() => setselected(!selected)}>{todo.text}</StyledText>
</StyledSection>
<StyledSelectedSection onClick={() => onremove()}>
<MdRemoveCircleOutline />
</StyledSelectedSection>
</StyledArticle>
);
};
위에서 StyledSection은 선택 영역이고, StyledSelectedSection은 제거 영역입니다.
선택 여부에 따라 다른 아이콘을 랜더링해주고,
(선택 시 MdcheckBox아이콘을, 선택 X시 MdCheckBoxOutlineBlank아이콘을)
할 일에 해당하는 문자열을 누르면, state가 바뀝니다.
StyledSelectedSection(제거 영역)을 누르면, 받아온 onremove함수가 실행됩니다.
선택 X시
선택 시
🌕컴포넌트 : TodoList.tsx
TodoList.tsx : 전체 할 일 목록을 리스트 랜더링 해주는 컴포넌트입니다.
상단부 부분
import TodoListItem from './TodoListitem';
import Todo from '../constants';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
TodoListitem이 하나하나의 할 일 목록이므로,
이를 가져왔고, 리스트 랜더링 시, 랜덤한 키 값을 만들기 위해 uuidv4함수도 가져왔습니다.
🌞 리스트 랜더링 시, 랜덤 키 값의 중요성
리액트에서, 컴포넌트의 속성이나 상태가 바뀔떄마다 render함수를 호출합니다.
render함수는 새 리액트 요소를 반환하고, 기존 요소 트리와 비교해, 새로운 변경점에서만
리랜더링을 수행합니다.
리액트에서 두 트리를 비교하기위해 key속성을 사용하며,
자식 요소들을 반복적으로 랜더링하는 과정에서, key를 사용합니다.
즉, 두 트리를 비교할 떄 key를 비교하고, key가 달라지면 랜더링도 다시합니다.
key prop에는 item의 id를 지정하거나, react uid 라이브러리를 이용해 고유한 key를 지정해주어야 합니다.
할 일을 제거하는 onremove함수
const onRemove = (text: string) => {
setTodo(todo.filter((eachtodo) => eachtodo.text !== text));
};
전체 소스 코드
import TodoListItem from './TodoListitem';
import Todo from '../constants';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
const TodoList = ({ todo, setTodo }: { todo: Todo[]; setTodo: Function }) => {
const onRemove = (text: string) => {
setTodo(todo.filter((eachtodo) => eachtodo.text !== text));
};
return (
<>
{todo.map((eachtodo) => (
<TodoListItem
key={uuidv4()}
todo={{
id: eachtodo.id,
text: eachtodo.text,
selected: eachtodo.selected,
}}
onremove={() => onRemove(eachtodo.text)}
/>
))}
</>
);
};
export default TodoList;
마지막 App.tsx
import './App.css';
import TodoTemplate from './Components/TodoTemplate';
import TodoInsert from './Components/TodoInsert';
import TodoList from './Components/TodoList';
import React, { useState, useRef, useCallback, useMemo } from 'react';
import Todo from './constants';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [todo, setTodo] = useState<Todo[]>([]);
const newtodoRef = useRef<HTMLInputElement>(null);
const onSubmit = (newtext: React.RefObject<HTMLInputElement>) => {
if (newtext.current === null) {
return;
}
const newtodo: Todo = {
id: uuidv4(),
text: String(newtext.current.value),
selected: false,
};
setTodo([...todo, newtodo]);
newtext.current.value = '';
};
return (
<>
<TodoTemplate>
<TodoInsert newtodoRef={newtodoRef} onsubmit={onSubmit} />
<TodoList todo={todo} setTodo={setTodo} />
</TodoTemplate>
</>
);
}
export default React.memo(App);
https://github.com/khj0426/Today-I-learnded/tree/main/React/my-app
'리액트' 카테고리의 다른 글
useState와 클로저 (2) | 2023.04.22 |
---|---|
BroswerRouter와 HashRouter의 차이점 (0) | 2023.04.15 |
리액트로 스크롤 애니메이션을 넣어보자! (0) | 2023.04.09 |
언제 컴포넌트가 랜더링 되는 걸까? (0) | 2023.04.01 |
간단한 뉴스 뷰어 만들기 (0) | 2023.01.19 |