1. INTRO

안녕하세요, 새해 복 많이 받으세요. 오늘은 2025.01.02 새해 첫 수업입니다. 2024.12.31 수업에 이어서 이벤트 핸들링 예제를 실습했습니다. 예제를 함수형 컴포넌트, 클래스형 컴포넌트로 나눠서 실행해보고, 축약해보면서 문법에 익숙해지는 시간이었습니다.

2. 이벤트 핸들링

리액트에서 이벤트 사용 시 주의 사항

>>> 이벤트 이름은 카멜 표현법을 사용

>>> 이벤트 핸들러로 자바스크립트 코드를 사용할 수 없고, 함수 형태로 전달

>>> 이벤트는 DOM 요소에만 설정 가능

3. 리액트에서 처리할 수 있는 이벤트 종류

참고 사이트
https://ko.reactjs.org/docs/handling-events.html
https://reactjs.org/docs/events.html

4. LAB

import { useState } from "react";

function Title({ title }) {
  return (
    <p>현재 카운트는 {title}입니다.</p>
  );
}

function Todo() {
  const [count, setCount] = useState(0);
  return (
    <>
      <Title title={count}></Title>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </>
  );
}

function App() {
  return (
    <Todo></Todo>
  );
}
export default App;

5. 이벤트 핸들링 예제 (클래스형 컴포넌트)

- src 디렉터리에 EventPractice.js 파일을 생성

- 1) App.js 파일에 EventPractice 클래스형 컴포넌트를 추가

import EventPractice from "./EventPractice";

function App() {
  return (
    <EventPractice />
  );
}
export default App;

- EventPractice 컴포넌트에 입력창을 추가하고, 입력값을 콘솔에 출력하도록 수정

onChange={e => console.log(e.target.value)}

e.target.value 가 입력 받는 값을 의미한다.

- state 변수를 추가하고, 입력 내용을 해당 변수에 설정

\<button onClick={() => this.setState({ message: "" })}>삭제\

- 버튼을 추가하고, 해당 버튼을 클릭하면 입력값과 입력 내용을 제거하도록 수정

입력받은 input type에도 제거 내용을 적용하려면 state 변수값을 연결시켜줘야한다.

value={this.state.message} ⇐ 해당 라인이 없으면 삭제 버튼을 클릭해도 입력창 내용은 삭제되지 않음

위와 같이 state 변수와 파란 부분 문법이 같이 묶여 다녀야 함.

- 이벤트 핸들러 함수를 미리 정의해서 전달하는 방식으로 변경

- input 창이 여러 개인 경우

- 이벤트 핸들러를 하나로 통합

계산된 속성명을 이용해서 이벤트가 발생한 입력창의 이름(name)에 해당하는 상태변수의 값을 변경한다.

handlerChange = e => {
// 계산된 속성명
this.setState({ [e.target.name]: e.target.value });
};

- 하나의 요소에 여러 이벤트를 추가 ⇒ 메시지(message) 입력창에 엔터 키를 눌렀을 때 삭제 버튼을 클릭한 것과 동일한 형태로 처리하도록 수정

아래의 코드를 추가한다.

handlerKeyUp = e => {
if (e.key === "Enter") {
this.handlerClick();
}
};
\

최종 코드는 아래와 같다.

import { Component } from "react";

class EventPractice extends Component {
    state = {
        message: "",
        username: ""
    };

    /*
    handlerChangeMessage = e => {
        console.log(e.target.value);
        this.setState({ message: e.target.value });
    };

    handlerChangeUsername = e => {
        console.log(e.target.value);
        this.setState({ username: e.target.value });
    };
    */

    handlerChange = e => {
        // 계산된 속성명
        this.setState({ [e.target.name]: e.target.value });
    };

    handlerClick = () => {
        this.setState({ message: "", username: "" });
    };

    handlerKeyUp = e => {
        if (e.key === "Enter") {
            this.handlerClick();
        }
    };

