프론트엔드/React

리액트 기초이론 3 - JSX 문법

syleemomo 2021. 10. 22. 11:12
728x90

 

* 리액트에서 자주 사용하는 HTML 태그 

<div> <h1> <h2> <ul> <li> 
<a> <p> <button> <input>
<span> <img> <Link> <style>
<html> <head> <body> <title>
<Fragment></Fragment>

Link 컴포넌트는 a 태그의 리액트 버전이다. 다른 페이지 주소로 이동한다. 차이점은 a 태그는 외부 URL 주소로 이동할 수 있지만 Link 컴포넌트는 웹사이트 내부에서만 이동 가능하다. Fragment 태그는 화면에 렌더링되지는 않지만 컴포넌트를 하나의 최상위 요소로 묶어주는 역할을 한다. 

 

* JSX 문법을 사용하지 않고 HTML 문서를 만드는 방법

참고문서

 

JSX 없이 사용하는 React – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

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

function App() {
  return (<div className="greeting">Hello</div>)
}

export default App;

App.js 코드를 위와 같이 만들자! 이것은 JSX 문법을 사용한 코드다. 

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

function App() {
  return React.createElement(
     'div', 
     {className: "greeting"}, 
     `Hello`
  )
}

export default App;

App.js 코드를 위와 같이 변경해보자! 이것은 JSX 문법을 사용하지 않은 것이다. React 객체의 createElement 메서드를 사용하여 div 태그를 생성하고 세번째 인자로 주어진 값을 HTML 요소의 컨텐츠로 설정한다. 이는 자바스크립트의 DOM 생성방법인 document.createElement('div') 메서드와 유사하다. 리액트는 JSX 문법을 해석한 다음 위와 같이 컴파일된다.

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

function App() {
  const e = React.createElement
  return e(
     'div', 
     {className: "greeting"}, 
     `Hello`
  )
}

export default App;

React 객체의 createElement 메서드를 코드에서 여러번 사용해야 하는 경우에는 코드 가독성을 위하여 위와 같이 변수에 참조값을 저장한 다음 사용할 수 있다. 

const element = {
  type: 'div',
  props: {
    className: 'greeting',
    children: 'Hello'
  }
};

마지막으로 React 객체의 createElement 메서드로 생성한 값은 위와 같이 단순한 자바스크립트 객체의 형태이다. 이러한 객체를 React 엘리먼트 라고 하며, React는 이 객체를 읽어서 가상 DOM 에서 HTML 요소를 생성한 다음 HTML 문서에 삽입한다. 

 

* JSX 문법이란

참고문서

 

JSX 소개 – React

A JavaScript library for building user interfaces

ko.reactjs.org

const title = <h1>Hello, world!</h1>

JSX 문법은 Javascript XML 의 약자이다. 자바스크립트 문법을 확장하여 HTML 문서를 손쉽게 생성할 수 있도록 도와준다. JSX 문법을 사용하여 구현한 HTML 을 리액트 엘리먼트 또는 JSX 엘리먼트라고 한다.

 

* JSX 문법을 사용하는 이유 

JSX 문법은 HTML 문서를 만드는 웹퍼블리셔나 프론트엔드 엔지니어 개발자들에게 익숙하다. 이는 웹개발시 프로그램의 가독성을 높여주는 부분이다. 다른 점은 중괄호({})를 이용하여 데이터나 자바스크립트 표현식(문법)을 삽입할 수 있다는 것이다. 

 

* JSX 문법의 사용방법

JSX 문법은 자바스크립트의 변수, 상수, 표현식을 삽입하기 위하여 중괄호({})를 사용한다. 

<div>{자바스크립트 표현식}</div>

 

JSX 문법에서 변수나 상수 사용하기

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

function App() {
  const name = "syleemomo"
  return (
    <div className="App">
      <div>{name}</div>
    </div>
  );
}

export default App;

JSX 문법은 변수나 상수 데이터를 중괄호 안에 삽입할 수 있다. 

 

JSX 문법에서 함수 사용하기

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

function App() {
  const name = "syleemomo"
  const changeName = (name) => {
    return `Hello, ${name}`
  }
  return (
    <div className="App">
      <div>{changeName(name)}</div>
    </div>
  );
}

export default App;

JSX 문법은 자바스크립트 표현식을 중괄호 안에 삽입할 수 있다. 여기서는 함수 호출을 하고 있다. 

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

function App() {
  const name = "syleemomo"
  const ChildComponent = ({name}) => { // 컴포넌트
    return <div>{name}</div>
  }
  return (
    <div className="App">
      <div><ChildComponent name="syleemomo"/></div>
    </div>
  );
}

export default App;

만약 위의 ChildComponent 처럼 함수이지만 JSX 엘리먼트(태그)를 반환한다면 리액트에서 이는 컴포넌트로 간주한다. 그러므로 사용할때는 함수 호출이 아니라 컴포넌트 형태로 사용해야 한다. 

 

JSX 문법에서 객체 사용하기

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

function App() {
  const person = {
    name: "syleemomo",
    age: 3
  }
  return (
    <div className="App">
      <div>{person.name} - {person.age}</div>
    </div>
  );
}

export default App;

여기서는 객체의 프로퍼티 값을 JSX 문법에서 사용하고 있다. 

 

