본문 바로가기

Clipper 스터디

Inflearn 2주차

간단한 To-Do 앱 만들며 리액트 익히기

1) Create React App으로 실행된 리액트의 기본 구조

https://create-react-app.dev/docs/folder-structure/

 

 

Folder Structure | Create React App

After creation, your project should look like this:

create-react-app.dev

npx create-react-app 설치 시 폴더 및 파일 생성 모습

-public/index.html(페이지 템플릿) & src/index.js(JS 시작점)는 이름이 수정되면 안되는 파일들

-src폴더 잍에 JS 파일과 CSS 파일들을 넣으면 됨. 이 폴더 이외에 넣는 것들은 webpack에 의해서 처리되지 않음.

따라서 대부분의 리액트 소스코드들은 이곳에 작성하면 된다.

package.json 파일

package.json 파일 내에는 해당 프로젝트에 대한 정보들이 들어있음.

프로젝트 이름, 버전, 필요한 라이브러리 등이 명시되어있음 + 앱을 시작, 빌드, 테스트할 때 사용할 스크립트등이 명시되어있음.

 

 

2) SPA란?

SPA = Single Page Application

기존 웹페이지 동작 방식은 페이지 전환할 때, a.html을 보여주다가 b.html을 보여주는 식으로 동작했지만

index.html 밖에 없는 SPA에서는 페이지 전환(브라우징)을 하는 방식이 조금 다르다.

 

바로 HTML%의 History API를 사용해서 가능하게 만든다.

JS 영역에서 React-Router-Dom 라이브러리에 있는 History API를 사용해 현재 페이지 내에서 화면 이동이 일어난 것처럼 작동하게 해줌.

*History.back(): 세션 바로 뒷페이지로 이동(브라우저 뒤로가기)

, History.forward(): 세션 바로 앞페이지로 이동(브라우저 앞으로 가기),

History.go(): 몇페이지 앞뒤로 갈지 1/-1같이 숫자를 넣어 호출

, History.pushState(): 주어진 데이터를 세션 기록 스택에 넣음.

, History.replaceState(): 최근 세션 기록 스태긩 내용을 주어진 데이터로 교체

[강의 자료]참고!

->개발자도구 console창에서 직접 실행해볼 수 있다.

 

3) To-Do 앱 소개 및 JSX 알아보기

JSX는 JS의 확장 문법. 리액트에서는 이 JSX를 이용해 화면에서 UI가 보이는 모습을 나타내줌.

->자바스크립트 로직과 HTML 구조(markup)을 같이 사용할 수 있기 때문에 기본 UI에 데이터가 변하는 것들이나 이벤트들이 처리되는 부분을 더욱 쉽게 구현할 수 있음.(비교 강의자료)

*필수는 아니지만 구현에 있어 훨씬 편리하기 때문에 대부분의 개발자들이 JSX를 사용함.

리액트 안에 Babel이라는 컴파일러가 있어서 JSX문법을 JS+HTML로 바꿔주는 작업을 함.

 

JSX를 사용하면서 주의해야 할 문법들

컴포넌트에 여러 element들이 있다면, element들을 하나로 감싸주는 부모요소가 존재해야 함.

주의해야 할 JSX 문법

이외에도 여러가지가 있지만, 하면서 배우자!

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

4) To-Do 앱 만들기 시작

 

Step1) 할 일 목록 앱 만들기 시작(Container 구성)

*html에서는 class=""이지만 JSX에서는 className=""이다.

<App.js>

!css 파일 import해줘야 스타일 적용되는 것 주의!

import React, {Component} from "react";//리액트 라이브버리에서 React와 Component를 가져옴
import "./App.css";
export default class App extends Component{
  render(){
    return (
      <div className="container">
        <div className="todoBlock">
          <div className="title">
            <h1>할 일 목록</h1>
          </div>
        </div>
      </div>
    )
  }
}

<App.css> 파일

body{
  background-color: aliceblue;
}

.container{
  margin: auto;
  max-width: 600px;
}

.todoBlock{
  padding: 30px;
  margin-top: 50px;
  background: #fff;
  border-radius: 10px;
  box-shadow: 10px -10px 7px rgb(0 0 0/20%);
}

npm start해서 실행한 모습

 

Step2) 할 일 목록 UI 만들기

->todoBlock div 안에 (체크박스 - 할 일 목록 - button)으로 이루어져있는 div 한 줄 생성 + render함수 위쪽에 스타일 코드 작성

render(){
    return (
      <div className="container">
        <div className="todoBlock">
          <div className="title">
            <h1>할 일 목록</h1>
          </div>
          <div style={this.getStyle()}>
            <input type="checkbox" defaultCheck={false} />공부하기
            <button style={this.btnStyle}>X</button>
          </div>
        </div>
      </div>
    )
  }

->CSS 스타일

