항해99

리액트 입문주차 2

Hyeon_E 2023. 6. 23. 16:58

[ React Component ]

▶ React Component란 무엇일까요?

컴포넌트 개념 이해하기

컴포넌트를 통해 UI를 재사용이 가능한 개별적인 여러 조각으로 나누고, 각 조각을 개별적으로 살펴볼 수 있음

개념적으로 컴포넌트는 JavaScript 함수와 유사

“props”라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환

 

리액트 컴포넌트를 표현하는 두 가지 방법

1. 함수형 컴포넌트

// props라는 입력을 받음
// 화면에 어떻게 표현되는지를 기술하는 React 엘리먼츠를 반환(return)

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 훨씬 쉬운 표현
function App () {
	return <div>hello</div>
}

 

2. 클래스형 컴포넌트

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

 

두 가지 모두 기능상으로는 동일하지만 공식 홈페이지에서는 함수형 컴포넌트를 사용하기를 권장하고 있음

 

결론적으로 리액트 세계에서 말하는 컴포넌트(블럭)는 즉 함수임

누군가 와서 “컴포넌트를 만들어 보세요" 라고 한다면 우리는 html을 return 하는 함수를 만들면 됨

 

▶ 우리가 만든 CRA 프로젝트 살펴보기

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

 

Component 보는 방법

출저: 스파르타 리액트 입문

컴포넌트(함수) 코드를 볼 때는 영역을 나누어서 보면 조금 더 편함

컴포넌트 밖에서는 내가 필요한 파일을 import 하거나 또는 export default 라는 기능을 통해 내가 만든 컴포넌트를 밖으로 내보내는 코드가 있음

컴포넌트 안에서는 자바스크립트를 쓸 수 있는 부분이 있음. 컴포넌트 안에서 어떤 자바스크립트 코드를 작성하고 싶다면 여기에다가 작성하면 됨

return 을 기준으로 아랫부분에서는 HTML(정확히 말하면 JSX)을 작성. 여기에 작성한 html 코드와 값들이 화면에 보여짐

 

주의

  • 컴포넌트를 만들 때 반드시 가장 첫 글자는 대문자로 만들어야 함
  • 폴더소문자로 시작하는 카멜케이스로 작성하고 컴포넌트를 만드는 파일대문자로 시작하는 카멜케이스로 이름을 지음

 

▶ 부모 - 자식 컴포넌트

컴포넌트 안에 컴포넌트 넣기

컴포넌트는 다른 컴포넌트를 품을 수 있음. 이때 다른 컴포넌트를 품는 컴포넌트부모 컴포넌트라고 부르고 다른 컴포넌트 안에서 품어지는 컴포넌트자식 컴포넌트라고 부름

 

// src/App.js
import React from "react";
function Child() {
  return <div>나는 자식입니다.</div>;
}
function App() {
  return <Child />;
}
export default App;

 

App.js 파일 안에서 Child라는 새로운 컴포넌트를 만들었음. Child 컴포넌트를 App 컴포넌트에서 마치 HTML 태그를 쓰듯이 넣었음. 이렇게 한 컴포넌트 안에 다른 컴포넌트를 넣을 수 있음이렇게 코드를 작성하면 화면에는 “나는 자식입니다” 라는 문장이 보여지게 될 것 임

왜냐하면 이 파일에서 내보내진 (export default) 컴포넌트는 App 컴포넌트 이기때문에 App 컴포넌트가 화면에 보여짐

하지만 App 컴포넌트는 Child 컴포넌트를 자식으로 삼고 있기 때문에 결국 자식 컴포넌트에 있는 “나는 자식입니다" 라는 문장이 보여지게 되는 것

.

이렇게 만들어진 컴포넌트는 마치 HTML 태그를 쓰듯이 사용하여 화면에 보여지게 할 수 있음(Rendering)

그리고 이렇게 함수로 만들어진 컴포넌트를 html 태그 사용하듯이 코드를 작성하는 이 방식을 JSX라고 부름

 

[ JSX(JavaScript + XML) ]

▶ JSX란 무엇일까?

const element = <h1>Hello, world!</h1>;

 

JS를 확장한 문법. JS의 모든 기능이 포함되어 있으며 React Element를 생성하기 위한 문법

여기서 엘리먼트는 단순히 화면에 그려지는 HTML적 요소라고 생각하면 됨

 

// import [패키지명] from [경로] 이 형식으로 불러옴
import React from 'react'; 
// js 파일 뿐 아니라 이미지도 가능
import logo from './logo.svg';
// css? 가능!
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

 