JSX 문법에서 배열 사용하기

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    fruits: [
      {name: "딸기", price: "9000원"},
      {name: "수박", price: "17000원"},
      {name: "바나나", price: "4700원"}
    ]
  }
  render(){
    const { fruits } = this.state
    return (
      <>
        <h1>과일 가격표</h1>
        {fruits.map( (fruit, id) => {
          return (
            <div key={id}>
              <h3>{fruit.name}</h3>
              <h4>{fruit.price}</h4>
              <h4>----------------</h4>
            </div>
          )
        })}
      </>
    )
  }
  
}

export default App;

JSX 문법에서 배열을 화면에 렌더링할때는 위와 같이 배열 메서드인 map 을 사용하면 된다. JSX 문법에서 동일한 컴포넌트가 반복되는 목록(리스트)을 사용하는 경우 key 속성(props)을 설정해주는 것이 좋다. 그렇지 않으면 아래와 같은 경고 메세지를 출력한다. key 속성을 설정해주면 리스트의 특정 아이템이 업데이트 되었을때 리액트가 어느 것이 변경되었는지 빠르게 알아차릴 수 있다.

목록(리스트)을 렌더링할때 key 속성(props)을 사용하지 않은 경우의 경고 메시지

 

 

JSX 문법에서 연산자 사용하기

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

function App() {
  const person = {
    name: "syleemomo",
    age: 3
  }
  return (
    <div className="App">
      <div>{person.name? "your name is nice !": "name doesn't exist !"} - {person.age}</div>
    </div>
  );
}

export default App;

여기서는 삼항 연산자 표현식을 사용하였다. 이와 같이 중괄호 안에 다양한 연산자로 표현할 수 있다. 여기서는 person 객체의 name 프로퍼티 존재 유무에 따라 서로 다른 UI 를 보여준다. 

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

function App() {
  const person = {
    age: 3
  }
  return (
    <div className="App">
      <div>{person.name? "your name is nice !": "name doesn't exist !"} - {person.age}</div>
    </div>
  );
}

export default App;

물론 person 객체에 name 프로퍼티가 존재하지 않으면 person.name 은 undefined 이므로 "name doesn't exist ! " 문구를 화면에 출력한다. 

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

function App() {
  const person = {
    name: "syleemomo",
    age: 3
  }
  return (
    <div className="App">
      <div>{
              person.name? 
                <h1>Your name is nice !</h1>: <h1>"name doesn't exist !"</h1>
            } - {person.age}</div>
    </div>
  );
}

export default App;

물론 중괄호 안에 JSX 엘리먼트(태그)를 사용해도 된다. 위 코드는 person 객체의 name 프로퍼티 유무에 따라 다른 UI 를 렌더링한다. 

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    loading: true
  }
  render(){
    const { loading } = this.state
    return (
      <>
        {loading && <h1>Home page</h1>}
      </>
    )
  }
  
}

export default App;

JSX 문법에서 중괄호 안에 AND 연산자를 사용할 수 있다. 위와 같이 하면 loading 상태(state)가 true 인 경우에만 "<h1>Home page</h1> 을 반환한다. 왜냐하면 자바스크립트 엔진은 AND 연산자를 사용한 경우 loading 이 false 이면 아래와 같이 중괄호 안의 전체 표현식 loading && <h1>Home page</h1> 이 false 이기 때문에 나머지 코드는 실행하지 않는다. 이를 문법 용어로 단락평가(short-circuit evaluation) 이라고 한다. 

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    loading: false
  }
  render(){
    const { loading } = this.state
    return (
      <>
        {loading && <h1>Home page</h1>}
      </>
    )
  }
  
}

export default App;

위 코드는 loading 상태(state)가 false 이므로 AND 연산자의 오른쪽 구문은 실행되지 않는다. 

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    loading: true
  }
  render(){
    const { loading } = this.state
    return (
      <>
        {loading || <h1>Home page</h1>}
      </>
    )
  }
  
}

export default App;

OR 연산자는 반대로 동작한다. loading 이 true 인 경우 loading || <h1>Home page</h1> 전체 구문은 true 가 되므로 OR 연산자 오른쪽 구문은 실행되지 않는다. 

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    loading: false
  }
  render(){
    const { loading } = this.state
    return (
      <>
        {loading || <h1>Home page</h1>}
      </>
    )
  }
  
}

export default App;

loading 이 false 인 경우 loading || <h1>Home page</h1> 전체 구문은 OR 연산자(||) 연산자 뒤에 오는 코드에 의해 결과가 정해지므로 자바스크립트는 <h1>Home page</h1> 을 실행하고 화면에 렌더링한다. 

 

JSX 문법에서 조건문 사용하기

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

function App() {
  let loading = false 
  if(!loading){
    return <h1>This is Laoding page</h1>
  }else{
    return <h1>This is Home page</h1>
  }
}

export default App;

JSX 문법(중괄호 내부)에서는 조건문을 사용할 수 없다. 조건문을 사용하려면 위와 같이 컴포넌트 내부에서 조건에 따라 서로 다른 JSX 엘리먼트를 반환하는 형태가 되어야 한다. 위 코드는 loading 변수의 상태에 따라 서로 다른 UI 화면을 보여준다. 

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

function App() {
  let loading = false 
  return (
    <>
      {loading ? <h1>This is Laoding page</h1> : <h1>This is Home page</h1>}
    </>
  )
}

export default App;

굳이 JSX 문법으로 조건문을 구현해야 한다면 위와 같이 삼항연산자를 이용하면 된다.

 

JSX 태그를 변수나 상수에 저장하기