btnStyle={
  color: "#fff",
  border: "none",
  padding: "5px 9px",
  borderRadius: "50%",
  cursor:"pointer",
  float:"right"//오른쪽 정렬
}

getStyle = () =>{
  return {
    padding: "10px",
    borderBottom:"1px #ccc dotted",//밑줄 생성
    textDecoration: "none",

  }

Step 3) Map Method를 이용한 할 일 목록 나열

하드코딩을 하지 않고, Map 메소드를 이용해 할 일 목록을 나열한다.

->todoData 객체를 나열한 배열을 하나 생성.

todoData = [
  {
    id: "1",
    title: "공부하기",
    completed: false
  },
  {
    id: "2",
    title: "청소하기",
    completed: false
  },
]
 

->map method 사용하여 데이터 나열하기

{this.todoData.map(data=>(
            <div style={this.getStyle()} key={data.id}>
            <input type="checkbox" defaultCheck={data.completed} />{data.title}
            <button style={this.btnStyle}>X</button>
          </div>
          ))}

todoData.map으로 todoData 안의 객체 하나씩 가져와서 아까 작성했던 할 일 목록 div를 map으로 하나씩 매칭시켜 줌.

data를 map의 매개변수로 받아 객체의 각 원소들을 {data.completed}, {data.title}, {data.id} 형식으로 가져옴.

 

*Array.prototype.map()의 쓰임에 대해 MDN 문서 참고

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map

map()메서드: 배열 내의 모든 요소 각각에 대해 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환함.

 

//list 안의 child는 unique "key" prop을 가져야 함.

+) [JSX key 속성] 이해하기

리액트에서 요소의 리스트를 나열할 때는 key를 넣어줘야 함. 키는 React가 변경, 추가 또는 제거된 항목을 식별하는 데 도움이 됨. 요소에 안정적인 id를 부여하려면 배열 내부 요소에 key를 제공해야 함.(index 형식은 비추천. 각자 순서가 바뀌어도 변하지 않는 고유한 값을 갖도록! 그래서 위의 할일 목록처럼 id를 key값으로 많이 활용함.)

리액트는 가상돔을 이용해 바뀐 부분만 실제 돔에 적용 -> 리스트의 변경사항을 key값을 확인해서 알아차림

 

리액트는 가상 돔을 이용한다고 했었다.

리액트에서는 리스트를 나열할 때 바뀐 부분만 찾을 때 어떤식으로 할까?

바로 key를 이용해서 어떤 부분이 바뀌었는지 인식할 수 있다.

(문제가 생기는 경우 vs 문제 없는 경우 예시 강의자료)

!key는 반드시 unuque한 값을 넣어줘야 함.(index 사용은 비추천. 요소의 순서가 바뀌면 index값도 바뀌기 때문.)

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Step 4) Filter 메서드 사용해 할 일 목록 지우기

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Array.prototype.filter();

filter()메서드는 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환함.

//클릭 이벤트 발생 시 함수를 호출해야 함.

<button style={this.btnStyle} onClick={()=>this.handleClick(data.id)}>X</button>

->button에 clickEvent 발생 시 handleClick함수를 실행, 이때 매개변수로 data.id를 전달

handleClick=(id)=>{
    let newTodoData=this.todoData.filter((data=>data.id!==id));
    console.log('newTodoData', newTodoData);
  }

->"x" 버튼을 누른 id를 매개변수로 받아와서  해당 id에 해당하는 title만 filter로 거름. -> newTodoData에 "x" 버튼 

누른 title이 제외되고 재생성됨.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Step 5) React State란?

리액트에서 데이터가 변할 때 화면을 다시 렌더링 해주기 위해서는 React State를 사용해야 함.

State가 변경되면 컴포넌트는 리렌더링됨. State는 컴포넌트 안에서 관리됨.

state 객체 안에 todoData를 통째로 갖다넣고 setState함수로 state 변경사항을 조정할 수 있음.

this.todoData -> this.state.todoData로 바꾸기

 

->todoData를 state 객체로 감싸주기

state={
  todoData: [
    {
      id: "1",
      title: "공부하기",
      completed: false
    },
    {
      id: "2",
      title: "청소하기",
      completed: false
    },
  ]
}

->handleClick함수 실행 시 setState함수를 사용해 todoData를 newTodoData로 변경

  handleClick=(id)=>{
    let newTodoData=this.state.todoData.filter((data=>data.id!==id));
    //todoData를 newTodoData로 변경
    this.setState({todoData:newTodoData});
  }

->앞으로는 this.todoData가 아닌 this.state.todoData로 불러오기

this.state.todoData.map(data=>

todoData를 state로 관리해준 모습

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Step 6) 할 일 목록 추가하기

-> 할 일 입력칸과 button UI를 form 태그 안에서 구성

