Back-End/React.js, 스프링 부트, AWS로 배우는 웹 개발 101

[React.js, 스프링 부트, AWS로 배우는 웹 개발 101][프론트엔트 개발] - 서비스 개발

얄루몬 2022. 6. 14. 13:00

본 포스팅은 'React.js, 스프링 부트, AWS로 배우는 웹 개발 101 - 김다정'님의 책을 보고 작성되었습니다.


목차
1. 컴포넌트
2. Props와 state
3. material ui를 이용한 디자인
4. 이벤트 핸들러

1. 컴포넌트

Todo 컴포넌트

checkbox와 label을 렌더링하는 컴포넌트

import React from 'react'

class Todo extends React.Component {
  render() {
    return (
        <div className='Todo'>
            <input type="checkbox" id="todo0" name="todo0" value= "todo" />
            <label for="todo0">Todo 컴포넌트 만들기</label>
        </div>
    );
  }
}

export default Todo;

App컴포넌트에서 Todo 컴포넌트 사용

import React from "react";

class Todo extends React.Component {
  render() {
    return (
      <div className="Todo">
        <input 
        type="checkbox" 
        id = "todo0"
        name="todo0"
        value="todo0"/>
        <label for = "todo0">Todo 컴포넌트 만들기</label>
      </div>
    );
  }
}

export default Todo;

Todo 컴포넌트 실행

  • JSX도 원하는 컴포넌트를 나열함으로써 원하는 만큼 컴포넌트를 재사용할 수 있다.

2. Props와 state

Props와 state를 사용한 수정 코드

import React from "react";

class Todo extends React.Component {
  constructor(props){
    super(props);
    this.state={item: props.item};
  }
  render() {
    return (
      <div className="Todo">
        <input 
        type="checkbox" 
        id = {this.state.item.id}
        name={this.state.item.id}
        checked={this.state.item.done}/>
        <label id = {this.state.item.id}>{this.state.item.title}</label>
      </div>
    );
  }
}

export default Todo;
  • 위의 경우엔 {state.item.id}를 사용해 HTML 태그 내에서 자바스크립트 변수를 사용한 경우이다.
    • 자바스크립트로 된 변수를 JSX에서 사용하기 위해선 변수를{}로 묶어주면 된다. {자바스크립트 코드}를 통해 HTML 안에서도 자바스크립트를 사용할 수 있다. 
  • state는 리액트가 관리하는 오브젝트이다. 리액트에서는 추후 변경이 가능한 변수를 state 오브젝트에서 관리한다.
    • 그 이유는 자바스크립트 내에서 변경한 변수의 값을 HTML에 다시 렌더링하기 위함이다.

props에서 item을 넘겨주는 방법

import React from 'react'

class Todo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      item:{id:0, title:"Hello world 1", done:true},
    };
  }
  render() {
    return (
        <div className='Todo'>
            <Todo item={this.state.item} />
        </div>
    );
  }
}

export default Todo;

props로 매개변수가 넘어가는 과정

props로 매개변수가 넘어가는 과정

Todo 리스트

import React from 'react'
import Todo from './Todo'

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items:[
        {id:"0", title:"Hello world 1", done:true},
        {id:"1", title:"Hello world 2", done:false},
      ],
    };
  }
  render() {
    var todoItems = this.state.items.map((item, idx)=>(
      <Todo item = {item} key={item.id} />
    ));
    return (
        <div className='App'>
            {todoItems}
        </div>
    );
  }
}

export default App;
  1. Todo를 하나 더 만들어 item을 하나 더 넘겨본 코드이다.
  2. Todo 컴포넌트를 연속 2개를 늘어 놓는 대신 배열과 반복문을 이용해 구현한 코드이다.

Todo 리스트

3. material ui를 이용한 디자인

  • material ui는 UI를 조금 더 편리하고 깔끔하게 구성할 수 있게 도와주는 라이브러리이고 리액트 18에는 아직 지원하지 않아 17 버전으로 다운그레이드 하여 yarn을 통해 다운 받아주었다. 
  • 복잡한 css 없이도 UI 개선이 가능하다. 이 역시도 컴포넌트로 제공되는 것임을 잊지말자.

