[React] JSX, 렌더링 엘리먼트, Components, Props

이슬기·2023년 12월 21일

React

목록 보기
3/13

JSX

js : javascript
x : 자바스크립트의 확장 문법

  • javascript + XML/HTML

  • React의 createElement() 함수

    • 역할 : 내부적으로 XML, HTML 코드를 javascript로 변환하는 과정을 거침. 최종적으로 jsx코드에서javascript 코드로 변환됨
  • 장점

    • 필수는 아니지만 얻을 수 있는 장점이 많음
    1. 간결한 코드
    2. 가독성 향상 - 버그 발견 쉬움
    3. injection Attacks 방어 : 입력창에 소스 코드 입력하여 그 코드가 그대로 실행되도록 하는 해킹 방법
  • 사용법

    • HTML과 자바스크립트 섞인 형태로 사용
      ...XML/HTML {JavaScript 코드}...XML/HTML
    • 태그의 속성(attribute)에 값을 넣는 방법
      큰따옴표 사이에 문자열을 넣거나
      const element = < div tabIndex="0">< /div>;
      중괄호 사이에 자바스크립트 코드 넣기
      const element = < img src={user.avatarUrl}>< /img>;
    • 자식(childern) 정의 방법
      상위태그가 하위태그를 둘러싸도록 하면 칠드런 정의됨.
      div 태그의 칠드런 - h1, h1 태그
import React from "react";

function Book(props){
  return(
    <div>
      <h1>{`이 책의 이름은 ${props.name} 입니다.`}</h1>
      <h2>{`이 책은 총 ${props.numOfPage}페이지로 이뤄져 있습니다.`}</h2>
    </div>
  );
}
export default Book;
import React from "react";
import Book from "./Book";

