React - 05

월요일좋아·2023년 1월 4일
0

React

목록 보기
5/11

state

state

  • 현재 컴포넌트의 상태를 나타내는 객체, 현재 컴포넌트에서 변경 가능한 객체, state 는 직접적인 수정이 불가능함(화면 렌더링과 관련이 있음)
  • setState() : state 의 값을 변경하는 함수

리액트 생명주기

컴포넌트가 생성되고 내용이 변경되고 컴포넌트가 삭제되는 상태

  • componentDidMount : 컴포넌트가 생성되고 화면에 렌더링 된 이후 실행되는 함수
  • componentDidUpdate : 컴포넌트의 상태가 변경된 후 실행되는 함수, props 변경(=부모가 주는 값 변경), setState()함수 호출, forceUpdate()를 통한 강제 업데이트 후 동작함
  • componentWillUnmount : 부모 컴포넌트에서 더이상 해당 컴포넌트를 사용하지 않아 삭제된 후 실행

folder3/LinkButton.js

// folder3/LinkButton.js

import React from "react";

class LinkButton extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      linked: false,
    }

    this.setState({
        linked: true,
      });
  }
}

timer 예제

folder3/Notification.jsx

// folder3/Notification.jsx
import React from "react";

const styles = {
  wrapper: {
    margin: 8,
    padding: 8,
    display: "flex",
    flexDirection: "row",
    border: "1px solid grey",
    borderRadius: 16,
  },
  messageText: {
    color: "black",
    fontSize: 16,
  },
};

class Notification extends React.Component {
  constructor(props) {
    super(props);

    this.state = {}
  }

  render() {
    return (
      <div style={styles.wrapper}>
        <span style={styles.messageText}>
          {this.props.message}
        </span>
      </div>
    )
  }

}

export default Notification;

folder3/NotificationList.jsx

// folder3/NotificationList.jsx

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

// 서버에서 전송되어야 할 데이터, 통신 모듈이 없기 때문에 임의로 만들어준 데이터
const msgData = [
  {id: 1, message: "안녕하세요. 오늘 일정입니다."},
  {id: 2, message: "점심 식사 시간입니다."},
  {id: 3, message: "이제 곧 미팅이 시작됩니다."},
];

// 자바스크립트 타이머 객체 정보를 저장할 변수
let timer;

// 클래스 컴포넌트 사용
class NotificationList extends React.Component {

  // 클래스 컴포넌트의 생성자
  constructor(props) {
    super(props);

    // state 객체 선언, 클래스(객체)의 멤버 변수로 선언
    this.state = {
      // 메시지를 저장할 배열
      notification: [],
    };
  }

  // 컴포넌트가 마운트된 후 실행되는 생명주기 함수
  componentDidMount() {
    // object 타입을 사용한 확장표현식. 현재는 비어있음. 타이머가 돌면서 값 들어갈 예정
    const { notification } = this.state;

    // 타이머를 사용하여 지정된 시간을 간격으로 반복 실행 (1000 = 1초)
    timer = setInterval(() => {

      // 첫번째 : 0 < 3 (true)
      // 두번째 : 1 < 3 (true)
      // 지연 변수인 배열 notification 크기가 전연 변수인 배열 msgData 의 크기보다 작을 경우 실행되는 코드
      if (notification.length < msgData.length) {
        const index = notification.length;

        // msgData[0] 값 push
        // msgData[1] 값 push
        notification.push(msgData[index]);

        // setState() 함수를 사용하여 state 객체의 notification 을 방금 업데이트한 값으로 수정
        this.setState({
          // 전역변수로 선언된 notification : 변수로 선언한 notification
          notification: notification,
        });
      }
      else {
        // timer 삭제
        clearInterval(timer);
      }
      // 1초 후 다시 실행
    }, 1000);
  }
  // 화면을 생성(렌더링)하는 함수
  render() {
    return (
      <div>
        {/* map : ES6 버전에서 추가된 배열 관련 함수, 지정한 배열의 크기만큼 반복하고 결과를 배열로 반환하는 함수 */}
        {this.state.notification.map((item) => {
          return <Notification key={item.id} message = {item.message} />;
        })}
      </div>
    );
  }
}

export default NotificationList;