material ui를 사용한 Todo 컴포넌트 디자인

import React from "react";
import {ListItem, ListItemText, InputBase, Checkbox} from '@material-ui/core'; 

class Todo extends React.Component {
  constructor(props){
    super(props);
    this.state={item: props.item};
  }
  render() {
    const item = this.state.item;
    return (
      <ListItem>
        <Checkbox checked={item.done}/>
        <ListItemText>
          <InputBase
            inputProps={{"aria-label":"naked"}}
            type = "text"
            id = {item.id}
            name = {item.id}
            value = {item.title}
            multiline = {true}
            fullWidth = {true}
          />
        </ListItemText>
      </ListItem>
    );
  }
}
export default Todo;

material ui를 사용한 App 컴포넌트 디자인

import React from 'react';
import Todo from './Todo';
import {Paper, List} from '@material-ui/core';
import "./App.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items:[
        {id:"0", title:"Hello world 1", done:true},
        {id:"1", title:"Hello world 2", done:false},
      ],
    };
  }
  render() {
    var todoItems = this.state.items.length > 0 && (
      <Paper style={{margin:16}}>
        <List>
          {this.state.items.map((item,idx)=>(
            <Todo item ={item} key ={item.id}/>
          ))}
        </List>
      </Paper>
    );
    return (
        <div className='App'>
            {todoItems}
        </div>
    );
  }
}

export default App;

@material-ui 사용해 UI 구성

4. 이벤트 핸들러

Todo 추가

  • Todo 추가를 위한 UI
  • 백엔드 콜을 대신할 가짜(Mock) 함수를 작성
  • 핸들러 함수 구현과 핸들러 함수를 UI에 연결하는 방법

UI 구성

import React from 'react';
import {TextField, Paper, Button, Grid} from '@material-ui/core'

class AddTodo extends React.Component {
  constructor(props){
    super(props);
    this.state = {item: {title: ""}}; /* 사용자 입력을 저장할 오브젝트 */
  }

  render() {
    return (
      <Paper style={{margin:16, padding:16}}>
        <Grid container>
          <Grid xs={11} md={11} item style={{paddingRight:16}}>
            <TextField placeholder='Add Todo here' fullWidth />
          </Grid>
          <Grid xs={1} md={1} item>
            <Button fullWidth color='secondary' variant='outline'>
              +
            </Button>
          </Grid>
        </Grid>
      </Paper>
    );
  }
}

export default AddTodo;
import React from 'react';
import Todo from './Todo';
import {Paper, List, Container} from '@material-ui/core';
import "./App.css";
import AddTodo from './AddTodo';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items:[
        {id:"0", title:"Hello world 1", done:true},
        {id:"1", title:"Hello world 2", done:false},
      ],
    };
  }
  render() {
    var todoItems = this.state.items.length > 0 && (
      <Paper style={{margin:16}}>
        <List>
          {this.state.items.map((item,idx)=>(
            <Todo item ={item} key ={item.id}/>
          ))}
        </List>
      </Paper>
    );
    return (
        <div className='App'>
          <Container maxWidth="md">
            <AddTodo/>
            <div className='TodoList'>{todoItems}</div>
          </Container>
        </div>
    );
  }
}

export default App;

Add 핸들러 추가

순서

  1. 키보드 입력
  2. onInputChange 함수 실행
  3. + 버튼 클릭 또는 Enter 키 누름
  4. onButtonClick 함수 실행 or enterKeyEventHandler 함수 실행

구현해야 할 함수들 목록

구현해야 하는 함수

  • onInputChange
    • 사용자가 input 필드에 키를 하나 입력할 때마다 실행되며 input 필드에 담긴 문자열을 자바스크립트 오브젝트에 저장한다.
  • onButtonClick
    • 사용자가 +버튼을 클릭할 때 실행되며 onInputChange에서 저장하고 있던 문자열을 리스트에 추가해준다.
  • enterKeyEventHandle
    • 사용자가 input 필드상에서 엔터 또는 리턴키를 눌렀을 때 실행되며 onInputChange에서 저장하고 있던 문자열을 리스트에 추가해준다.

