본문 바로가기

[내일배움캠프]/TIL

22.12.06)React27일차

#오늘 배운 것

 

function Todo(props){
	return <div>{props.todo}</div>
}

자식 컴포넌트를 받을 때 props를 사용하는 모든 곳에서 props. 를 붙여줘야만 했다.

다만 js 구조 분해 할당을 이용하면 생략이 가능하다.

//중괄호{} 안에 객체 key name을 적으면 된다.
function Todo({ title }){
	return <div>{title}</div>
}

 

defaultProps,

부모 컴포넌트에서 props를 보내주지 않아도 설정될 초기 값. 

prop중에서는 자주 받거나 무조건 받아야하는 props가 있는데 부모 컴포넌트가 props를 주기 전에 defaultProps를 이용하고 부모 컴포넌트에서 props가 내려오면 내려받을 props로 바뀌게 된다.

// 이렇게 설정합니다.
Child.defaultProps={
	name: '기본 이름'
}
// 구조 분해 할당 문법을 사용하면 이렇게도 할 수 있어요.
function Child({ name = '기본이름' }){
	return <div>내 이름은 {name} 입니다. </div>
}

 

State

컴포넌트 내부에서 바뀔 수 있는 값

if const에 있는 value값을 고정이 아닌 계속 변경해줘야 한다면 그 때 사용하는것이 state다

State를 만들 때는 useState()를 사용

 

Q.let이라는 변수로 선언해줘도 되는데 왜 state로 선언하는가?

A. 컴포넌트의 lifecycle이라는 화면 값이 변했을 때 이 값을 다시 렌더링해줘야 하는 조건이 있다.

그 조건 중 하나가 state가 변경됐을 때다.그러므로 화면에 바로 반영하려면 state를 써야함.

//usestae를 통해 state를 선언한다.
const [ value, setValue ] = useState( 초기값 )
//setValue는 state를 변경할 때 사용하는 함수. 이 함수를 사용해야만 state변경이 가능하다.
//새로고침하면 다시 초기값으로 변경된다.

먼저 const 로 선언을 하고 [ ] 빈 배열 을 생성하고, 배열의 첫번째 자리에는 이 state의 이름, 그리고 두번째 자리에는 set 을 붙이고 state의 이름을 붙입니다. 그리고 useState( ) 의 인자에는 이 state의 원하는 처음값 을 넣어줍니다.

state를 변경할때는 setValue(바꾸고 싶은 값) 를 사용한다.

Hook

기존에 사용하던 Class를 이용한 코드를 작성할 필요 없이,
state와, 여러 React 기능을 사용할 수 있도록 만든 라이브러리

 

 

useState + onClick Event

 

state 구현하고 이벤트 핸들러와 연결

 

이벤트핸들러로 컴포넌트와 함수를 연결한다

state를 하나 만든다(버튼을 클릭을 했을 때 state 값을 바꾼다) -> 이벤트 핸들러를 만들어주고 그 안에 setName 을 넣어준다. ->이제 버튼을 누르면 setName()안에 있는 값 "길동이"에서 “누렁이”로 바뀐다.

function App() {
  const [name, setName] = useState("길동이");
  function onClickHandler() {
    setName("누렁이");
  }
  return (
    <div>
      {name}
      <button onClick={onClickHandler}>버튼</button>
    </div>
  );
}

Input과 state 구현하기

input에서는 보통 사용자가 입력한 값을 state로 관리하는 패턴을 많이 사용합니다.

input에 onChange라는 이벤트를 불러내고 -> 생성한 이벤트 핸들러 함수를 넣습니다.

여기서 사용한 event객체는 dom html객체이다.

const App = () => {
  const [value, setValue] = useState("");

  const onChangeHandler = (event) => {
  	//사용자가 입력한 input값을 추출하기 위해event.target.value에 접근.
    const inputValue = event.target.value;
    //이후 추출한 값을 setValue의 state에 연결하면 input과 연결된다.
    setValue(inputValue);
  };

  return (
    <div>
    //사용자가 input의 값을 입력하면 그 때마다(onchange) value인 setValue를 해서 값을 넣는다.
      <input type="text" onChange={onChangeHandler} value={value} />
    </div>
  );
};

 

