세가지 기능을 활용해서 To Do list 만들기!
함수형 프로그래밍! (클래스형은 따로 있다.)
리액트는 스테이트와 프롭스가 변동되면 랜더링한다.
(이번 프로젝트는 기능구현이 우선이라 CSS(꾸미기)는 하지 않았다.)
와이어프레임
와이어프레임대로 위에서부터 차례대로 만들면 된다.
각 컴포넌트 폴더를 만들어주고 각 css파일까지 함께 넣어주면 깔끔하게 정리가능하다.
export default function 컴포넌트이름
으로 밖으로 빼주고
import{컴포넌트이름} from ./경로
로 불러와주면 된다.
그러면 각 컴포넌트 별로 살펴보자!
( 컴포넌트는 첫 글자를 대문자로 표현하고, 파일명도 대문자로 표현해준다.
그리고 .js와 .jsx는 기능상 차이는 없으나 컴포넌트가 들어간다면 .jsx로 표시해주자. )
가장 상위가 되는 컴포넌트로 사실상 body라고 보면된다! 실제로는 index.html에 body가 있고 컴포넌트들은 root에 표시되는 div이다 (js의 SPA 형태와 같아보인다)
먼저 App컴포넌트를 만들어준다
(아래사진 App.jsx 파일에서 오류코드가 있는데 글 마지막에 해결한 방법을 설명함)
useState를 사용해야하니 불러와주고 uuid 라이브러리는 유니크 아이디를 생성하기 위함이다.
uuid를 사용하려면 기본설치가 먼저 필요하니 옆의 링크를 먼저 확인하자 (react셋팅)
7번줄은 cosnt [state, setState] useState("");
를 사용한 것인데
state
의 객체를
setState
로 편집하고 그걸 사용할 수 있게
useState
를 입력.
("")
는 초기값이니 필요하다면 입력하면 된다. // (오류코드 마지막에 해결방법제시)
나는 todos
라는 객체에 들어갈 속성이 필요하니 state
지정하고 시작!
폴더 생성
폴더는 위와같이 만들면 깔끔하다. 이후 컴포넌트가 늘어날 때 마다 같은 방식으로 만들면 된다.
(이후 폴더 만드는 컷은 생략)
코드 확인
위와같이 import
를 설정하면 되고 하위 컴포넌트들이 생성되면 추가 입력해주면 된다.
지금은 App컴퍼넌트 하위로 새로운 컴퍼넌트를 만들었으니 App.jsx에 Header
를 import
해준다.
4번줄의 children
는 상위 컴포넌트(App)에서 props를 받아온 것(상위 컴포넌트의 데이터를 받아옴)인데 더이상 하위 컴퍼넌트가 없다면 {children}
으로 props 받아올 수 있다.(children도 사실 구조분해할당 같은 형식이지 않을까)
구조분해할당
구조분해할당은 props - props.이름의 구조를 분해해서 할당하는 것이다.
props를 끌어오는 곳(컴포넌트 매개변수('여기'))에{ .이름 에 해당하는 이름}
을 넣으면 된다. 분해해서 끌어온 뒤 원하는 곳에 이름만 써서 할당하는 것이다.
(진행하다보면 사용할 부분이 있다. 그 때 자세히 확인하길 바람)
Title(Header)부분을 다 만들었으니 Todo를 입력,등록할 수 있는 form부분을 만든다.
App.jsx 코드
컴포넌트 태그에 출력할 내용이 없다면 <AddForm setTodos={setTodos}/>
이런식으로 만들어도 된다. 그러면 AddForm 컴포넌트를 살펴보자
코드 확인
여기서 짚고 넘어가야할 점은 prevetDefault
는 단순히 새로고침을 막는 기능이 아니라, 기본적으로 가지고 있는 설정을 없애주는 역할을 하는 것이다!! 여기서는 onSubmit
은 실행되면 새로고침 되는 특성을 가지고 있기 때문에 prevetDefault
를 사용하면 새로고침을 막아 주는것!!
다시 말하지만 return
전에 js작업을 한다. 코드의 설명은 주석에 달아놨으니 확인하면 된다. 이해가 안가는 부분은 html부분을 함께 보면서 하니 이해가 잘 됐다. 그러니 바로 return
도 함께 확인해보자.
자, js작업한 부분은 주석을 잘 달아놨으니 어떤 기능인지는 안다.
그러니 어떻게 실행되는지 한번 알아보자.
그전에 form태그의 특징 먼저 살펴보면,
<form>
폼태그는, form 태그안에 button(컴퍼넌트화 되어 있을 수 있음)이 클릭이 되면 onSubmit(제출)이 실행 된다. 위 코드에서는 addTodo 이벤트가 실행된다.
그리고 추가로<label>
과<input>
은 1+1이라고 생각하면 편하다. 라벨안의 내용을 클릭했을때htmlFor
와 같은id
값을 가진<input>
이 선택이 된다.
먼저, form
태그에 Button
을 통해 무언가 onSubmit
(제출)되면 addTodo
이벤트가 실행되게 코딩을 했고,
그 제출할 입력값을 input
을 통해 넣는데 label
은 위에서 설명했듯 사용자 편의를 위해 input
과 1+1이라고 생각하면 된다.
input
의 type
을 text
타입으로 받으며 무언가 onChange
(변경)되면 handleChange
가 실행이 된다. autoFocus
는 새로고침 했을 때 input
이 선택되게 해준다.
value
는 최종적으로 addTodo
이벤트에 들어갈 todoValue
가 들어가게 되고 이 값이 setTodos
를 통해 todos
에 입력된다.
여기서 setTodos
를 보면 todos
에 무엇이 들어갈지 정해준다. 확인해보면,
setTodos((prev)=>[...prev,{todo: todo, isDone: false, id: uuid()}]);
// todo: todo와 같이 키 값과 value가 같으면 아래코드같이 todo 만 적어주고 넘어가도 된다.
// setTodos((prev)=>[...prev,{todo, isDone: false, id: uuid()}]);
// setTodos를 통해서 ...prev는 만든 todos를 다 불러오는 것이고 거기에
// {}내용을 추가하는 것이다. todos에 들어갈 요소를 보면,
// todo(내용), inDone(완료여부), id(유니크ID-각자의 주소 느낌) 이렇게 세가지 값을 필요로 한다.
그리고 마지막으로 setTodoValue("")
를 초기화(addTodo
이벤트 후 input
창을 비워주는 것) 하면서 마무리 해준다.
코드를 보면 버튼을 컴포넌트로 만들어 놨는데 다른 곳에도 사용할 예정이기 때문에 컴포넌트로 만들었으며, 그렇기 떄문에 AddForm.jsx에서 Button을 import
해서 사용해야 한다.
( <Button value="등록" />
의 value는 그냥 value라는 이름으로 Button 컴포넌트에서 props 해온 것)
코드 확인
Button은 여러 곳에서 사용하게 되니 컴포넌트로 만들어주고 각 버튼에서 사용할 기능을 구조분해할당을 통해 props 받아왔고,
Button을 사용할 컴퍼넌트에서 필요한 기능을 만들어 사용하게끔 했다.
여기서 한번 더 짚고 넘어가야 할 점은 AddFrom,jsx에서 form태그 안의 Button에는 onClick 이벤트를 넣지 않았음에도 onSubmit이 됐는데, 이건 외우고 넘어가자 !!
form
태그안에button
이 클릭되면onSubmit
이 실행된다.
버튼까지 만들었으니 이제 만들어진 todos를 출력해줄 곳을 만들면 된다.
만들어진 todos들이 나열되는데 와이어프레임에서 보듯, working(진행중)과 Done(완료)를 나눠야하기 때문에 두 곳에 만들어 준다.
App.jsx 코드
여기서 name
은 내가 넣어줄 todos
의 list 키값(?) 으로 보는게 맞는 것 같다.
그리고 제출된 todos
를 컨트롤 해야하니 setTodos
도 props 한다.
코드 확인
구조분해할당으로 name
,todos
,setTodos
를 가져온다.
리스트를 두 개로 나눠야 하니 삼항연산자를 사용해서isWorkingList
의 name
이 working
인 리스트는 true
, 그렇지 않은 친구들은 자동으로 false
처리하며 name
이 done
이다.
(세 가지 이상이면 if/if else/ else를 사용하면 괜찮을 것 같다.)
return
에서 isWorkingList
의 true
리스트는 Working!!
이라는 텍스트를 넣어주고
false
리스트는 Done :)
이라는 텍스트를 넣어준다.
그러면 각 리스트의 제목까지 달아줬으니 들어갈 리스트의 내용을 보자.
먼저 filter
를 통해 todos
의 요소중 isDone
이 false
인 항목이 isWorkingList
의 true
에 해당하는 working
에 들어가야하니 앞에 !
를 붙여준다.
filter
를 통해 isDone
을 확인해서 working
에 나타낼지 done
에 나타낼지 정했으니 filter
로 들어갈 todos
의 요소를 map
메서드를 사용해서 입력한다. 그래서 필요한 값들인 todo
(내용),isDone
(list정함),setTodos
(todos편집),key,id
(각 todos
창의 고유 키값)을 Todo 컴포넌트로 프롭스 해준다.
Todo 컴포넌트에서는 각 Todo의 id
값을 이용해서 isDone
을 true
와 false
를 토글해주거나 그 id
값을 통해 무엇을 출력해서 보낼지 정해주는 역할을 할 것이다.
코드 확인
주석처리가 되있어서 각 함수가 하는 역할은 확인하면 된다. 여기서 헷갈렷던 점은 t.id
와 id
가 있는데 t.id
는 todos에 들어있는 모든 객체의 id
를 말하고 비교대상 id
는 내가 선택한 객체의 id
이다.
그래서 삭제 함수를 말로 해석하자면 todos
가 가지고있는 객체 중, 내가 선택한 특정 id
와 같지않은 (한마디로 선택한 id
말고 나머지 전부라고 할 수 있음) 객체들을 setTodos
를 통해 todos
로 보내어 호출하면 마치 삭제된 것처럼 선택한 것 제외한 나머지만 호출된다.
이 이벤트를 Button의 onClick
을 통해서 실행하고 있다.
위와같이 코딩하게되면 오류가 나게된다. (이거 찾느라 2시간썼다....)
그 이유를 알기위해 아래사진을 먼저 확인하자
위 코드의 최종 App.jsx 모습
여기서 오류가 난 부분은 10번줄이다.
const [todos, setTodos] = useState("");
먼저 내가 저렇게 만든 이유는 초기값을 안주고 시작하려고 만든 코드이다.
다시말해 어떤todos
도 없는 상태에서 내가 입력한 todos
만 추가하게 하려고 한 것이다.
하지만 여기서 문제가 뭐냐면 todos
는 객체(배열)이다. 그런데 들어갈 useState("")
에 빈 text를 넣은 것이다.
useState("")
를 -> usdState([]);
로 만들어주는 방법이 있고,
같은 방법이지만 다른 형식으로 아래 코드와 같이 만들어 줄 수 있다.
빈 배열(A
)을 만들어 주고 그 값을 useState(A)
이렇게 넣어주면 해결된다.
여기서 A
배열안(27번줄)에 AddForm에서 선언한 todos
의 형식
{
todo: todo,
isDone: false,
id: uuid()
}
을 지켜서 몇 개 만들어주면 초기값으로 사용된다.
css를 사용안해서 보기는 안좋지만 기능은 완벽하게 구현했다.