function App() {
  const loadingPage = (
    <div>
      <h1>Loading page</h1>
    </div>
  )
  const homePage = (
    <div>
      <h1>Home page</h1>
    </div>
  )
  const loading = {
    state: true
  }
  if(!loading.state){
    return loadingPage
  }else{
    return homePage
  }
}

export default App;

물론 JSX 문법을 사용하여 생성한 JSX 엘리먼트(태그)를 변수나 상수에 담아서 사용가능하다. 변수나 상수에 담는 것이 가능한 이유는 리액트에서 JSX 문법을 컴파일하면 최종적으로 객체가 되기 때문이다. 위 코드는 loadingPage, homePage 변수 각각에 해당 페이지의 UI에 해당하는 JSX 엘리먼트(태그)를 저장한 다음 로직에 따라 필요한 UI 를 렌더링한다. 

 

JSX 문법 최상위 요소로 묶기

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  render(){
    return (
      <h1>Hello world !</h1>
    )
  }
  
}

export default App;

JSX 문법에서 반환할 요소가 하나인 경우에는 위와 같이 하면 된다. 

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

function App() {
  return (
    <>
      <h1>hello</h1>
      <p>nice to meet you !</p>
    </>
  )
}

export default App;

그러나 여러개의 요소를 반환해야 하는 경우 <div> 태그나 <Fragment> 태그로 묶어서 최상위 요소 하나만 리턴해야 한다. 그렇지 않으면 에러가 발생한다. 

 

JSX 문법에서 종료태그로 닫아주기

const profilePicture = <img src={user.profilePicture}></img>

 

JSX 문법에서는 종료태그를 이용하여 반드시 태그를 닫거나 슬래쉬(/)를 이용하여 태그가 끝났음을 알려줘야 한다. 그렇지 않으면 에러가 발생한다. 

 

하나의 모듈에서 여러개의 컴포넌트 사용하기

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

function App() {
  
  // 모듈
  const MyComponents = {
    DatePicker: function(props) {    
      return <div>Imagine a {props.color} datepicker here.</div>; // 컴포넌트
    },
    Movie: function(props){
      return <div>Watch movie {props.title} in the future !</div> // 컴포넌트
    },
    Fruit: function(props){
      return <div>{props.name} is healthy food ^^</div>  // 컴포넌트
    }
  }

  return (
    <div className='App'>
      <MyComponents.DatePicker color="blue"/>
      <MyComponents.Movie title="아이언맨"/>
      <MyComponents.Fruit name="블루베리"/>
    </div>
  ) 
  
}

export default App;

MyComponents 는 하나의 모듈이다. 해당 모듈 안에서 객체의 프로퍼티를 사용하여 여러개의 컴포넌트를 정의할 수 있다.  모듈을 사용하는 이유는 서로 관련된 컴포넌트끼리 그룹화하기 위함이다. 실행 결과는 아래와 같다.

 

하나의 모듈에서 여러개의 컴포넌트를 사용하여 출력한 결과

 

 

* JSX 문법의 사용 예시

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    loading: false,
    isAuthorized: true,
    userId: 'sunrise'
  }
  render(){
    const { loading, isAuthorized, userId } = this.state
    return (
      <>
        {!loading && isAuthorized && userId === 'sunrise' && 
          (
            <div>
              <h1>Home page</h1>
              <h3>This is home</h3>
            </div>
          )
        }
      </>
    )
  }
  
}

export default App;

위 코드는 loading 이 false 이고, isAuthorized 가 true 이며, userId 값이 sunrise 문자열인 경우에 홈페이지 화면을 보여준다. loading, isAuthorized, userId 상태를 변경하여 화면에 어떤 변화가 있는지 실험해보자!

 

import './App.css';
import React, { Component } from 'react'

const loadingPage = <h1>로딩중...</h1>
const homePage = <h1>홈 페이지</h1>

class App extends Component {
  state = {
    loading: true,
  }
  componentDidMount(){
    setTimeout(() => {
      this.setState({ loading: false })
    }, 1000);
  }
  render(){
    const { loading } = this.state
    if(loading){
      return loadingPage
    }else{
      return homePage
    }
  }
  
}

export default App;