function Library(props){
  return (
    <div>
      {/* 3개의 Book 컴포넌트를 렌더링 하고 있다. */}
      <Book name="처음 만난 파이썬" numOfPage={300} />
      <Book name="처음 만난 AWS" numOfPage={400} />
      <Book name="처음 만난 리액트" numOfPage={500} />
    </div>
  );
}
export default Library;
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
// 라이브러리 컴포넌트 가져옴
import Library from './chapter_03/Library';
// 리액트 돔 사용하여 루트 돔 노드에 렌더링
ReactDOM.render(
  <React.StrictMode>
    <Library />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

렌더링 엘리먼트

  • Elements : 리액트 요소. 리액트 앱을 구성하는 가장 작은 블록들
  • 리액트 엘리먼트는 DOM 엘리먼트의 가상 표현
    • DOM 엘리먼트가 더 크고 무거움
  • Elements는 화면에서 보이는 것들을 기술
  • 리액트 Elements는 자바스크립트 객체 형태로 존재
    • 불변성 갖고 있음. 한 번 생성되면 못 바꿈
  • 이 객체를 만드는 역할을 하는 것이 createElement() 함수
  • createElement() 함수를 호출할 때 넣는 3가지 파라미터
  1. type
    html 태그 이름이 문자열로 들어가거나 또 다른 react 컴포넌트가 들어감 -> html 태그가 됨
  2. props
    엘리먼트의 속성(attribute) - 사실 속성보다는 좀 더 상위에 있는 복잡한 개념이지만 일단 이렇게 이해
  3. children
    해당 엘리먼트의 자식 엘리먼트들이 이 부분에 들어감
    개발자 도구에서 하나의 html 태그 하위에 다시 여러 개의 html 태그가 나옴

  • 특징
    • 불변성 : 엘리멘트 생성 후에는 children, attribute 변경 불가
      • 구워진 붕어빵 속 내용은 바꿀 수 없듯
      • 화면에 변경된 엘리먼트들을 보여주려면 새로운 엘리먼트를 만들어 기존과 바꿔치기 함(빠른 렌더링 속도 - Virtual DOM)
  • Elements rendering 하기

    Root DOM Node
    <div id="root"></div>

리액트로만 만들어진 모든 웹사이트들은 단 하나의 루트 돔노드를 가짐

루트DIV에 리액트 엘리먼트를 렌더링하기 위해서 아래 코드 사용

엘리먼트 하나 생성 후 생성된 엘리먼트를 루트DIV에 렌더링. 렌더링 위해 리액트돔에 렌더라는 함수 사용.
렌더 함수의 1번째 파라미터 : React 엘리먼트
2번째 파라미터 : HTML 엘리먼트 - DOM 엘리먼트에 렌더링 하는 역할을 함

  • 돔 엘리먼트와 리액트 엘리먼트 차이
    • 돔 엘리먼트 : 실제 브라우저 DOM에 존재
    • 리액트 엘리먼트 : 리액트의 Virtual DOM에 존재

결국 React 엘리먼트가 렌더링되는 과정은 Virtual DOM에서 실제 DOM으로 이동하는 과정임

  • 렌더링된 Elements를 업데이트 하기
    • 반드시 새로 만들어야 함
import React from "react";
// 현재 시간 출력하는 컴포넌트
function Clock(props){
  return(
    <div>
      <h1>안녕, 리액트!</h1>
      <h2>현재 시간 : {new Date().toLocaleTimeString()}</h2>
    </div>
  );
}
export default Clock;
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 라이브러리 컴포넌트 가져옴
import Library from './chapter_03/Library';
import Clock from './chapter_04/Clock';
// 리액트 돔 사용하여 루트 돔 노드에 렌더링
// setInterval 함수 사용해 1초마다 새롭게 clock 컴포넌트를 root.div에 rendering함
// 매 초 Clock 컴포넌트의 엘리먼트 새롭게 생성됨
setInterval(() => {
  ReactDOM.render(
    <React.StrictMode>
      <Clock />
    </React.StrictMode>,
    document.getElementById('root')
  );  
}, 1000);

reportWebVitals();

Components, Props

Components

리액트는 컴포넌트 기반의 구조
리액트에서는 모든 페이지가 컴포넌트로 구성되어 있고 하나의 컴포넌트는 또 다른 여러 개의 컴포넌트 조합으로 구성될 수 있다.
레고 블록 조립하듯 컴포넌트들을 모아 새로운 컴포넌트 만들 수 있다.

  • 작은 컴포넌트들이 모여 하나의 컴포넌트를 구성하고 또 이런 컴포넌트들이 모여 전체 페이지를 구성한다.

    하나의 컴포넌트를 반복 사용하여 전체 코드 양 줄일 수 있디 -> 개발시간, 유지보수 비용 감소

  • 개념적으로 자바스크립트 함수와 비슷

    • 리액트 컴포넌트도 입력 받아서 정해진 출력 내뱉음
      단, 리액트 컴포넌트의 입력은 Props가 출력은 React elements가 한다.
      • 리액트 컴포넌트 역할 : 어떤 속성들을 입력으로 받아 그에 맞는 리액트 엘리먼트 생성하여 리턴
        • 리액트 컴포넌트 : 붕어빵 틀 / 리액트 엘리먼트 : 각 붕어빵

Props

리액트 컴포넌트의 속성

  • 붕어빵에 들어가는 재료

    각기 다른 이미지와 텍스트를 갖고 있는 것 - Props

    Props는 컴포넌트에 전달할 다양한 정보를 담고 있는 자바스크립트 객체

  • 특징

    • read-only : 읽기 전용. 읽을 수만 있다 = 값을 변경할 수 없다
      프랍스의 값은 React 컴포넌트가 엘리먼트를 생성하기 위해 사용하는 값이다. 그런데 이 값들이 엘리먼트를 생성하는 도중 바뀌면 제대로 된 엘리먼트가 생성될 수 없다(붕어빵 다 구워졌는데 속재료 바꿀 수 없듯).
      • 다른 프랍스 값으로 엘리먼트 생성하려면?
        새로운 값을 컴포너늩에 전달해 새 엘리먼트를 생성 - 이 과정에서 엘리먼트가 다시 렌더링
  • 자바스크립트 함수의 속성


    -> 리액트 컴포넌트의 정의와 관련되어 있다

  • 모든 리액트 컴포넌트는 그들의 Props에 관해서는 Pure 함수 같은 역할을 해야 한다.
    = 모든 리액트 컴포넌트는 Props를 직접 바꿀 수 없고, 같은 Props에 대해서는 항상 같은 결과(리액트 엘리먼트)를 보여줄 것

  • Props 사용법
    <jsx의 경우>
    중괄호 : 무조건 JavaScript 코드 들어간다.
    Props에 값을 넣을 때도 문자열 이외 정수, 변수, 다른 컴포넌트 컴포넌트 등이 들어가면 중괄호로 감싸야 함(문자열로 중괄호로 감싸도 상관은 없음).


    Props의 중괄호를 사용해 Props의 값으로 컴포넌트도 넣을 수 있다.
    Layout 컴포넌트의 Props로는 정수 값을 가진 width, height와 react 엘리먼트로 header, footer가 들어오게 된다.

    jsx 사용하면 간단하게 컴포넌트에 Props를 넣을 수 있다.

Component 만들기 및 렌더링

컴포넌트에는 클래스 컴포넌트와 함수 컴포넌트가 있다. 클래스가 불편함에 따라 나오게 된 함수 컴포넌트. 이 중요하다.

  • Function Component
    모든 리액트 컴포넌트는 pure 함수 같은 역할 해야 함 = 리액트의 컴포넌트를 일종의 함수로 생각함.

    간단한 코드를 가진다는 장점이 있다.

  • Class Component
    JS ES6의 class 사용해 만들어진 형태의 컴포넌트.
    함수 컴포넌트에 비해 몇 가지 추가적인 기능을 갖고 있다.
    함수 컴포넌트와 가장 큰 차이점 : React의 모든 클래스 컴포넌트는 React.Component를 상속 받아 만든다.
    상속 받았기 때문에 결과적으로 리액트 컴포넌트가 되는 것이다.

  • Component 이름
    항상 대문자로 시작해야 한다.

    • React는 소문자로 시작하는 컴포넌트를 DOM 태그로 인식하기 때문
      • div, span : 내장 컴포넌트를 뜻함. 문자열 형태로 react.createElement에 전달됨
      • Foo : react.createElement Foo 형태로 컴파일되며 JS파일 내에서 사용자가 정의했거나 임포트한 컴포넌트를 가리킴
  • Component 렌더링
    컴포넌트는 붕어빵 틀 역할.
    때문에 실제로 컴포넌트가 화면에 렌더링 되는 것은 아님.
    컴포넌트라는 붕어빵 틀을 통해 찍어 나온 엘리먼트라는 붕어빵이 실제로 화면에 보이는 것.
    따라서 렌더링을 위해 가장 먼저 컴포넌트로부터 엘리먼트를 만들어야 함.

    실제 렌더링하는 코드

Component 합성과 추출

  • Component 합성
    여러 개의 컴포넌트를 합쳐 하나의 컴포넌트를 만드는 것.
    리액트에서는 컴포넌트 안에 또 다른 컴포넌트 쓸 수 있다.
    -> 복잡한 화면을 여러 개의 컴포넌트로 나눠 구현 가능

웰컴 컴포넌트를 사용해서 컴포넌트 합성하는 예제 코드)
Props의 값을 다르게 해서 웰컴 컴포넌트 여러 번 사용함.

  • 앱 컴포넌트 : 웰컴 컴포넌트 3개를 포함하고 있는 컴포넌트.
    이와 같이 여러 개의 컴포넌트를 합쳐 또 다른 컴포넌트를 만드는 것이 컴포넌트 합성

    앱 컴포넌트 안에 3개의 웰컴 컴포넌트가 있고 각각의 웰컴 컴포넌트의 각기 다른 프랍스를 갖고 있다.
    이렇게 앱 컴포넌트를 루트로 해서 하위 컴포넌트들이 존재하는 형태가 리액트로만 구성된 앱의 기본적인 구조임.
  • Component 추출
    복잡한 컴포넌트를 쪼개서 여러 개의 컴포넌트로 나누는 것.
    큰 컴포넌트에서 일부 추출해 새로운 컴포넌트를 만드는 것.
    • 재사용성 높여줌 ( 컴포넌트 작아질수록 기능, 목적 명확. 프랍스 단순해지니까) + 개발속도 향상
    • 기능 단위로 구분 + 바로 재사용 가능한 형태로 추출하는 것이 좋다.