    render() {
        return (
            <div>
                <h1>이벤트 연습</h1>
                <input type="text" name="message" placeholder="입력해 보세요."
                    value={this.state.message}
                    onChange={this.handlerChange} onKeyUp={this.handlerKeyUp} />
                <input type="text" name="username" placeholder="입력해 보세요."
                    value={this.state.username}
                    onChange={this.handlerChange} />
                <h2>message: {this.state.message}</h2>
                <h2>username: {this.state.username}</h2>
                <button onClick={this.handlerClick}>삭제</button>
            </div>
        );
    }
}

export default EventPractice;

6. 이벤트 핸들링 예제 (함수형 컴포넌트)

- 이벤트 핸들러를 각각 구현한 경우

- 하나의 이벤트 핸들러로 통합

const handlerChange = e => {
    if (e.target.name === "message") {
        setMessage(e.target.value);
    } else if (e.target.name === "username") {
        setUsername(e.target.value);
    }
};
<input type="text" name="message" placeholder="입력해 보세요."
                value={message}
                onChange={handlerChange} onKeyUp={handlerKeyUp} />

- 입력창의 이름을 관리하는 상태 변수를 추가

const [form, setForm] = useState({ message: "", username: "" });

// 객체 비구조화
const { message, username } = form;

const handlerChange = e => {
// 전개 연산자를 이용해서 form 객체의 사본을 만들고, 그 사본에 변경을 반영
const newForm = { ...form, [e.target.name]: e.target.value };
// 세터 함수를 이용해서 사본을 저장
setForm(newForm);
};