strict

  • strictMode : 자바스크립트의 문법을 엄격하게 확인한다는 의미
  • React.strictMode : 배포 버전에는 제외되고 개발 버전에서만 동작하는 엄격 모드, 몇 가지 함수를 중복 실행하여 잘못된 것이 없는지 개발자에게 확인하도록 함(생략가능)

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import App2 from "./App2";
import App3 from "./App3";
import App4 from "./App4";
import App5 from "./App5";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(

  // strictMode : 자바스크립트의 문법을 엄격하게 확인한다는 의미
  // React.strictMode : 배포 버전에는 제외되고 개발 버전에서만 동작하는 엄격 모드, 몇 가지 함수를 중복 실행하여 잘못된 것이 없는지 개발자에게 확인하도록 함(생략가능)
  <React.StrictMode>
  {/*   <App />*/}
  {/*   <App2 />*/}
  {/*   <App3 />*/}
  {/*   <App4 />*/}
    <App5 />
  </React.StrictMode>
);


reportWebVitals();

setState()

countButton.jsx

// folder3/CountButton.jsx : setState() 익히기

import React from "react";
import Button from "react-bootstrap/Button";
import {logDOM} from "@testing-library/react";

class CountButton extends React.Component {
  constructor(props) {
    super(props);

    // state 객체 생성
    this.state = {
      count: 0,
    }

    // 객체 멤버 변수 count 선언
    this.count = this.state.count;
  }

  // 메서드 2개 생성
  countUp = () => {
    console.log("이전 this.count : " + this.count);
    this.count = this.count + 1

    // setState 함수를 실행해야만 state 의 값이 수정이 되고, state 의 값이 수정되면 render 함수가 재실행되면서 화면을 다시 그려줌
    this.setState({
      count: this.count
    });
    console.log("+ 사용 후 this.count : " + this.count);
  };
  countDown = () => {
    console.log("이전 this.count : " + this.count);
    this.count = this.count - 1
    this.setState({
      count: this.count
    });
    console.log("- 사용 후 this.count : " + this.count);
  };


  render() {
    return (
      <div>
        <label htmlFor="" className={"form-label"}>count : <span>{this.count}</span></label><br/>
        <Button variant={"primary"} onClick={this.countUp} > + </Button>
        <Button variant={"danger"} onClick={this.countDown}> - </Button>
      </div>
    );
  }
}

export default CountButton;


hooks

기존의 존재하는 기능을 추가로 함께 사용하도록 하는 것, state 와 리액트 생명주기 기능을 원하는 시점에 실행할 수 있도록 하는 기능
훅을 사용 시 미리 해당 함수를 import 해서 사용해야 함

  • useState : state 객체를 함수 컴포넌트에서 사용할 수 있도록 하는 기능
  • useEffect : useState() 와 함께 가장 많이 사용되는 훅, 리액트 생명주기 함수인 componentDidMount, componentDidUpdate, componentWillUnmount 의 기능을 하나로 합한 기능
  • useMemo : 재 렌더링 시 연산량이 높은 작업을 반복하는 것을 피할 수 있게 하는 기능 (거의 안씀)
  • useCallback : useMemo 와 비슷한 기능, 값이 아닌 함수를 반환함 (거의 안씀)
  • useRef : 지정한 컴포넌트에 접근할 수 있는 객체 (거의 안씀)

useState

클래스 컴포넌트에 존재하는 state 객체를 함수 컴포넌트에서 사용할 수 있도록 함

  • 사용법
const[변수명, set함수명] = useState(초기값);

변수명 : state 로 지정할 지역변수명
set함수명 : state 로 지정된 변수명에 접두사 set 을 붙여서 사용, 카멜명명법 사용
useState(초기값) : 확장표현식을 통해서 지정한 변수를 state 객체로 설정함, 초기값은 state 로 설정된 변수의 초기값을 말함
state 로 지정할 변수가 여러개일 경우 useState() 를 변수의 수 만큼 실행해야 함

useEffect

클래스 컴포넌트에 존재하는 생명주기 함수를 사용할 수 있도록 함
사용법

useEffect(이펙트함수, 의존성배열);

이펙트함수(= 콜백 함수)