리액트에서는 딱 하나의 html 파일만 존재함(public 폴더 아래에 있는 index.html) → CSR 관점

리액트에서 를 App.js 파일에서 보이듯 JSX 문법을 사용해서 React 요소를 만들고 DOM에 렌더링 시켜서 그림

 

HTML을 품은 JS ⇒ JSX

<div>
	<h1>안녕하세요!</h1> 
	<p>시작이 반이다!</p>
</div>

HTML 태그는 .js 파일 안에서 쓸 수 없음

그래서 나온것이 JSX. JS 안에서 html 태그같은 마크업을 넣어 뷰(UI) 작업을 편하게 할 수 있음

 

const start_half = <div>
    <h1>안녕하세요!</h1>
    <p>시작이 반이다!</p>
  </div>;

 

그럼 JSX에서 쓰는 <div>~</div>는 DOM 요소인가?

정확히는 React 요소. 지금은 리액트 돔을 구성하는 건 리액트 요소이고 돔을 구성하는 건 돔 요소라고 알아놓으면 됨

 

더보기

- 코드스니펫을 사용하면 이미 만들어진 명령어를 사용하여 편하게 코드를 짜는데 용이

확장에서 react snippet을 검색하여 설치하면 됨

rfc: export default가 들어간 function 키워드

rfce: function키워드

 

▶ 실습해보기

1. 태그는 꼭 닫아주어야 함

function App() {
  return (
    <div className="App">
      <input type='text'>	//오류
      <input type='text' />	//정상적인 작동
    </div>
  );
}

 

2. 무조건 1개의 엘리먼트를 반환하기

//function App() {
//	return (
//    <p>안녕하세요! 리액트 반입니다 :)</p>
//    <div className="App">
//      <input type='text'/>
//    </div>
//  );
//} 오류

return (
    <div className="App">
      <p>안녕하세요! 리액트 반입니다 :)</p>
      <input type='text'/>
    </div>
  );	//정상작동

컴포넌트에서 반환할 수 있는 엘리먼트는 1개임

 

3. JSX에서 JS값을 쓸때는 중괄호 사용

function App() {
	const cat_name = 'perl';
  return (
    <div>
      hello {cat_name}!
    </div>
  );
}

값을 가져올 때 뿐만 아니라, map, 삼항연산자 등 자바스크립트 문법을 JSX 안에 쓸 때도 {}를 이용할 수 있음

 

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  const number = 1;

  return (
    <div className="App">
      <p>안녕하세요! 리액트 반입니다 :)</p>
      {/* JSX 내에서 코드 주석은 이렇게 씁니다 :) */}
      {/* 삼항 연산자를 사용했어요 */}
      <p>{number > 10 ? number+'은 10보다 크다': number+'은 10보다 작다'}</p>
    </div>
  );
}

export default App;

 

4. class 대신 className

<div className="App">

 

JSX로 작성하는 태그 내에서 클래스 명을 정해줄 땐 속성 값을 className으로 사용. id는 그냥 id

 

5. 인라인으로 style 주기

css 문법 대신 json 형식으로 넣어주면 됨

//HTML
<p style="color: orange; font-size: 20px;">orange</p>

//JSX
// 중괄호를 두 번 쓰는 이유는 객체도 자바스크립트이기 때문
// 이렇게 쓰거나,
<p style={{color: 'orange', fontSize: '20px'}}>orange</p>

//혹은 스타일 객체를 변수로 만들고 쓸 수 있음
function App() {
  const styles = {
    color: 'orange',
    fontSize: '20px'
  };

  return (
    <div className="App">
      <p style={styles}>orange</p>
    </div>
  );
}

 

[ Props의 개요 ]

▶ props란?

컴포넌트 끼리의 정보교환 방식!

부모 컴포넌트가 자식 컴포넌트에게 물려준 데이터. 다시 말해, 컴포넌트 간의 정보 교류 방법임.

  • props는 반드시 위에서 아래 방향으로 흐름 즉, [부모] → [자식] 방향으로만 흐름(단방향).
  • props는 반드시 읽기 전용으로 취급하며, 변경하지 않음

 

▶ props로 값 전달하기

1. 전달하기 - [주체: 부모]

컴포넌트 간의 정보를 교류할 때 Props를 사용

 

import React from "react";
function App() {
  return <GrandFather />;
}
function GrandFather() {
  return <Mother />;
}
function Mother() {
	const name = '홍부인';
  return <Child motherName={name} />; // 💡"props로 name을 전달했다."
}
function Child() {
  return <div>연결 성공</div>;
}
export default App;

 