const handlerClick = () => {
const handlerKeyUp = e => {
if (e.key === "Enter") {
handlerClick();
}

최종 코드는 아래와 같다.

import { useState } from "react";

function EventPractice() {
    /*
    const [message, setMessage] = useState("");
    const [username, setUsername] = useState("");
    */
    const [form, setForm] = useState({ message: "", username: "" });

    // 객체 비구조화 
    const { message, username } = form;

    const handlerChange = e => {
        /*
        if (e.target.name === "message") {
            setMessage(e.target.value);
        } else if (e.target.name === "username") {
            setUsername(e.target.value);
        }
        */
        // 전개 연산자를 이용해서 form 객체의 사본을 만들고, 그 사본에 변경을 반영
        const newForm = { ...form, [e.target.name]: e.target.value };
        // 세터 함수를 이용해서 사본을 저장
        setForm(newForm);
    };

    const handlerClick = () => {
        /*
        setMessage("");
        setUsername("");
        */
        const newForm = { message: "", username: "" };
        setForm(newForm);
    };

    const handlerKeyUp = e => {
        if (e.key === "Enter") {
            handlerClick();
        }
    };

    return (
        <div>
            <h1>이벤트 연습</h1>
            <input type="text" name="message" placeholder="입력해 보세요."
                value={message}
                onChange={handlerChange} onKeyUp={handlerKeyUp} />
            <input type="text" name="username" placeholder="입력해 보세요."
                value={username}
                onChange={handlerChange} />
            <h2>message: {message}</h2>
            <h2>username: {username}</h2>
            <button onClick={handlerClick}>삭제</button>
        </div>
    );

}

export default EventPractice;

7. 컴포넌트 반복

정렬되지 않은 목록을 출력하는 컴포넌트를 생성한다.

- App.js에 IterationSample 컴포넌트 추가

import IterationSample from "./IterationSample";

function App() {
  return (
    <IterationSample />
  );
}
export default App;

- IterationSample.js 파일을 생성

- IterationSample 컴포넌트를 항목을 데이터로 선언하고 map 함수를 이용해서 출력하도록 개선

    const words = ["봄", "여름", "가을", "겨울"];
    const wordList = words.map(word => \<li>{word}\</li>);
    return \<ul>{wordList}\</ul>;
};
export default IterationSample;

아래와 같은 warning 발생.

- 배열의 인덱스를 이용해서 key props를 설정하여 문제 해결 가능

문자열을 데이터로 가지는 배열을 선언.

const IterationSample = () => {
    const words = ["봄", "여름", "가을", "겨울"];
    const wordList = words.map((word, index) => \<li key={index}>{word}</li>);
    return \<ul>{wordList}\</ul>;
};
export default IterationSample;

- 데이터 자체에 식별자(id)를 추가하여 문제 해결 가능

키값을 갖는 객체의 배열을 갖게 함.

const IterationSample = () => {
    const words = [
        { id: 1, word: "봄" },
        { id: 2, word: "여름" },
        { id: 3, word: "가을" },
        { id: 4, word: "겨울" }
    ];
    const wordList = words.map(item => <li key={item.id}>{item.word}</li>);
    return <ul>{wordList}</ul>;
};

export default IterationSample;

- 리스트 데이터의 항목을 동적으로 추가할 수 있도록 수정

목록 데이터를 관리하는 변수를 상태변수로 변경하고, 항목을 입력하는 입력창과 버튼을 추가


입력창에 내용을 입력했을 때 상태변수의 값을 변경하는 이벤트 핸들러 추가

추가 버튼을 클릭했을 때 동작을 처리할 이벤트 핸들러 추가

리스트 항목을 더블클릭하면 선택한 항목을 삭제하도록 기능 추가



최종 코드는 아래와 같다.

import { useState } from "react";

const IterationSample = () => {
    // 목록 데이터를 관리하는 상태변수
    const [words, setWords] = useState([
        { id: 1, word: "봄" },
        { id: 2, word: "여름" },
        { id: 3, word: "가을" },
        { id: 4, word: "겨울" }
    ]);
    const wordList = words.map(item => <li key={item.id} onDoubleClick={() => handleDoubleClick(item.id)}>{item.word}</li>);

    // 입력창 내용을 관리하는 상태변수
    const [inputText, setInputText] = useState("");

    // id를 관리하는 상태변수
    const [nextId, setNextId] = useState(5);

    // 이벤트 핸들러
    const handleChange = e => setInputText(e.target.value);
    const handleAddItem = () => {
        // const newWords = [ ...words, { id: nextId, word: inputText } ];
        const newWords = words.concat({ id: nextId, word: inputText });
        setWords(newWords);

        setNextId(nextId + 1);
        setInputText("");
    };

    const handleDoubleClick = id => {
        const newWords = words.filter(item => item.id !== id);
        setWords(newWords);
    };

    return (
        <>
            <input type="text" value={inputText} onChange={handleChange} />
            <button onClick={handleAddItem}>추가</button>
            <ul>{wordList}</ul>
        </>
    );
};

export default IterationSample;

8. state 내리기 / 끌어올리기

- state 내리기

부모 컴포넌트의 상태변수를 자식 컴포넌트의 props로 전달
부모 컴포넌트의 상태변수가 변경되면 부모 컴포넌트, 자식 컴포넌트 모두 다시 렌더링된다.

App.js

import { useState } from "react";


function ChildB({ value }) {
  return (
    <p>{value * 3}</p>
  )
};


function ChildA({ count }) {
  return (
    <p>{count * 2}</p>
  )
};


function Parent() {
  const [count, setCount] = useState(0);


  return (
    <>
      <input type="number" value={count} onChange={e => setCount(e.target.value)} />
      <ChildA count={count} />
      <ChildB value={count} />
    </>
  )
}


function App() {
  return (
    <Parent />
  );
}
export default App;

- state 끌어 올리기

자식 컴포넌트의 변경 사항을 부모 컴포넌트로 전달하는 방법.
부모 컴포넌트에서 정의한 함수를 자식 컴포넌트의 props 변수로 전달, 해당 함수를 자식 컴포넌트에서 이벤트 핸들러로 지정

App.js

import { useState } from "react";


function Child({ increment }) {
  return <button onClick={increment}>하나증가</button>;
};


function Parent() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  return (
    <>
      <h1>{count}</h1>
      <Child increment={increment} />
    </>
  )
}