이펙트 함수는 기본적으로 컴포넌트가 처음 렌더링된 후 데이터 업데이트에 의한 재렌더링 시 실행됨
이펙트 함수를 마운트, 언마운트 시 각각 1번씩만 실행하고자 할 경우 의존성 배열에 빈 배열을 사용(componentDidMount, componentWillUnmount 기능 수행)

의존성 배열

콜백 함수인 이펙트 함수가 의존하고 있는 배열, 해당 의존성 배열 안에 있는 변수 중 하나라도 값이 변경되면 실행
의존성 배열이 없을 경우 화면이 재 렌더링 된 이후에 실행(componentDidUpdate 기능 수행)

Hook 의 규칙 :
1. Hook은 무조건 최상위 레벨에서만 호출 (if, for 문 내부에서 훅을 호출하면 안됨)
2. Hook은 컴포넌트가 렌더링 될 때마다 매번 같은 순서로 호출되어야 함
3. 함수 컴포넌트에서만 Hook을 사용할 수 있음
일반적인 자바스크립트 함수에서 Hook을 호출하면 안됨

커스텀 훅

리액트에서 제공하는 훅이 아닌 사용자가 필요에 의해서 생성하여 사용하는 훅
이름에 접두사로 use 를 사용하고, 함수 내부에서 다른 훅을 호출하는 단순 자바스크립트 함수
파라미터 및 반환값을 사용자가 직접 지정할 수 있음
중복되는 로직을 커스텀 훅으로 설정하여 재사용하기 위함
이름의 접두사로 use 를 사용하지 않을 경우 함수 내부에서 훅을 사용하는지 판단할 수 없음

folder3/CountButton2.jsx

// folder3/CountButton2.jsx : hooks 익히기

// useState() 훅을 사용하기 위해서 미리 import
import React, {useState, useEffect} from "react";

// 함수 컴포넌트 사용
function CountButton2(props) {
  // 지역변수
  // let count = 0;

  // useState 를 통해서 state 객체에 추가함
  const [count, setCount] = useState(0);

  // [deps] 값 안줬으면 컴포넌트 DidMount 될때마다 동작, [deps] 값을 [count]로 줬으면 count 값이 변경될때마다 동작
  // useEffect 를 사용하여 componentDidMount, componentDidUpdate 를 구현함
  useEffect(() => {
    document.title = `${count}회 클릭했습니다.`
  }/*[count]*/);


  // 함수 선언
  const countUp = () => {
    // count++; <- 지역변수 사용했을때 코드.(주석처리)

    setCount(count + 1);

    console.log(`count++ : ${count}`);
  }
  const countDown = () => {
    // count--; <- 지역변수 사용했을때 코드.(주석처리)

    // setCount(count - 1);

    // 콜백함수의 형태도 사용 가능
    setCount(() => {
      return count - 1;
    });

    console.log(`count-- : ${count}`);
  }

  return (
    <div>
      <label htmlFor="" className={"form-label"}>count : <span>{count}</span></label><br/>
      <button className={"btn btn-outline-primary"} onClick={countUp}> + </button>
      <button className={"btn btn-outline-danger"} onClick={countDown}> - </button>
    </div>
  );
}

export default CountButton2;


커스텀 훅

이름에 use 를 접두사로 사용
매개변수, 반환값을 사용자 마음대로 설정
내부에서 훅을 사용한 함수이며, 소스코드 재활용을 위해서 사용함
props 가 아닌 initValue 인 이유 : 커스텀 훅이라서

folder3/UseCounter.jsx

// folder3/UseCounter.jsx : 커스텀 훅


import React, {useState} from "react";

// 커스텀 훅
// 이름에 use 를 접두사로 사용
// 매개변수, 반환값을 사용자 마음대로 설정
// 내부에서 훅을 사용한 함수이며, 소스코드 재활용을 위해서 사용함
// props 가 아닌 initValue 인 이유 : 커스텀 훅이라서