추출 예제 코드)

  1. Avatar 추출

    Props에 기존에 사용하던 author 대신 조금 더 보편적인 의미를 갖는 user로 변경(재사용성 측면 고려)

    가독성 높아짐

  2. UserInfo 추출



실습 - 댓글 컴포넌트 만들기

import React from "react";

function Comment(props) {
  return (
    <div>
      <h1>제가 만든 첫 컴포넌트입니다.</h1>
    </div>
  )
}
export default Comment;
import React from "react";
import Comment from "./Comment";

function CommentList(props) {
  return (
    <div>
      <Comment />
    </div>
  )
}
export default CommentList;
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 라이브러리 컴포넌트 가져옴
import Library from './chapter_03/Library';
import Clock from './chapter_04/Clock';
import CommentList from './chapter_05/CommentList';
// 리액트 돔 사용하여 루트 돔 노드에 렌더링
ReactDOM.render(
  <React.StrictMode>
    <CommentList />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

+ css추가, 컴포넌트 코드 변경

import React from "react";

const styles = {
  wrapper: {
    margin: 8,
    padding: 8,
    display: "flex",
    flexDirection: "row",
    border: "1px solid grey",
    borderRadius: 16,
  },
  imageContainer: {},
  image: {
    width: 50,
    height: 50,
    borderRadius: 25,
  },
  contentContainer: {
    marginLeft: 8,
    display: "flex",
    flexDirection: "column",
    justfyContent: "center",
  },
  nameTest: {
    color: "black",
    fontSize: 16,
    fontWeight: "bold",
  },
  commentText: {
    color: "black",
    fontSize: 16,
  },
};
function Comment(props) {
  return (
    <div style={styles.wrapper}>
      <div style={styles.imageContainer}>
        <img 
            src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" 
            style={styles.image} 
        />
      </div>

      <div style={styles.contentContainer}>
        <span style={styles.nameText}>이슬기</span>
        <span style={styles.commentText}>
            제가 만든 첫 컴포넌트입니다.
        </span>
      </div>
    </div>
  )
}
export default Comment;

+ Comment 컴포넌트에 Props 추가하기(동적변경 위해)

import React from "react";

const styles = {
  wrapper: {
    margin: 8,
    padding: 8,
    display: "flex",
    flexDirection: "row",
    border: "1px solid grey",
    borderRadius: 16,
  },
  imageContainer: {},
  image: {
    width: 50,
    height: 50,
    borderRadius: 25,
  },
  contentContainer: {
    marginLeft: 8,
    display: "flex",
    flexDirection: "column",
    justfyContent: "center",
  },
  nameTest: {
    color: "black",
    fontSize: 16,
    fontWeight: "bold",
  },
  commentText: {
    color: "black",
    fontSize: 16,
  },
};
function Comment(props) {
  return (
    <div style={styles.wrapper}>
      <div style={styles.imageContainer}>
        <img 
            src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" 
            style={styles.image} 
        />
      </div>

      <div style={styles.contentContainer}>
        <span style={styles.nameText}>{props.name}</span>
        <span style={styles.commentText}>{props.comment}</span>
      </div>
    </div>
  )
}
export default Comment;

Props를 사용하도록 comment 컴포넌트를 변경하면 아래 그림과 같이 아무 내용도 나오지 않게 된다. name과 comment 값이 정의되지 않아 undefined이기 때문.

  • commentList 컴포넌트에 Props 추가
import React from "react";
import Comment from "./Comment";

function CommentList(props) {
  return (
    <div>
      <Comment name={"이슬기"} comment={"안녕하세요, 슬기입니다."}/>
    </div>
  )
}
export default CommentList;

수정 시 코멘트 컴포넌트의 props로 입력한 네임과 코멘트 값이 전달되어 아래처럼 내용이 표시된다.

  • 댓글 추가
import React from "react";
import Comment from "./Comment";

function CommentList(props) {
  return (
    <div>
      <Comment name={"이슬기"} comment={"안녕하세요, 슬기입니다."}/>
      <Comment name={"유재석"} comment={"리액트 재밌어요~~"}/>
    </div>
  )
}
export default CommentList;

실습 - Comment데이터를 별도의 객체로 분리하기(동적으로 받아온 데이터 표시할 수 있는 구조로)

import React from "react";
import Comment from "./Comment";


const comments = [
  {
    name: "이슬기",
    comment: "안녕하세요. 슬기입니다."
  },
  {
    name: "유재석",
    comment: "리액트 재밌어요~~"
  },
  {
    name: "김지유",
    comment: "저도 리액트 배워보고 싶어요!"
  }
]
function CommentList(props) {
  return (
    <div>
      {comments.map((comment) => {
        return (
          <Comment name={comment.name} comment={comment.comment} />
        );
      })}
    </div>
  );
}
export default CommentList;

0개의 댓글