motherName이라는 이름으로 name 값을 Child 컴포넌트에게 전달해준 것. 이 과정을 'Props 로 정보를 전달했다'고 표현

 

2. 받기 - [주체: 자식]

function Child(props){
	console.log(props) // 이게 바로 props
	return <div>연결 성공</div>
}

 

부모가 보내준 정보가 객체 형태로 보내짐

props란 결국 부모 컴포넌트가 자식에게 넘겨준 데이터들의 묶음이라고 볼 수 있음

리액트에서는 Props를 통해 부모 컴포넌트로부터 자식 컴포넌트에 데이터를 넘겨줄 수 있음

 

▶ props로 받은 값을 화면에 렌더링 하기

import React from "react";
function Child(props) {
  return <div>{props.motherName}</div>;
}
function Mother() {
  const name = "홍부인";
  return <Child motherName={name} />;
}
function GrandFather() {
  return <Mother />;
}
function App() {
  return <GrandFather />;
}
export default App;

 

propsobject literal 형태이기 때문에 {props.motherName} 로 꺼내서 사용할 수 있음

object literal의 keymotherName인 이유는 Child로 보내줄 때 motherName={name} 으로 보내주었기 때문

여기서 'object literal'란 {key: “value”} 데이터 형태를 의미

 

또한 JSX에서도 { } 중괄호를 사용하면 자바스크립트 코드를 사용할 수 있음

 

▶ prop drilling

[부모] → [자식] → [그 자식] → [그 자식의 자식]이 데이터를 받기 위해선 무려 3번이나 데이터를 내려줘야 함

이것을 바로 prop drilling(props가 아래로 뚫고 내려간다)라고 함

이를 피하기 위해 나중에 Redux와 같은 데이터 상태관리 툴을 사용해야 함

 

[ Props Children ]

▶ children이란?

컴포넌트에서 말하는 children은 자식 컴포넌트로 정보를 전달하는 또 다른 방법

우리는 이미 그 방법을 props라고 배웠고 이놈도 props가 맞음

 

// src/App.js
import React from "react";
function User() {
  return <div></div>;
}
function App() {
  return <User>안녕하세요</User>;
}
export default App;

 

실행하면 안녕하세요가 안뜸. 이유는 User 컴포넌트에서는 그 정보를 받지 않았기 때문

여기서는 정보를 <User hello='안녕하세요'>가 아닌 <User>안녕하세요</User>로 보내고 있음

이것이 바로 children props를 보내는 방식. 정보를 받는 방식은 기존과 동일하지만 이름이 children으로 정해져 있음

 

function User(props) {
	console.log(props.children)
  return <div></div>;
}

 

▶ children의 용도

Layout 컴포넌트를 만들 때 자주 사용

Layout컴포넌트 안에는 header라는 컴포넌트가 있고, header아래에 {props.children}를 통해서 props를 받아 렌더링 하고 있음. 즉 Layout컴포넌트가 쓰여지는 모든 곳에서 <Layout>…</Layout> 안에 있는 정보를 받아서 가져올 수 있는 것

 

import React from "react";
import Layout from "./components/Layout";
function App() {
  return (
    <Layout> 
      <div>여긴 App의 컨텐츠가 들어갑니다.</div>
    </Layout>
  );
}
export default App;

 

이 코드를 통해 Layout에 있는 header가 보여지게 되고 “여긴 App의 컨텐츠가 들어갑니다.”라는 문장이 Layout의 props로 전달되는 것. 결과적으로 우리는 header 컴포넌트를 Layout 컴포넌트에서 한번만 작성하면 여러 페이지에서 모두 보여지게 할 수 있는 것

 

[ Props 추출 ]

▶ 구조분해 할당과 Props

구조 분해 할당 통해 props 내부값 추출하기

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

이것을 조금 더 짧게 쓰는 방법이 있음. 바로 JS의 구조 분해 할당을 이용하는 것

propsobject literal형태의 데이터 그래서 구조 분해 할당을 이용할 수 있음

 

function Todo({ title }){
	return <div>{title}</div>
}

 

여러개의 props를 받는다면, { }안에 여러개의 props를 그대로 써주면 됨

 

function Todo({ title, body, isDone, id }){
	return <div>{title}</div>
}

 

▶ defaultProps

defaultProps란?

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

컴포넌트를 만들고 자식 컴포넌트에서 props를 받다보면 자주 받거나 또는 무조건 받아야 하는 props들이 있음

 

// components/Child.js

import React from 'react';

function Child({ name }){
	return <div>내 이름은 {name} 입니다. </div>
}

export default Child

 