위 코드는 화면에 보여줄 UI 를 JSX 엘리먼트로 미리 변수에 저장한 다음, 조건문을 사용하여 loading 상태에 따라 서로 다른 UI 를 화면에 보여준다. 로딩중... 이라는 문구가 화면에 보인 다음 1초 후에 홈 화면을 보여준다. 

 

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    count: 0
  }
  showUI = (cnt) => {
    let ui = null;
    switch(cnt){
      case 0:
        ui = <h1>Home</h1>
        break;
      case 1:
        ui = <h1>About</h1>
        break;
      case 2:
        ui = <h1>Detail</h1>
        break;
      default:
        ui = <h1>NotFound</h1>
    }
    return ui
  }
  increase = () => {
    this.setState({count: this.state.count + 1})
  }
  render(){
    const { count } = this.state
    return (
      <>
      {this.showUI(count)}
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

switch case 문을 이용해서 사용자가 버튼을 클릭할때마다 카운트 값을 증가시키고, 카운트 값에 따라 다른 웹화면 UI를 보여주는 코드는 위와 같다. App.js 코드를 위와 같이 변경해보고 테스트해보자!  

하지만 한가지 문제점이 있다. showUI 메서드는 JSX 엘리먼트를 반환한다. 결국 컴포넌트인 셈이다. 그러므로 아래와 같이 변경하는 것이 조금 더 나은 방법이다. 화면에 렌더링할 데이터를 pages 라는 배열로 선언하고, count 상태에 따라 특정 UI 를 선택하여 화면에 보여준다. 

import './App.css';
import React, { Component } from 'react'

const pages = ["Home", "About", "Detail", "NotFound"]

class App extends Component {
  state = {
    count: 0
  }
  increase = () => {
    this.setState({count: this.state.count + 1})
  }
  render(){
    const { count } = this.state
    const selectedPage = pages[count % pages.length]
    return (
      <>
      <h1>{selectedPage}</h1>
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

코드를 위와 같이 수정하고 동일하게 동작하는지 확인하자. 동일하게 동작하지는 않을 것이다. 왜냐하면 위 코드에서는 count 상태가 0->1->2->3->0 과 같이 순환하기 때문에 페이지 UI 도 순환하면서 반복적으로 보여준다.

import './App.css';
import React, { Component } from 'react'

const Page = ({ cnt }) => {
  let ui = null;
  switch(cnt){
    case 0:
      ui = <h1>Home</h1>
      break;
    case 1:
      ui = <h1>About</h1>
      break;
    case 2:
      ui = <h1>Detail</h1>
      break;
    default:
      ui = <h1>NotFound</h1>
  }
  return ui
}

class App extends Component {
  state = {
    count: 0
  }
  increase = () => {
    this.setState({count: this.state.count + 1})
  }
  render(){
    const { count } = this.state
    return (
      <>
      <Page cnt={count}/>
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

또는 위와 같이 Page 라는 컴포넌트를 따로 만들고, App 컴포넌트에서 사용하는 것이 자연스러운 방법이다.

 

 

import './App.css';
import React, { Component } from 'react'

const pages = [
  {pageTitle: "Home", pageNum: 1, pageDescription: 'this is home page !'}, // 페이지에 렌더링할 데이터
  {pageTitle: "About", pageNum: 2, pageDescription: 'this is about page !'}, 
  {pageTitle: "Detail", pageNum: 3, pageDescription: 'this is detail page !'}, 
  {pageTitle: "NotFound", pageNum: 4, pageDescription: 'this is 404 page !'}
]

const PageComponent = ({ pageTitle, pageNum, pageDescription }) => { // 컴포넌트
  return (
    <div>
      <h1>{pageTitle} <span>{pageNum}</span></h1>
      <p>{pageDescription}</p>
    </div>
  )
}

class App extends Component {
  state = {
    count: 0
  }
  increase = () => {
    this.setState({count: this.state.count + 1})
  }
  render(){
    const { count } = this.state
    const selectedPage = pages[count % pages.length]
    return (
      <>
      <PageComponent 
        pageTitle={selectedPage.pageTitle} 
        pageNum={selectedPage.pageNum} 
        pageDescription={selectedPage.pageDescription}/>
        
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

 하나의 페이지에 렌더링할 데이터가 많은 경우에는 위와 같이 한 페이지에 나타낼 데이터를 객체로 묶어서 사용하면 된다. 또한, 화면에 보여줄 엘리먼트가 많은 경우 위와 같이 페이지 UI 를 PageComponent 라는 컴포넌트로 따로 정의해 둔 다음 render 메서드에서 사용하면 된다. 이전 수업에서 컴포넌트를 파일에 정의해 놓고 임포트해서 사용하였는데 간단한 UI 의 컴포넌트는 위와 같이 같은 파일에 정의해도 된다. 

 

import './App.css';
import React, { Component } from 'react'

const pages = [
  {pageTitle: "Home", pageNum: 1, pageDescription: 'this is home page !'}, // 페이지에 렌더링할 데이터
  {pageTitle: "About", pageNum: 2, pageDescription: 'this is about page !'}, 
  {pageTitle: "Detail", pageNum: 3, pageDescription: 'this is detail page !'}, 
  {pageTitle: "NotFound", pageNum: 4, pageDescription: 'this is 404 page !'}
]

const PageComponent = ({ pageTitle, pageNum, pageDescription }) => { // 컴포넌트
  return (
    <div>
      <h1>{pageTitle} <span>{pageNum}</span></h1>
      <p>{pageDescription}</p>
    </div>
  )
}

class App extends Component {
  state = {
    count: 0
  }
  increase = async () => {
    console.log(this.state.count)
    await this.setState({count: this.state.count + 1})
    console.log(this.state.count)
    await this.setState({count: this.state.count + 1})
    console.log(this.state.count)
    await this.setState({count: this.state.count + 1})
    console.log(this.state.count)
  }
  render(){
    console.log('렌더링')
    const { count } = this.state
    const selectedPage = pages[count % pages.length]
    return (
      <>
      <PageComponent 
        pageTitle={selectedPage.pageTitle} 
        pageNum={selectedPage.pageNum} 
        pageDescription={selectedPage.pageDescription}/>
        
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

setState에 async await 을 쓰면 이벤트핸들러 안에서도 변경된 값을 읽을수 있다. async, await 은 비동기 코드를 동기로 바꿔주기 때문이다. 그러나 이렇게 코드를 작성하면 리액트의 장점으로 생각되는점이 사라진다. 아래와 같이 상태를 변경할때마다 render  함수가 호출되서 그때마다 화면이 렌더링 된다. 이는 CPU 자원을 많이 소모하며 화면 깜빡임 현상이 발생할 수 있다. 또한, 업데이트를 여러번 하더라도 render 함수가 한번만 호출되서 cpu 자원을 아끼는 리액트의 장점이 사라지게 된다.

리액트 setState 에 async, await 을 사용한 경우

 

import './App.css';
import React, { Component } from 'react'

const pages = [
  {pageTitle: "Home", pageNum: 1, pageDescription: 'this is home page !'}, // 페이지에 렌더링할 데이터
  {pageTitle: "About", pageNum: 2, pageDescription: 'this is about page !'}, 
  {pageTitle: "Detail", pageNum: 3, pageDescription: 'this is detail page !'}, 
  {pageTitle: "NotFound", pageNum: 4, pageDescription: 'this is 404 page !'}
]

const PageComponent = ({ pageTitle, pageNum, pageDescription }) => { // 컴포넌트
  return (
    <div>
      <h1>{pageTitle} <span>{pageNum}</span></h1>
      <p>{pageDescription}</p>
    </div>
  )
}

class App extends Component {
  state = {
    count: 0
  }
  increase = async () => {
    for(let i=0; i<100; i++){
      console.log(this.state.count)
      await this.setState({count: this.state.count + 1})
      await new Promise((resolve, reject) => setTimeout(resolve, 10));
    }
  }
  render(){
    console.log('렌더링')
    const { count } = this.state
    const selectedPage = pages[count % pages.length]
    return (
      <>
      <PageComponent 
        pageTitle={selectedPage.pageTitle}
        pageNum={selectedPage.pageNum}
        pageDescription={selectedPage.pageDescription}
      />
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

async, await 을 사용한 것과 아래와 같이 사용하지 않은 것은 render 함수 호출과 CPU 자원 소모를 줄이는 방향으로 리액트가 설계되었기 때문이다. 

import './App.css';
import React, { Component } from 'react'

const pages = [
  {pageTitle: "Home", pageNum: 1, pageDescription: 'this is home page !'}, // 페이지에 렌더링할 데이터
  {pageTitle: "About", pageNum: 2, pageDescription: 'this is about page !'}, 
  {pageTitle: "Detail", pageNum: 3, pageDescription: 'this is detail page !'}, 
  {pageTitle: "NotFound", pageNum: 4, pageDescription: 'this is 404 page !'}
]

const PageComponent = ({ pageTitle, pageNum, pageDescription }) => { // 컴포넌트
  return (
    <div>
      <h1>{pageTitle} <span>{pageNum}</span></h1>
      <p>{pageDescription}</p>
    </div>
  )
}

class App extends Component {
  state = {
    count: 0
  }
  increase = () => {
    for(let i=0; i<100; i++){
      console.log(this.state.count)
      this.setState({count: this.state.count + 1})
    }
  }
  render(){
    console.log('렌더링')
    const { count } = this.state
    const selectedPage = pages[count % pages.length]
    return (
      <>
      <PageComponent 
        pageTitle={selectedPage.pageTitle}
        pageNum={selectedPage.pageNum}
        pageDescription={selectedPage.pageDescription}
      />
      <button type="button" onClick={this.increase}>페이지 변경</button>
      </>
    )
  }
  
}

export default App;

 

 

JSX 문법 참고문서 2

 

JSX In Depth – React

A JavaScript library for building user interfaces

reactjs.org

 

* JSX 문법의 속성(props) 설정하기

속성(props)이 문자열인 경우

const nameTag = <div name="syleemomo"></div>

JSX 문법에서는 속성(props)이 문자열인 경우 HTML 태그의 속성을 설정하는 것과 동일하게 따옴표로 감싸면 된다. 

 

속성(props)이 숫자인 경우

const counter = <div cnt={3}></div>

JSX 문법에서는 속성(props)이 숫자인 경우 중괄호({})를 사용하여 설정할 수 있다. 

 

속성(props)의 값을 설정하지 않은 경우

<MyTextBox autocomplete /> // 속성에 값을 설정하지 않은 경우
<MyTextBox autocomplete={true} />

속성(props)에 값을 설정하지 않으면 기본적으로 true 이다. 위의 2줄은 동일한 코드이다. 

 

클래스와 이벤트 설정하기

<button className="card-box" 
	onClick={this.handleClick} 
        onChange={this.handleChange}
>click card</button>

JSX 문법에서 스타일을 적용하기 위한 클래스 이름은 카멜 케이스를 사용하여 className 으로 설정한다. 또한, 이벤트 핸들러는 인라인 방식을 사용하고 이벤트 이름도 카멜 케이스를 사용하여 onClick, onChange 와 같이 사용한다. 

 

나머지 연산자 사용하기

import React from 'react'

function Movie({ id, title, language, release, length, author, production }){
    return (
        <>
            <h1>무비 정보</h1>
            <h3>{title}</h3>
            <h3>{language}</h3>
            <h3>{release}</h3>
            <h3>{length}</h3>
            <h3>{author}</h3>
            <h3>{production}</h3>
        </>
    )
}
export default Movie

Movie 컴포넌트는 id, title, language, release, length, author, production 속성(props)을 전달받아서 화면에 렌더링한다.

import './App.css';
import React, { Component } from 'react'
import Movie from './Movie'

class App extends Component {
  render(){
    return (
      <Movie 
        id="1234567890"
        title="해리포터"
        language="english"
        release="2013년 7월 19일"
        length="27345"
        author="조앤K롤링"
        production="sunrise"
      ></Movie>
    )
  }
  
}

export default App;

Movie 컴포넌트를 App 컴포넌트에서 가져와서 화면에 렌더링한다. Movie 컴포넌트에는 많은 속성(props)들이 설정된다. Movie 컴포넌트는 아래와 같이 나머지 연산자(...)를 사용하여 속성(props)을 한번에 전달받을수 있다. 

import React from 'react'

function Movie({ id, ...rest }){
    console.log(rest) // 나머지 속성들이 rest 객체의 프로퍼티로 설정됨
    return (
        <>
            <h1>무비 정보</h1>
            <h3>{rest.title}</h3>
            <h3>{rest.language}</h3>
            <h3>{rest.release}</h3>
            <h3>{rest.length}</h3>
            <h3>{rest.author}</h3>
            <h3>{rest.production}</h3>
        </>
    )
}
export default Movie

위 코드에서 나머지 연산자를 사용하면 id 속성(props)를 제외한 나머지 속성(props)들이 rest 라는 객체의 프로퍼티에 복사된다. 즉, id 를 제외한 나머지 속성(props)들을 rest 라는 하나의 객체로 묶어준다. 

나머지 연산자 출력화면

 

import React from 'react'

function Movie({ id, ...rest }){
    return (
        <>
            <h1>무비 정보</h1>
            <p {...rest}></p> 
        </>
    )
}
export default Movie

위 코드에서 p 태그에 전개 연산자를 사용하고 있다. 이렇게 하면 p 태그에 id 를 제외한 나머지 속성(props)들이 풀어헤쳐서 들어간다. 즉, 하나로 묶어졌던 속성들을 풀어헤쳐서 태그 속성으로 한번에 설정한다. 결과는 아래와 같다.

전개 연산자를 사용한 속성 전달하기

 

 

만약 컴포넌트에 다수의 속성(props)을 한번에 전달하려면 어떻게 하면 될까?

import './App.css';
import React, { Component } from 'react'
import MovieList from './MovieList'

class App extends Component {
  state = {
    movies: [
      {id: "123450", title: 'Harry Potter', language: "korean", release: '2003-02-22', length:"12345", author: "sunrise", production: "sunrise" }, 
      {id: "123451", title: 'Indiana Jhones', language: "english", release: '2009-01-09', length:"12345", author: "sunrise", production: "sunrise"}, 
      {id: "123452", title: 'Terminator', language: "japanese", release: '2007-04-11', length:"12345", author: "sunrise", production: "sunrise"},
      {id: "123453", title: 'Dracula', language: "chinese", release: '2007-04-13', length:"12345", author: "sunrise", production: "sunrise"},
      {id: "123454", title: 'Jurassic Park', language: "germany", release: '2007-05-13', length:"12345", author: "sunrise", production: "sunrise"},
      {id: "123455", title: 'Iron man', language: "italian", release: '2012-12-18', length:"12345", author: "sunrise", production: "sunrise"},
      {id: "123456", title: 'Spider man', language: "russian", release: '2017-03-07', length:"12345", author: "sunrise", production: "sunrise"}
  ]
  }
  render(){
    const { movies } = this.state
    return (
      <MovieList movies={movies}></MovieList>
    )
  }
  
}

export default App;

App 컴포넌트는 위와 같다. movies 상태(state)를 정의하고, MovieList 컴포넌트에 props 로 전달한다. 

import React from 'react'
import Movie from './Movie'

function MovieList({ movies }){
    return (
        <>
            {movies.map( (movie, id) => {
                return (
                    <Movie key={id} {...movie}></Movie>
                )
            })}
        </>
    )
}
export default MovieList

MovieList 컴포넌트는 전달받은 movies 속성(props)을 map 메서드와 Movie 컴포넌트를 이용하여 화면에 렌더링한다. 이때 Movie 컴포넌트에는 id, title, language, release, length, author, production 와 같은 많은 속성(props)을 전달해야 한다. 이러한 경우에 나머지 연산자(...)을 활용하여 한번에 속성(props)을 전달할 수 있다. 

import React from 'react'

function Movie({ id, title, language, release, length, author, production }){
    return (
        <div id={id}>
            <h1>무비 정보</h1>
            <h3>{title}</h3>
            <h3>{language}</h3>
            <h3>{release}</h3>
            <h3>{length}</h3>
            <h3>{author}</h3>
            <h3>{production}</h3>
        </div>
    )
}
export default Movie

Movie 컴포넌트에서는 이전과 동일하게 비구조화 할당({}) 으로 props 를 풀어헤친 다음에 사용하면 된다. 

 

컴포넌트에 컨텐츠(자식요소) 전달하기

import './App.css';
import React, { Component } from 'react'
import Movie from './Movie'

class App extends Component {
  render(){
    return (
      <Movie 
        id="1234567890"
        title="해리포터"
        language="english"
        release="2013년 7월 19일"
        length="27345"
        author="조앤K롤링"
        production="sunrise"
      >
        <h3>자식요소 1</h3>
        <h3>자식요소 2</h3>
        <h3>자식요소 3</h3>
      </Movie>
    )
  }
  
}

export default App;

Movie 컴포넌트에 컨텐츠(자식요소)를 설정할 수 있다. 

import React from 'react'

function Movie({ id, children, ...rest }){
    return (
        <>
            <h1>무비 정보</h1>
            <p {...rest}></p> 
            <>{children}</>
        </>
    )
}
export default Movie

Movie 컴포넌트 내부에서 컨텐츠(자식요소)는 props 객체의 children 프로퍼티를 조회하면 된다. 

children 속성을 이용한 자식요소 조회하기

 

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  render(){
    return [
      // Don't forget the keys :)
      <li key="A">First item</li>,
      <li key="B">Second item</li>,
      <li key="C">Third item</li>,
    ]
  }
  
}

export default App;

리액트 컴포넌트는 엘리먼트 배열을 반환할 수 있다. 위 코드는 li 엘리먼트의 배열을 반환하고 있다. 이것이 가능하기 때문에 배열의 내장 메서드인 map 으로 반환한 배열이 화면에 순서대로 렌더링될 수 있다. 

import './App.css';
import React, { Component } from 'react'

// 컴포넌트
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i)); // props.children : Repeat 컴포넌트의 자식요소로 전달된 콜백함수
  }
  return <div>{items}</div>;
}

function App(){
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  )

}

export default App;

JSX 문법에서는 컴포넌트의 컨텐츠(자식요소)로 함수를 전달할 수 있다. Repeat 컴포넌트 내부에서 props.children 으로 함수를 조회하고 실행할 수 있다. 이때 props.children 은 함수 컴포넌트 또는 콜백함수이다. 함수 컴포넌트가 실행될때마다 items 배열에 <div key={index}>This is item {index} in the list</div> 요소가 추가된다. 그런 다음 items 배열을 화면에 렌더링한다.

import './App.css';
import React, { Component } from 'react'

// 컴포넌트
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(<props.children index={i} key={i}/>); // props.children : Repeat 컴포넌트의 자식요소로 전달된 콜백함수
  }
  return <div>{items}</div>;
}

function App(){
  return (
    <Repeat numTimes={10}>
      {({index}) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  )

}

export default App;

또는 props.children 이 함수 컴포넌트이므로 위와 같이 작성해도 동작한다. 

컴포넌트의 컨텐츠(자식요소)로 함수를 전달한 경우의 결과 화면

 

* Virtual DOM 

DOM 참고문서

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

DOM 은 Document Object Model 의 약자이다. 자바스크립트는 HTML 요소들을 소위 알고리즘에서 말하는 트리 형태로 만들고 나서 DOM 을 검색하고 DOM 을 업데이트한다. 브라우저는 일반적으로 웹페이지에서 여러번의 변경사항이 있으면 변경사항이 생길때마다 DOM 을 업데이트한다. 이렇게 되면 많은 CPU 자원을 소모하게 되어 화면 렌더링이 느려지거나 성능이 저하된다. 

리액트에서는 Virtual DOM 이라는 가상의 DOM 트리를 사용하며, 여러번의 변경사항이 있을 경우 모아두었다가 한번에 가상의 DOM 트리를 업데이트한다. 그런 다음 가상의 DOM 트리와 실제 브라우저 화면에 렌더링된 DOM 트리를 비교하여 서로 다른 부분만 한꺼번에 변경한다. 

이렇게 하면 10번의 변경사항이 존재하는 경우 웹페이지를 10번 다시 그리는 대신에 모든 변경사항을 체크하여 1번만 그리게 되므로 CPU 자원을 효율적으로 사용하고 성능향상을 기대할 수 있다. 

 

* 연습과제 1

사전검색 서비스의 프론트엔드 코드를 컴포넌트로 만들어본다. 즉, 사전검색 서비스의 HTML 코드를 JSX 문법으로 만들어본다. 크롤링한 사전 데이터인 JSON 파일을 가져와서 컴포넌트에 삽입하고 렌더링해보자! 

import words from './dictionary.json'

컴포넌트에서 JSON 파일을 가져오는 코드는 위와 같다. 

// import logo from './logo.svg';
import './App.css';
import React, { Component } from 'react'
import words from './kor_dic_coll.json'

const App = () => {
  const containerStyle = {
    width: '60%',
    columns: '2',
    margin: '50px auto'
  }
  const itemStyle = {
    width: '100%',
    /* height: 300px; */ 
    marginBottom: '10px',
    background: 'tan',
    display: 'inline-block' /* 컬럼 짤림 방지*/
  }
  return (
    <div id="container" style={containerStyle}>
      <h1>사전 검색 서비스</h1>
      {/* 데이터 => HTML 템플릿 */}
      {/* map: 데이터 순회 filter: 삭제, 검색 reduce */}
      {words.map(word => {
        return (
          <div class="item" style={itemStyle}>
              <div class="word"><a href={word.r_link}>{word.r_word}<sup>{word.r_seq}</sup> ({word.r_chi})</a> - {word.r_pos}</div>
              <p class="description">{word.r_des}</p>
          </div>
        )
      })}
    </div>
  )
}
  
export default App;

 

* 연습과제 2

다음은 어느 IT 회사에 등록된 사용자 목록이다. 나이(age)가 0 보다 작거나 실수인 유효하지 않은 정보를 가진 사용자가 존재한다. 또한, 연락처(email)에 @가 존재하지 않거나 .com 으로 끝나지 않은 사용자도 존재한다. 이러한 유효하지 않은 사용자 정보를 걸러내보자! 

const users = [
    {name: 'victoria', age: 13, city: 'seoul', email: 'victoria@gmail.com'},
    {name: 'sun', age: 34, city: 'busan', email: 'sun@gmail.com'},
    {name: 'johseb', age: 25, city: 'busan', email: 'johseb@gmail'}, // 탈락
    {name: 'syleemomo', age: 9.23, city: 'seoul', email: 'syleemomo@nate.com'}, // 탈락
    {name: 'hannah', age: 41, city: 'daegu', email: 'hannah0923*gmail.com'}, // 탈락
    {name: 'shara', age: 37, city: 'seoul', email: 'shara@gmail.com'},
    {name: 'martin', age: -34, city: 'daegu', email: 'martin@gmail.com'}, // 탈락
    {name: 'gorgia', age: 39, city: 'seoul',  email: 'gorgia@gmail.com'},
    {name: 'nana', age: 24, city: 'busan', email: 'nana@gmail.com'},
    {name: 'dannel', age: 19, city: 'seoul', email: 'dannel@gmail.com'},
]

필터링된 사용자 정보를 JSX 문법을 이용하여 아래와 같이 화면에 출력해보자!

유효한 사용자 정보 출력 1
유효한 사용자 정보 2

 

* 연습과제 3

아래는 SNS 에 등록된 나의 친구목록을 버튼 클릭에 따라 필터링해서 보여주는 컴포넌트이다. 예를 들어 "서울" 이라는 버튼을 클릭하면 서울에 사는 친구만 필터링해서 화면에 보여주면 된다. (단, 맨 첫 화면은 필터링되지 않은 전체 친구 목록을 보여줘야 한다. )

import './App.css';
import React, { Component } from 'react'

class App extends Component {
  state = {
    friends: [
      {name: 'victoria', age: 13, city: 'seoul'},
      {name: 'sun', age: 34, city: 'busan'},
      {name: 'johseb', age: 25, city: 'busan'},
      {name: 'syleemomo', age: 9, city: 'seoul'},
      {name: 'hannah', age: 41, city: 'daegu'},
      {name: 'shara', age: 37, city: 'seoul'},
      {name: 'martin', age: 28, city: 'daegu'},
      {name: 'gorgia', age: 39, city: 'seoul'},
      {name: 'nana', age: 24, city: 'busan'},
      {name: 'dannel', age: 19, city: 'seoul'},
    ],
    updatedFriends: null
  }
  // 구현하기
  filterCity = (city) => {
  }

  // 구현하기
  render(){
    let { friends, updatedFriends } = this.state 

    return (
      <>
        <button onClick={() => this.filterCity("seoul")}>서울</button>
        <button onClick={() => this.filterCity("busan")}>부산</button>
        <button onClick={() => this.filterCity("daegu")}>대구</button>
      </>
    ) 
  }
}

export default App;

결과는 아래 화면과 같이 출력되면 된다. 

초기화면
서울에 사는 친구목록
부산에 사는 친구목록&amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;
대구에 사는 친구목록

 

* 연습과제 4

2차원 배열에서 배운 2D 픽셀아트 예제를 참고하여 2차원 배열로 생성한 sign1, sign2, sign3, sign4, sign5 를 1초마다 순서대로 화면에 보여주도록 구현해보자! (단, sign5 를 보여준 다음에는 다시 sign1을 보여줘야 한다.) 

2차원 배열과 배열의 확장 해답을 참고해도 된다. 

className={조건 ? 'cell' : 'cell bright'}

리액트에서 조건부 스타일링은 삼항연산자를 사용하면 된다. 위 코드는 조건에 따라 서로 다른 스타일을 적용한다. 

import './App.css';
import React, { Component } from 'react'

const signs = [
  [
    [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  ],
  [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 1, 1, 1, 1, 0, 0, 1, 0],
    [0, 0, 0, 0, 1, 0, 0, 0, 1, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
    [0, 0, 1, 0, 1, 0, 0, 0, 1, 0],
    [0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
  ],
  [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 1, 1, 1, 0, 0, 0, 1, 0],
    [0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
    [0, 1, 0, 0, 0, 1, 1, 1, 1, 0],
    [0, 1, 0, 0, 0, 1, 1, 1, 1, 0],
    [0, 1, 0, 0, 0, 1, 0, 0, 1, 0],
    [0, 0, 1, 1, 1, 1, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  ],
  [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
    [0, 1, 1, 1, 0, 0, 1, 0, 1, 0],
    [0, 0, 0, 1, 1, 1, 1, 0, 1, 0],
    [0, 0, 0, 1, 1, 1, 1, 0, 1, 0],
    [0, 0, 0, 1, 0, 0, 1, 0, 1, 0],
    [0, 1, 1, 0, 0, 0, 1, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  ],
  [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 1, 0, 0, 1, 1, 0],
    [0, 1, 0, 0, 0, 1, 0, 1, 1, 0],
    [0, 1, 0, 0, 0, 1, 0, 1, 1, 0],
    [0, 1, 0, 0, 0, 1, 0, 1, 1, 0],
    [0, 0, 1, 1, 1, 0, 0, 1, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
  ]
]

class App extends Component {
  state = {
    index: 0,
  }

  // 구현하기
  redraw = () => {
  }

  componentDidMount(){
    setInterval(this.redraw, 1000)
  }

  // 구현하기
  render(){
  }
}

export default App;
body{
  margin: 0;
  padding: 0;
  background: black;
}
.sign{
  width: 100px;
  height: 100px;
  margin: 100px auto;
  background: black;

  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}
.cell{
  width: 10px; 
  height: 10px;
  background: blue;
}
.bright{
  background: red;
}

App.css 파일을 위와 같이 수정하자!

728x90