onInputChange 함수 작성

  onInputChange = (e) => { //함수 작성
    const thisItem = this.state.item;
    thisItem.title = e.target.value;
    this.setState({item:thisItem});
    console.log(thisItem);
  }
  • Event e를 매개변수로 받는다. event는 어떤 일이 일어난 것을 의미한다.
  • 자바스크립트의 event 오브젝트는 어떤 일이 일어났을 때의 상태와 그 일에 대한 정보를 담고 있다.
  • onChage()는 이벤트가 발생할 때 실행해야 하는 함수로 이벤트 핸들러라고 한다. 개발자는 우리가 만든 함수와 이벤트 핸들러를 사용필드와 연결해주는 작업을 해야 한다.

onInputChange 함수 사용

<TextField 
    placeholder='Add Todo here' 
    fullWidth
    onChange={this.onInputChange}
    value ={this.state.item.title}
/>

  • 사용자가 input 필드에 입력하는 정보를 컴포넌트 내부에서 임시로 저장하려고 변수를 초기화 시켜주었다.
  • input 필드에 정보를 입력하기 시작하면 그 정보는 TextField 컴포넌트로 전달된다.
  • TextField는 onChange를 props로 받고 이 함수는 사용자가 TextField에서 키보드를 하나 누를 때마다 실행된다.
  • onChange에 핸들러 함수인 onInputChange를 연결해 사용자가 입력하는 정보를 item에 저장할 수 있다.

간단 정리쇼

이벤트 함수를 작성 -> 이벤트 핸들러를 통해 사용하고자 하는 필드에 연결 -> 필드 값을 변화 시켜주기(value값을 위에 예시에선 변경해줌.).

add함수 작성

  • 추가되는 새로운 Todo 리스트의 경우엔 AddTodo 컴포넌트에서 관리할 수 없다. 그 이유는 App 컴포넌트가 AddTodo의 상위 컴포넌트로 하위 컴포넌트에서 상위 컴포넌트의 items에 접근할 수 없기 때문이다.
  • 따라서 리스트에 추가하는 함수는 App 컴포넌트에서 작성해주어야 하고 App 컴포넌트에 add() 함수를 추가해준 뒤 해당 함수를 AddTodo의 프로퍼티로 넘겨 AddTodo에서 사용할 수 있게 해야 한다.
import React from 'react';
import Todo from './Todo';
import {Paper, List, Container} from '@material-ui/core';
import "./App.css";
import AddTodo from './AddTodo';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items:[
        {id:"0", title:"Hello world 1", done:true},
        {id:"1", title:"Hello world 2", done:false},
      ],
    };
  }

  // add() 함수 추가
  add = (item) => {
    const thisItems = this.state;
    item.id = "ID" + thisItems.length; // key를 위한 id 추가
    item.done = false; // done 초기화
    thisItems.push(item); // 리스트에 아이템 추가
    this.setState({items:thisItems}); //업데이트의 경우엔 반드시 this.setState로 진행
    console.log("items: ", this.state.items); 
  } 
  render() {
    var todoItems = this.state.items.length > 0 && (
      <Paper style={{margin:16}}>
        <List>
          {this.state.items.map((item,idx)=>(
            <Todo item ={item} key ={item.id}/>
          ))}
        </List>
      </Paper>
    );

    //함수 연결 AddTodo에서 사용할 수 있게 props로 넘겨준다.
    return (
        <div className='App'>
          <Container maxWidth="md">
            <AddTodo add={this.add} />
            <div className='TodoList'>{todoItems}</div>
          </Container>
        </div>
    );
  }
}

export default App;
// add() 함수 추가
  add = (item) => {
    const thisItems = this.state.items;
    item.id = "ID" + thisItems.length; // key를 위한 id 추가
    item.done = false; // done 초기화
    thisItems.push(item); // 리스트에 아이템 추가
    this.setState({items:thisItems}); //업데이트의 경우엔 반드시 this.setState로 진행
    console.log("items: ", this.state.items); 
  }
<AddTodo add={this.add} />

AddTodo에서 add 함수 사용 방법

import React from 'react';
import {TextField, Paper, Button, Grid} from '@material-ui/core'