function App() {
  return (
    <Parent />
  );
}
export default App;

LAB


최종 소스는 아래와 같다.

import { useState } from "react";


function Child({ addCnt, subCnt }) {
  return (
    <>
     <button onClick={addCnt}>+1</button>
     <button onClick={subCnt}>-1</button>
    </>
    
  );
};

function Parent() {
  const [count, setCount] = useState(0);
  const addCnt = () => setCount(count + 1);
  const subCnt = () => setCount(count - 1);
  const resetCnt = () => setCount(0);

  return (
    <>
      <div>{count}</div>
      <button onClick={resetCnt}>Reset</button>
      <Child addCnt={addCnt} subCnt={subCnt}/>
    </>
  )
}


function App() {
  return (
    <Parent />
  );
}
export default App;

LAB

import { useState } from "react";

function Controller({ plusOne, minusOne }) {
  return (
    <>
      <button onClick={plusOne}> +1 </button>
      <button onClick={minusOne}> -1 </button>
    </>
  );
}

function Display({ count }) {
  return (
    <h1>{count}</h1>
  );
}

function Parent() {
  const [count, setCount] = useState(0);
  const reset = () => setCount(0);
  const plusOne = () => setCount(count + 1);
  const minusOne = () => setCount(count - 1);
  return (
    <>
      <button onClick={reset}>Reset</button>
      <Controller plusOne={plusOne} minusOne={minusOne} />
      <Display count={count} />
    </>
  );
}

function App() {
  return (
    <Parent />
  );
}
export default App;

화씨, 섭씨 변환 및 물 끓음 표시

import { useState } from "react";

function BoillingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

// 화씨, 섭씨를 변경하기 전 입력값 검증과 변경 후 일정한 형식으로 반올림하는 로직을 공통통 적용
function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return "";
  }

  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

const scaleName = {
  c: "섭씨",
  f: "화씨"
};

function Temperature({ scale, changeTemperature, temperature }) {
  // 변경된 내용을 부모 컴포넌트로 전달하므로, 상태 변수를 유지할 필요가 없음 
  // const [temperature, setTemperature] = useState(0);
  // const handleChange = e => setTemperature(e.target.value);
  const handleChange = e => changeTemperature(e.target.value);

  return (
    <fieldset>
      <legend>온도를 입력하세요. (단위: {scaleName[scale]}) </legend>
      <input value={temperature} onChange={handleChange} />
    </fieldset>
  );
}

function Calculator() {
  const [temperature, setTemperature] = useState("");
  const [scale, setScale] = useState("c");

  // 화씨 온도가 입력된 경우 호출될 함수
  const changeFahrenheit = t => {
    setTemperature(t);
    setScale("f");
  };

  // 섭씨 온도가 입력된 경우 호출될 함수
  const changeCelsius = t => {
    setTemperature(t);
    setScale("c");
  };


  const celsius = scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;

  return (
    <>
      <Temperature scale="c" changeTemperature={changeCelsius} temperature={celsius} />
      <Temperature scale="f" changeTemperature={changeFahrenheit} temperature={fahrenheit} />
      <BoillingVerdict celsius={celsius} />
    </>
  );
}

function App() {
  return (
    <>
      <Calculator />
    </>
  );
}
export default App;

OUTRO

수업 중 어렵다고 느껴질 쯤 강사님께서 "이 부분이 잘 이해되길 바랍니다" , "갑자기 너무 어려워졌나요?" 라고 하셔서 신기했다. 그러다가 긴장감이 확 풀리는 시간이 종종 찾아오는데 집중을 잘 할 수 있는 방법을 찾아야할 것 같다.

profile
지니니

0개의 댓글