불변성이란?

메모리에 있는 값을 변경할 수 없는 것. 변수가 메모리에 저장되는 방식과 연관성이 있다.ex) 자바스크립트의 데이터 형태중에 원시 데이터는 불변성이 있고, 원시 데이터가 아닌 객체, 배열, 함수는 불변성 없습니다.

원시 데이터는 값을 수정 했을 때 기존 메모리가 아닌 새로운 메모리를 교체하는 것이고, 원시 데이터가 아닌 것은 기존 메모리에서 값을 수정한 것.

 

React의 경우 화면을 리렌더링 할지 말지 정할 때 state 변화를 확인한다.

state가 변했는지 변하지 않았는지 확인하는 방법이 state의 변화 전, 후의 메모리 주소를 비교한다.

만약 리액트에서 원시데이터가 아닌 데이터를 수정할 때 불변성을 지켜주지 않고, 직접 수정을 가하면 값은 바뀌지만 메모리주소는 변함이 없게 되는것

//배열에 setState를 할 때 전개연산자...를 통해 기존값을 복사하고 추가하거나 수정한다.
function App() {
  const [dogs, setDogs] = useState(["말티즈"]);

  function onClickHandler() {
		// spread operator(전개 연산자)를 이용해서 dogs를 복사합니다. 
	  // 그리고 나서 항목을 추가합니다.
    setDogs([...dogs, "시고르자브르종"]);
  }

 

컴포넌트

리액트의 핵심 빌딩 블록

 

DOM (명령형 프로그래밍)

명령형으로 작성된 코드의 경우 Hello, World!를 출력하기 위해 컴퓨터가 수행하는 절차를 일일히 코드로 작성해주어야한다.

리액트 (선언형 프로그래밍)

화면에 어떻게 그려야할지는 React 내부에 잘 숨겨져 추상화되어 있다.

// React 코드 (선언적인)
const header = <h1>Hello World</h1>; // jsx
ReactDOM.render(header, document.getElementById('root'));

 

렌더링

컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업

 

쉽게 이해하기 위해 예시를 들어본다.

  • UI - 음식
  • 컴포넌트 - 음식을 만드는 주방장
  • 리액트 - 주문받고 서빙하는 웨이터
    1. 렌더링 일으키는 것은 (triggering) - 손님이 UI를 주문하고 주방으로 전달하는 것
    2. 렌더링한다는 것은 (rendering)- 주방에서 컴포넌트가 UI를 만들고 준비하는 것
    3. 렌더링 결과는 실제 DOM에 커밋한다는 것은 (commit) - 리액트가 준비된 UI를 손님 테이블에 올려놓는 것

리액트 앱이 실행되고 첫 렌더링이 일어나면 리액트는 컴포넌트의 루트에서 시작하여 아래쪽으로 쭉 훑으며 컴포넌트가 반환하는 JSX 결과물을 DOM 요서에 반영한다.(자동)
첫 렌더링을 끝난 이후에 추가로 렌더링을 트리거 하려면 상태를 변경해주면된다.

컴포넌트 상태에 변화가 생기면 리렌더링이 발생합니다. 이때 여러 상태가 변경됐다면 리액트는 이를 큐(queue)자료구조에 넣어 순서를 관리합니다.

  1. 주방 예시를 다시 들어보면 리렌더링은 음식점 손님이 첫 주문 이후에 갈증이 생겨 추가로 음료를 주문하거나 처음 받은 음식이 마음에 들지 않아 새로운 메뉴를 주문하는 것과 같습니다. == 새로운 주문(ui)
  2. 새로운 UI주문(리렌더링)이 일어나면 리액트가 변경된 내용을 주방에 있는 요리사인 컴포넌트에 전달하고 컴포넌트는 새로운 변경된 주문을 토대로 새로운 요리(UI)를 만듭니다.
  3. 새롭게 만들어진 요리(렌러딩 결과)는 리액트에 의해 다시 손님 테이블에 올려집니다(DOM에 반영 - commit phase).

브라우저 렌더링

렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 그립니다. 이 프로세스를 "브라우저 렌더링"이라고 하지만 혼동을 피하기 위해 "페인팅"이라고도 합니다

 

count 만들기

import React, { useState } from "react";

function App() {
  const [number, setNumber] = useState(0); //초기값 0
  return (
    <div>
      {number}
      <buttononClick={() => {setNumber(number + 1);}}>+</button>
      <buttononClick={() => {setNumber(number - 1);}}>-</button>
    </div>
  );
} //익명함수를 사용한 방법

export default App;

 

CSS 분리없이 한번에 처리하기

const App = () => {
  const style = {
    padding: "100px",
    display: "flex",
    gap: "12px",
  }; //바탕
  const squaryStyle = {
    width: "100px",
    height: "100px",
    border: "1px solid green",
    borderRadius: "10px",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
  }; //네모
  return (
    <div style={style}>
      <div style={squaryStyle}>감자</div>
      <div style={squaryStyle}>고구마</div>
      <div style={squaryStyle}>오이</div>
    </div>
  );
};

컴포넌트 파일에서 CSS 코드 분리하기 -> app.js파일을 css와 jsx파일로 나눈다.(js와 jsx파일 둘다 사용가능)

import "./App.css"; //css파일 연결하고,
const App = () => { //CSS class를 className에 붙여넣기
  return (
    <div className="app-style">
      <div className="squaryStyle">감자</div>
      <div className="squaryStyle">고구마</div>
      <div className="squaryStyle">오이</div>
    </div>
  );
};
// -> 이 코드의 문제점 : 같은 코드가 중복되고,개발자가 직접 타이핑해야한다.
//그리고 추가시 수기로 작성해서 다시 배포해야한다.
//이 때 사용하는 것이 map()이다.

map()

JSX에서 map() 은 배열의 모든 요소를 순회함.

const App = () => {
  const vet = ["감자","고구마","오이"]
  return (
    <div className="app-style">
      {vet.map((vegetableName) =>{ 
        return(
          <div className="square-style" key={vegetableName}>
            {vegetableName}
          </div>
        )
      })}
    </div>
  );
};
//위의 코드를 배열로수정한 코드
import React, { useState } from "react";
import "./App.css";

//1. 버튼 컴포넌트 생성
function CustomButton(props) {
  //props로 onclick을 받았고,children은 버튼사이의 값을 children으로받는다.
  const { color, onClick, children } = props;
  if (color) {
    //무조건 color가 있는 코드를 만든다면 if문은 딱히 안써도 됨.
    return (
      <button style={{ background: color, color: "white" }} onClick={onClick}>
        {children}
      </button>
    );
  }
  return <button onClick={onClick}>{children}</button>;
}

function User(props) {
  return (
    <div className="squaryStyle">
      {props.user.age}살 - {props.user.name}
      <CustomButton
        color="red"
        onClick={() => props.handleDelete(props.user.id)}
      >
        삭제하기
      </CustomButton>
    </div>
  );
}
const App = () => {
  const [users, setUsers] = useState([
    { id: 1, age: 30, name: "송중기" },
    { id: 2, age: 24, name: "송강" },
    { id: 3, age: 21, name: "김유정" },
    { id: 4, age: 29, name: "구교환" },
  ]);
  const [name, setName] = useState(""); // <-- 유저의 입력값을 담을 상태
  const [age, setAge] = useState("");

  const addUserHandler = () => {
    const newUser = {
      id: users.length + 1, //유저의 길이를 추가한다. 지금 유저가 4이니 새유저가되면 5가되도록하는 것.
      age: age,
      name: name,
    };
    setUsers([...users, newUser]); //배열을 만들었고,기존 user배열을 스프레드문법으로 풀었고,기존 user배열에 추가했다.
  };
  const deleteUserHandler = (id) => {
    //삭제할 id를 받는다.
    const newUserList = users.filter((user) => user.id !== id);
    //filter를 이용해서 각각의 유저에서 내가 설정한 아이디와 같지 않으면 새로운 유저리스트배열을 추가해라.
    setUsers(newUserList);
  };

  return (
    // 중괄호->user의 map을 돌림 -> 각 유저에 화살표 함수로 유저 컴포넌트를 리턴해라
    // -> 그리고 각각의 유저의 props를 넘긴다.->그리고 key라는 것을 보낸다.
    // 나이와 이름을 렌더링하니 user가 props로 배열안에 긴 객체 값을 받아야한다.*
    <div>
      <div className="app-style">
        {users.map((user) => {
          return (
            <User handleDelete={deleteUserHandler} user={user} key={user.id} />
          );
        })}
      </div>
      <input
        value={name}
        placeholder="이름을 입력해주세요"
        // 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
        onChange={(e) => setName(e.target.value)}
      />
      <input
        value={age}
        placeholder="나이를 입력해주세요"
        // 인풋 이벤트로 들어온 입력 값을 age의 값으로 업데이트
        onChange={(e) => setAge(e.target.value)}
      />
      <CustomButton color="green" onClick={addUserHandler}>
        추가하기
      </CustomButton>
    </div>
    //app-style컴포넌트 안에 user가 map으로 돌면서 user안에있는 정보를 props로 넘긴다
    //그럼 user에 props를 위의 squaryStyle에 받아 이름과 나이를 출력
  );
};

export default App;

 

 

key : 

리액트에서 map을 사용하여 컴포넌트를 반복 렌더링 할 때는 반드시 컴포넌트에 key를 넣어줘야 합니다

key가 필요한 이유는 React에서 컴포넌트 배열을 렌더링했을 때 각각의 원소에서 변동이 있는지 알아내려고 사용하기 때문입니다. 만약 key가 없다면 React는 가상돔을 비교하는 과정에서 배열을 순차적으로 비교하면서 변화를 감지하려 합니다. 하지만 key가 있으면 이 값을 이용해서 어떤 변화가 일어났는지 더 빠르게 알아낼 수 있게 됩니다. 즉, key값을 넣어줘야 React의 성능이 더 최적화 된다. 라는 의미입니다.

key는 props처럼 생겼지만 props로 설정하지 않아도 key는 넣을 수 있다.
예시에서 age가 25이상인 user는 제외하고 렌더링

map() 안에서도 조건식을 사용 할 수 있습니다.

age가 25 미만인 user는 User 컴포넌트를 이용해 화면에 렌더링 해주고, 그것이 아닐 때는 화면에 아무것도 렌더링 하지 않기 위해 null 을 return 합니다. null 이란, 값이 “없음”을 개발자가 의도적으로 표현하는 데이터 형태이자 값 입니다.

return (
    <div style={style}>
      {users.map((user) => {
        if (user.age < 25) {
          return <Square user={user} key={user.id} />;
        } else {
          return null;
        }
      })}
    </div>

Compiled with problems:  Module not found: Error: Can't resolve

 

★오류 발생 : 컴포넌트 파일을 분리 후 갑자기 오류가 발생했다..export랑 import도 제대로 했는데 왜지...

구글링으로는 js 파일 가져올 때 파일의 경로가 잘못되었다는 뜻입니다 라는데...왜..

-> 알고보니 src 주소를 추가해서 그런것이였다. 초반에는 src빼도 안됐었는데 왜 이랬지...

 

 

Vercel

Vercel은 AWS 와 같은 클라우트 컴퓨팅 서비스를 제공하는 서비스

단지 깃헙과 연동하고, 소스를 push 를 할때 마다 새로운 소스코드가 자동으로 배포도 됩니다. 또한 SSL 설정도 자동으로 적용이 되며, custom domain도 아주 쉽게 연결할 수 있습니다.

 

깃허브 레포지토리 생성 및 remote 등록

Vercel로 들어갑니다. 그리고 깃허브 아이디를 이용해서 로그인 ->

Overview 화면으로 이동됩니다. 우측 상단에 New Project 버튼을 클릭

-> Import Git Repository 라는 메뉴에 우리가 아까 생성한 레포지토리가 보입니다. 그것을 import 

->그리고 Deploy 버튼을 클릭 이후 STATUS가 ready 라면 배포가 완료된 것이다.