class AddTodo extends React.Component {
  constructor(props){
    super(props);
    this.state = {item: {title: ""}}; /* 사용자 입력을 저장할 오브젝트 */
    this.add = props.add; // props의 함수를 this.add에 연결
  }

  onInputChange = (e) => { //함수 작성
    const thisItem = this.state.item;
    thisItem.title = e.target.value;
    this.setState({item:thisItem});
    console.log(thisItem);
  }

  onButtonClick= (e) => {
    this.add(this.state.item);//add함수 사용
    this.setState({item:{title: ""}});
  }

  render() {
    //함수 연결
    return (
      <Paper style={{margin:16, padding:16}}>
        <Grid container>
          <Grid xs={11} md={11} item style={{paddingRight:16}}>
            <TextField 
            placeholder='Add Todo here' 
            fullWidth
            onChange={this.onInputChange}
            value ={this.state.item.title}
          />
          </Grid>
          <Grid xs={1} md={1} item>
            <Button 
              fullWidth 
              color='secondary' 
              variant='outline'
              onClick={this.onButtonClick}>
              +
            </Button>
          </Grid>
        </Grid>
      </Paper>
    );
  }
}

export default AddTodo;

간단 정리쇼

상위 컴포넌트에서 관리하는 객체를 사용해야 할 경우?

  1. 상위 컴포넌트 (App)
    1. 상위 컴포넌트에서 함수를 만든다.
    2. 만든 함수를 하위 컴포넌트의 props로 넘겨준다.
  2. 하위 컴포넌트 (AddTodo)
    1. 사용하고자 하는 함수를 하위컴포넌트에서 props로 연결해준다.
    2. 하위 컴포넌트에서 사용 해준다. (onButtonClick에서 add() 함수를 props로 넘겨 받아서 사용한다는 의미이다.)
    3. 이벤트 핸들러를 사용해서 함수를 연결해준다.

Enter키 입력 시 아이템 추가

import React from 'react';
import {TextField, Paper, Button, Grid} from '@material-ui/core'

class AddTodo extends React.Component {
  constructor(props){
    super(props);
    this.state = {item: {title: ""}}; /* 사용자 입력을 저장할 오브젝트 */
    this.add = props.add; // props의 함수를 this.add에 연결
  }

  onInputChange = (e) => { //함수 작성
    const thisItem = this.state.item;
    thisItem.title = e.target.value;
    this.setState({item:thisItem});
    console.log(thisItem);
  }

  onButtonClick= (e) => {
    this.add(this.state.item);//add함수 사용
    this.setState({item:{title: ""}});
  }
  enterKeyEventHandler = (e) => {
    if(e.key === 'Enter'){
      this.onButtonClick();
    }
  }

  render() {
    //함수 연결
    return (
      <Paper style={{margin:16, padding:16}}>
        <Grid container>
          <Grid xs={11} md={11} item style={{paddingRight:16}}>
            <TextField 
              placeholder='Add Todo here' 
              fullWidth
              onChange={this.onInputChange}
              value ={this.state.item.title}
              onKeyPress={this.enterKeyEventHandler}
            />
          </Grid>
          <Grid xs={1} md={1} item>
            <Button 
              fullWidth 
              color='secondary' 
              variant='outline'
              onClick={this.onButtonClick}>
              +
            </Button>
          </Grid>
        </Grid>
      </Paper>
    );
  }
}

export default AddTodo;

전체적인 흐름 정리(개인적으로 학습된 것들)

  • 컴포넌트가 무엇인지 리액트는 컴포넌트를 어떻게 사용하는지를 살펴보았다.
  • props는 변하지 않는 것 부모 요소에서 정해지는 것이라는 점을 알았다.
  • state는 변할 수 있는 것이고 컴포넌트가 스스로 관리하는 것이라는 점을 알았다.
  • 조금 더 편리한 UI 사용을 위해 material-ui 사용법을 알아 보았고 이는 부트스트랩과 별반 다르지 않아 어렵지 않게 다가왔다.
  • 이벤트 핸들러를 추가하기 위해서는 함수를 추가한 뒤 바인딩한 뒤 사용하고 상위 컴포넌트에서 정의한 함수를 사용하기 위해서는 함수 추가 바인딩 하위 컴포넌트에 연결하는 패턴을 학습했다.