Child 컴포넌트 입장에서는 부모 컴포넌트에서 name을 props 정보를 받기 전까지는 name 이 없는 상태

그래서 자식 컴포넌트 입장에서는 name이 무엇인지 알 수 없음

결과적으로 자식컴포넌트는 화면에 아무것도 표시해주지 못하게 됨(내 이름은 "" 입니다. )

이런 경우를 방지하기 위해 부모 컴포넌트에서 props를 받기전까지 임시로 사용 할 수 있는 props를 자식컴포넌트에서 직접 설정할 수 있음. 이후에 부모 컴포넌트에서 name props가 오게되면 설정된 defaultProps는 사라지고 내려 받은 props로 값이 바뀌게 됨

 

default props 지정하기

 

import React from 'react';
function Child({ name }){
	return <div>내 이름은 {name} 입니다. </div>
}
Child.defaultProps={
	name: '기본 이름'
}
export default Child

 

이렇게 하면 props값을 받지 못했더라도 임시로 props의 값을 채울 수 있음

이후 부모 컴포넌트로부터 props가 오게되면 defaultProps값은 진짜 props값으로 변경됨

마치 함수의 default argument를 설정하는것과 같음

 

▶ default argument

매개변수가 지정되지 않았으면 자동으로 지정해줄 값을 정하라는 의미

 

function multiply(a, b = 1) {
  return a * b;
}
console.log(multiply(5, 2));
// Expected output: 10
console.log(multiply(5));
// Expected output: 5

 

[ State ]

▶ State

State란?

State란 컴포넌트 내부에서 바뀔 수 있는 값을 의미. 바꾸는 이유는 바로 UI(엘리먼트)로의 반영을 위해서

 

State 만들기

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

 

import React, { useState } from 'react';
function GrandFather() {
  const [name, setName] = useState("김할아"); // 이것이 state!
  return <Mother grandFatherName={name} />;
}

 

useState 는 state를 만들어주는 리액트에서 제공하는 기능 그래서 리액트에만 존재하는 개념이자 기능

리액트에서는 이것을 훅이라고 부름

 

useState 훅을 사용하는 방식

 

const [ value, setValue ] = useState( 초기값 )

 

  1. 먼저 const 로 선언을 하고 [ ] 빈 배열 을 생성
  2. 배열의 첫번째 자리에는 이 state의 이름 그리고 두번째 자리에는 set을 붙이고 state의 이름을 붙임
  3. useState( )의 인자에는 이 state의 원하는 처음값(initial state)을 넣어줌

 

State 변경하기

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

하지만 이렇게 바뀐 값은 브라우저를 새로고침하면 다시 초기값으로 바뀌게 됨

setValue을 통해서 바꾼 값은 어디에 저장되는 것이 아니기 때문에 단순히 화면에서만 바뀐 값으로 다시 렌더링 됨

 

▶ useState + onChange Event

const App = () => {
  const [value, setValue] = useState("");
  const onChangeHandler = (event) => {
    const inputValue = event.target.value;
    setValue(inputValue);
  };
	console.log(value) 
  return (
    <div>
      <input type="text" onChange={onChangeHandler} value={value} />
    </div>
  );
};

 

 

이벤트 핸들러 안에서 자바스크립트의 event 객체(리액트 개념X, HTML DOM event개념 O)를 꺼내 사용할 수 있음

사용자가 입력한 input의 값은 event.target.value 로 꺼내 사용할 수 있음

마지막으로 state인 value를 input의 attribute인 value에 넣어주면 input과 state 연결하기 완료

 

결국 총정리하면 사용자가 input에 어떤 값을 입력하면 그 값을 입력할 때마다(onChange될 때마다) value라는 state에 setValue해서 넣어주는 것

 

[ 불변성 & 순수함수 ]

▶ 불변성

불변성이란?

불변성이란 메모리에 있는 값을 변경할 수 없는 것을 말함

JS의 데이터 형태중에 원시 데이터는 불변성이 있고 원시 데이터가 아닌 객체, 배열, 함수는 불변성 없음

 

변수를 저장하면 메모리에 어떻게 저장이 되는가?

만약 우리가 let number = 1 이라고 선언을 하면 메모리에는 '1'값이 저장됨. number변수는 메모리에 있는 1을 참조함

let secondNumber = 1 이라고 다른 변수를 선언을 했다고 가정한다면 이때도 자바스크립트는 이미 메모리에 생성되어 있는 1이라는 값을 참조함

즉 number와 secondNumber는 변수의 이름은 다르지만 같은 메모리의 값을 바라보고 있는 것

그래서 우리가 콘솔에 number === secondNumber 를 하면 true가 나옴

 