// 커스텀 훅으로 설정
// 매개변수를 마음대로 설정함
function UseCounter(initValue) {

  // state 사용을 위해서 useState 설정을 걸어줌
  const [count, setCount] = useState(initValue);

  // 함수 실행 시 state 를 수정하기 위함 setCount 를 실행
  const increaseCount = () => {
    // 자바스크립트에서 지원하는 수학 클래스의 max 함수를 사용하여 0 이하의 값을 사용할 수 없도록 제약을 걸어놓음
    setCount((count) => {return count + 1});
  }

  const decreaseCount = () => {
    setCount((count) => Math.max(count - 1, 0));
  }
  // math 사용한 코드와 동일
  // setCount((count) => {
  //   count--;
  //   if (count < 0) {
  //     count = 0;
  //   }
  //   return count;
  // });
  return[count, increaseCount, decreaseCount];
}

export default UseCounter;

Accomodate.jsx

// folder3/Accommodate.jsx : 커스텀 훅

import React, {useState, useEffect} from "react";
// 커스텀 훅 (useCounter)
import useCounter from "./UseCounter";


const MAX_CAPACITY = 10; // 최대값


function Accommodate(props) {
  // state 를 사용하기 위해 useState() 를 설정
  const [isFull, setIsFull] = useState(false);

  // 원래 코드 :
  // const[count, setCount] = useState(0); -> 하위에 함수 만들어주고....
  // 해당 컴포넌트 내부에서 생성해야 할 state 객체 및 setState 를 커스텀 훅을 통해서 생성함
  // 커스텀 훅을 사용했기 때문에 재활용이 가능함
  const [count, increaseCount, decreaseCount] = useCounter(0);

  // 리액트 생명주기 함수를 사용하기 위해서 useEffect 를 설정함
  // 의존성 배열이 없을 겨우 componentDidMount, componentWillUnmount 를 실행하는 것과 같은 효과
  useEffect(() => {
    console.log("====================");
    console.log("useEffect() is called");
    console.log(`isFull : ${isFull}`);
  }, []);

  // 의존성 배열에 count 를 설정하여 count 값이 수정되면 componentDidUpdate 를 실행하는 것과 같은 효과 발생
  useEffect(() => {
    setIsFull(count >= MAX_CAPACITY);
    console.log("현재 count값 : " + count);
  }, [count]);

  return (
    <div>
      {/* 현재 state 로 설정된 count 의 값을 출력 */}
      <p>{`${count}명 수용했습니다.`}</p>
      {/* 커스텀 훅을 통해서 만들어진 사용자 입장/퇴장 함수를 버튼에 등록 */}
      <button className={"btn btn-warning"} onClick={increaseCount} disabled={isFull}>입장</button>
      <button className={"btn btn-info"} onClick={decreaseCount}>퇴장</button>
      {/* isFull 이 true 면 뒤에거 확인(실행), isFull 이 false 라면 && 기호 뒤에는 아예 읽지도 않음 = 렌더링자체가 안됨 */}
      {isFull && <p style={{color: "red"}}>정원이 가득찼습니다.</p>}
    </div>
  )

}

export default Accommodate;

문제1

숫자 2개를 입력받아 계산 기호 버튼에 따라서 결과를 출력하는 리액트 컴포넌트를 작성하세요

  • input 태그를 통해서 숫자를 입력(태그이름 : num1, num2, result)
  • 버튼 5개 ( + - * / = )
    • 컴포넌트 이름 : Calulator.jsx
  • useState 총 3개 사용 (함수 컴포넌트로 작성)

folder/Calculator.jsx

// folder/Calculator.jsx

/*
문제1) 숫자 2개를 입력받아 계산 기호 버튼에 따라서 결과를 출력하는 리액트 컴포넌트를 작성하세요
- input 태그를 통해서 숫자를 입력(태그이름 : num1, num2, result)
- 버튼 5개 ( + - * / = )
  - 컴포넌트 이름 : Calulator.jsx
- useState 총 3개 사용 (함수 컴포넌트로 작성)
*/

import React, {useState} from "react";