<form style={{display:'flex'}}>
            {/* flex의 값은 단위 없이 사용, 콘텐츠의 최소 너비 미만으로 줄어들지 않음.  */}
            <input type="text" name="value" style={{flex: '10',padding: "5px"}}
            placeholder="해야할 일을 입력하세요."
            value={this.state.value}
            />
            <input type="submit"
            value="입력"
            className="btn"
            style={{flex:"1"}}
            />
        </form>

-> input 글 입력시 state 변경

+) state에 value: "" 추가(value가 변경되면 컴포넌트를 리렌더링 하기 위해서)

*handleChange 함수에서 event를 받아서 value값을 event.target.value값으로 리렌더링.

  handleChange=(e)=>{
    console.log(e.target.value);
    this.setState({value: e.target.value});
  }

*input에 onChange 이벤트 발생 시 handleChange 함수 실행

onChange={this.handleChange}

 

->버튼 눌러서 form의 submit 이벤트 실행 시 UI에 할 일 목록 추가

 <form style={{display:'flex'}} onSubmit={this.handleSubmit}>
handleSubmit=(e)=>{
    e.preventDefault();//from안에서 input을 전송할 때 페이지 reload를 막아줌.
    let newTodo={
      id: Date.now(),
      title: this.state.value,//handleChange의 setState덕분에 title에 실시간으로 업로드 됨.
      completed: false,
    }
    //... : concat연산자, 기존에 있던 배열에 추가
    this.setState({todoData: [...this.state.todoData, newTodo]});
    this.state.value=""; //입력 마치고 input란을 비워줌.
  }

*이벤트를 매개변수로 받아와 e.preventDefault();는 form 안에서 input을 전송할 때 페이지 reload를 막아줌.

newTodo 객체를 새롭게 생성 후 submit이 실행된 후 this.setState함수를 사용해 기존 todoData에 newTodo객체를 추가함.

*이 때, value는 onChange함수에 의해 setState함수로 실시간 사용자 입력값을 받아오므로 title:this.state.value를 사용

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

//전개 연산자(Spread Operator) - 강의 자료 참조

특정 객체 또는 배열의 값을 다른 객체, 배열로 복제하거나 옮길 때 사용함.

연산자의 모양: ...

 

원래는 concat 메서드를 썼음. const arr=arr1.concat(arr2, arr3);

const arr = [...arr1, ...arr2, ...arr3];

 

객체 조합도 가능. 원래는 객체 안에 객체 자체가 들어갔다면, 전개연산자를 사용하면, 각 요소들이 들어가 하나의 객체로 만들어짐.

obj1, obj2가 새로운 객체 안에 통째로 들어가는 게 아니라 각 객체의 요소들이 하나의 객체 원소로 들어옴.

*아래와 같이 기존 배열을 보존하는 효과도 있음.

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Step7) 마무리 된 일 표시하기(조건부 삼항 연산자)

마무리 된 일 목록에는 선 긋기 효과를 넣어준다.

->data.completed를 매개변수로 전달해 completed가 true인 경우 "line-through" 스타일 을 적용하도록 함수를 만든다.

listStyle=(completed)=>{
  return {
    padding: "10px",
    borderBottom:"1px #ccc dotted",
    textDecoration: completed? "line-through":"none",//line-through 추가(삼항연산자)

  }
}
 

->이처럼 div style에 listStyle함수를 불러오면서, 함수의 매개변수로 data.completed를 전달한다.

<div style={this.listStyle(data.completed)} key={data.id}>
            <p>
              <input type="checkbox" defaultCheck={false} onChange={()=>this.handleCompleChange(data.id)}/>{data.title}
              <button style={this.btnStyle} onClick={()=>this.handleClick(data.id)}>X</button>
            </p>
          </div>

 

 

*이처럼 completed 값 여부에 따른 스타일 적용에 대한 부분을 끝냈으면, 이번에는 체크박스 클릭에 따라 todoData의 completed 변수를 state에 업데이트 시켜주는 작업을 수행해야 한다.

 

->체크박스가 클릭되었을 때, completed 변수를 반대로 바꿔주는 함수다.

클릭한 체크박스의 id를 인식해 해당 id에 해당하는 data의 completed값을 반전시켜주고, setState함수로 state값을 업데이트해준다.

handleCompleChange=(id)=>{
    let newTodoData=this.state.todoData.map((data)=>{
      if(data.id===id){
        data.completed=!data.completed;
      }
      return data;
    })
    this.setState({todoData:newTodoData});
  }

->input type="checkbox"태그에 onChange 이벤트가 발생했을 때, id를 매개변수로 전달해주면서 handleCompleChange 함수를 실행시킨다.

 <input type="checkbox" defaultCheck={falseonChange={()=>this.handleCompleChange(data.id)}/>

todoList 완성!

'Clipper 스터디' 카테고리의 다른 글

Inflearn 4주차  (0) 2023.11.07
Inflearn 3주차  (0) 2023.11.05
백준 2주차  (0) 2023.10.05
백준_1주차  (0) 2023.09.28
인프런_1주차  (0) 2023.09.28