하지만 원시데이터가 아닌 값(객체, 배열, 함수)는 이렇지 않음

let obj_1 = {name: ‘kim’} 이라는 값을 선언하면 메모리에 obj_1이 저장이 됨

그리고 이어서 let obj_2 = {name: ‘kim’} 이라고 같은 값을 선언하면 obj_2라는 메모리 공간에 새롭게 저장이 됨

그래서 obj_1 === obj_2  false가 나옴

 

데이터를 수정하면 어떻게 될까?

원시 데이터는 불변성이 있음

위에 예시에서 number 값을 변경한다고 가정한다면 기존 메모리에 저장이 되어 있는 1이라는 값이 변하지 않고, 새로운 메모리 저장공간에 2가 생기고 number라는 값을 새로운 메모리 공간에 저장된 2를 참조함

그래서 secondNumber를 콘솔에 찍으면 여전히 1이라고 나옴

number와 secondNumber는 각각 다른 메모리 저장공간을 참조하고 있기 때문

 

객체는 불변성이 없음

그래서 obj_1.name = ‘park’이라고 새로운 값을 할당한다면 기존 메모리 저장공간에 있는 {name: ‘kim’} 이라는 값이 {name : ‘park’} 으로 바뀌어 버림(크나큰 문제)

 

결론적으로 원시데이터는 수정을 했을 때 메모리에 저장된 값 자체는 바꿀 수 없고 새로운 메모리 저장공간에 새로운 값을 저장. 원시데이터가 아닌 데이터는 수정했을 때 기존에 저장되어 있던 메모리 저장공간의 값 자체를 바꿔버림

 

왜 리액트에서는 원시데이터가 아닌 데이터의 불변성을 지켜주는 것을 중요시할까?

리액트에서는 화면을 리레더링 할지 말지 결정할 때 state의 변화를 확인함

state가 변했으면 리렌더링하고 state가 변하지 않았으면 리렌더링을 하지 않음

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

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

즉, 개발자가 값은 바꿨지만 리액트는 state가 변했다고 인지하지 못하게 되고 결국 마땅히 일어나야 할 리렌더링이 일어나지 않게됨

 

리액트 불변성 지키기 예시

배열을 setState 할 때 불변성을 지켜주기 위해 직접 수정을 가하지 않고 전개 연산자를 사용해서 기존의 값을 복사하고 그 이후에 값을 수정하는 식으로 구현함

 

import React, { useState } from "react";
function App() {
  const [dogs, setDogs] = useState(["말티즈"]);
  function onClickHandler() {
		// spread operator(전개 연산자)를 이용해서 dogs를 복사 그리고 나서 항목을 추가
    setDogs([...dogs, "시고르자브르종"]);
  }
  console.log(dogs);
  return (
    <div>
      <button onClick={onClickHandler}>버튼</button>
    </div>
  );
}
export default App;

 

▶ 순수함수

개념

하나 이상의 인자를 받고 인자를 변경하지 않고 참조하여 새로운 값을 반환하는 함수

즉, 같은 input동일한 인자가 전달되면 항상 동일한 결과를 반환하는 함수(코드 블록)

 

예시

1. 순수함수

// 매개변수를 복사한 값을 변경하는 순수함수
const addSixPure = (arr) => {
  // 펼침 연산자로 새로운 배열에 6 추가
  newArr = [...arr, 6];
  return newArr;
};

 

2. 순수함수가 아닌 것

const num_arr = [1, 2, 3, 4, 5];
// 매개변수의 값을 직접 변경하는 불순함수
const addSixImpure = (arr) => {
  // 매개변수에 직접 6 추가
  arr.push(6);
  return arr;
};

 

[출처 : https://velog.io/@fe_jungseok/리액트와-순수함수] 

 

리액트 과정에서 왜 순수함수를?

  1. 컴포넌트의 많은 루틴을 순수 함수로서 작성하기를 요구하고 있음
    • 컴포넌트에서 state와 props가 같으면 항상 같은 값을 반환해야함
    • 다른 Side effects를 발생시키지 않아야 함(HTTP 요청, 데이터 저장, 쿠키 조작 등)
  2. 포넌트의 상탯값은 불변 객체(Immutable Object)로 관리해야만 함
    • 수정할 때에는 기존 값을 변경하는 것이 아니라, 같은 이름의 새로운 객체를 생성
  3. 이를 통해, UI 개발의 복잡도를 낮추고, 버그 발생 확률도 줄임
    • 같은 입력에 대해 항상 같은 출력을 보장하니, 테스트 하기도 훨씬 수월