let calResult = 0;
function Calculator(props) {
  const [num1, setNum1] = useState(0);
  const [num2, setNum2] = useState(0);
  const [result, setResult] = useState(0);

  const plus = () => {
    const number1 = parseInt(num1);
    const number2 = parseInt(num2);
    calResult = number1 + number2;
  }
  const minus = () => {
    const number1 = parseInt(num1);
    const number2 = parseInt(num2);
    calResult = number1 - number2;
  }
  const multiply = () => {
    const number1 = parseInt(num1);
    const number2 = parseInt(num2);
    calResult = number1 * number2;
  }
  const division = () => {
    const number1 = parseInt(num1);
    const number2 = parseInt(num2);
    calResult = number1 / number2;
  }

  const equal = () => {
    setResult(calResult);
  }
  return (
    <div>
      <hr/>
      <div className={"my-3 text-center"}>
        <span>숫자1 </span><input type="text" value={num1} onChange={(e) => (setNum1(parseInt(e.target.value)))}/>
        <span className={"ms-3"}>숫자2 </span><input type="text" value={num2} onChange={(e) => (setNum2(parseInt(e.target.value)))}/>
      </div>
      <div className={"text-center"}>
        <button onClick={plus} className={"btn btn-info me-5"}> + </button>
        <button onClick={minus} className={"btn btn-info me-5"}> - </button>
        <button onClick={multiply} className={"btn btn-info me-5"}> * </button>
        <button onClick={division} className={"btn btn-info me-5"}> / </button>
        <button onClick={equal} className={"btn btn-success"}> = </button>
      </div>
      <div className={"text-center mt-4"}>
      <span>결과 = </span><input type="text" value={result}/>
      </div>
    </div>
  );
}

export default Calculator;


Event

리액트는 html 과 같은 이벤트를 가지고 있음
카멜명명법을 사용하므로 onclick="sum()" => onClick={sum} 으로 변경되어 사용

  • 매개변수 전달 시 이벤트 부분에 화살표 콜백함수를 사용
    onClick={() => sum(10)}
  • 이벤트 사용 시 이벤트 핸들러도 매개변수로 전달이 가능함
    onClick={(event) => sum(10, event)}

folder3/Events.jsx

//folder3/Events.jsx

import React from "react";

function Events() {

  // 기본 함수를 클릭이벤트와 연동
  const click1 = () => alert("일반 클릭 이벤트");

  // 매개변수가 있는 함수를 클릭이벤트와 연동
  const click2 = (item) => alert(`매개변수 값 : ${item}, \n매개변수가 있는 이벤트`);

  // 매개변수로 이벤트 핸들러(= 이벤트 객체)를 사용하는 함수를 클릭이벤트아 연동
  const click3 = (item, event) => {
    alert(`매개변수와 event 객체가 있는 클릭 이벤트
매개변수 값 : ${item}, 이벤트 객체 : ${event.type}`);
  }


  return (
    <div>
      <button type={"button"} className={"btn btn-primary"} onClick={click1}>일반 클릭 이벤트</button>
      <button type={"button"} className={"btn btn-success"} onClick={() => click2(100)}>매개변수가 있는 클릭 이벤트</button>
      <button type={"button"} className={"btn btn-info"} onClick={(event) => click3(200,event)}>event 객체가 있는 클릭 이벤트</button>
    </div>
  );
}

export default Events;


confirmButton

folder/ConfirmButon.jsx

// folder/ConfirmButon.jsx

import React, {useState} from "react";

function ConfirmButton(props) {
  const [isConfirmed, setIsConfirmed] = useState(false);
  const handleConfirm = () => {
    setIsConfirmed((prevConfirmed) => !isConfirmed);
    setTimeout(() => {
      setIsConfirmed(false);
    }, 1000)
  };

  return (
    <div>
     <button type={"button"} className={"btn btn-primary"} onClick={handleConfirm} disabled={isConfirmed}>
       {isConfirmed ? "확인 완료" : "확인 하기"}
     </button>
    </div>
  );
}

export default ConfirmButton;
  • 클릭 전
  • 클릭 후
  • 1초 후

App5.jsx

// App5.jsx

import React from "react";
import NotificationList from "./folder3/NotificationList";
import CountButton from "./folder3/CountButton";
import CountButton2 from "./folder3/CountButton2";
import Accommodate from "./folder3/Accommodate";
import Calculator from "./folder3/Calculator";
import Events from "./folder3/Events";
import ConfirmButton from "./folder3/ConfirmButton";

function App5() {
  return (
    <div className={"container"}>
      <NotificationList/>
      <CountButton/>
      <CountButton2/>
      <Accommodate/>
      <Calculator/>
      <br/>
      <Events/>
      <br/>
      <ConfirmButton/>
    </div>
  );
}

export default App5;

0개의 댓글