본문 바로가기

React

[React] state와 이벤트 핸들링

728x90

 


state

  리액트에서 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다
  state가 체인지 되면 변화를 감지하여 리렌더링을 수행한다 
  클래스 vs 함수형 컴포넌트에서 사용하는 방법이 다르다
  함수형 컴포넌트에서는 Hook 개념을 이용해 더욱 쉽게 사용 가능하다

 

리액트에는 두 가지 종류의 state가 있다

  1️⃣ 클래스형 컴포넌트가 지니고 있는 state

  2️⃣ 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state

 

    //1st
     let data = useState('초기값');
     console.log(data);   // 배열이기 때문에 다음과 같이 출력
     let a = data[0];  - 현재값
     let b = data[1];  - 함수

    //2nd
     let [data, setData] = useState('초기값');
     console.log(data);     //state값
     console.log(setData);  //state setter함수

 

   data = '1'; 👉 직접 값을 바꾸게 되면 값이 바뀌어서 나타나지만 나중에 문제 발생할 수 있음

 

⚠️ 다음과 같이 오류가 발생한다!

 

 

⭐  state값은 직접 변경하는 것이 불가능하고 변경하려면 setter 함수 이용해야 한다

  setData('변경할값');

 

 

const StateComponent = () => {

    let [data, setData] = useState('초기값');
  
    let func = () => setData("변경");
    let enter = () => setData("입장");
    let exit = () => setData("퇴장");
    // 값을 변경할 때마다 렌더링이 일어나고 있다!


    return (
        <>
            <button onClick={func}> 값 변경하기 </button>
            <button onClick={enter}> 입장 </button>
            <button onClick={exit}> 퇴장 </button>
        </>
    )
}

 

 

 

👩‍💻 함수 내부에 state는 여러 개 존재할 수 있다

const StateComponent = () => {

    let [data, setData] = useState('초기값');
 
    let func = () => setData("변경");
    let enter = () => setData("입장");
    let exit = () => setData("퇴장");
    // 값을 변경할 때마다 렌더링이 일어나고 있다!

    // state는 여러개일 수 있다
    let [color, setColor] = useState("pink");
    console.log(color);

    return (
        <>
            <h3 style={{ 'color': color }}> state값 : {data} </h3>
            <button onClick={func}> 값 변경하기 </button>
            <button onClick={enter}> 입장 </button>
            <button onClick={exit}> 퇴장 </button>

            <hr />
            <button onClick={() => setColor("red")}>붉은색</button>
            <button onClick={() => setColor("blue")}>푸른색</button>
            <button onClick={() => setColor("green")}>그 사이 3초 그 짧은색</button>
            
        </>
    )
}

export default StateComponent;

 

 

 

 

클래스형 컴포넌트의 state

 

< Counter.js >

 import { Component } from 'react';
 
 class Counter extends Component {
   constructor(props) {
    super(props);
    // state의 초깃값 설정하기
    this.state = {
      number: 0
    };
  }
  
 render( ){
   const { number } = this.state; // state를 조회할 때는 this.state로 조회한다
   return (
    <div>
      <h1>{number}</h1>
      <button
        // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다
        onClick={() => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다
          this.setState({ number: number + 1 });
        }}
      >
      +1
     </button>
    </div>
   );
  }
 }
 
 export default Counter;

 

컴포넌트에 state를 설정할 때는 constructor 메서드를 작성해 설정한다

 class Counter extends Component {
   constructor(props) {
    super(props);
    // state의 초깃값 설정하기
    this.state = {
      number: 0
    };
  }

 

 

render 함수에서 현재 state를 조회할 때는  this.state를 조회하면 된다

그리고 button 안에 onClick이라는 값을 props로 넣어주었는데, 이는 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 해준다 (이벤트 설정)

함수 내부에서 this.setState라는 함수를 사용하는데 이는 state값을 바꿀 수 있게 해준다

 render( ){
   const { number } = this.state; // state를 조회할 때는 this.state로 조회한다
   return (
    <div>
      <h1>{number}</h1>
      <button
        // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다
        onClick={() => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다
          this.setState({ number: number + 1 });
        }}
      >
      +1
     </button>
    </div>
   );
  }
 }

 

 

 

함수형 컴포넌트의 State

const StateComponentQ = () =>{
    
    // < 내 답안 >
    // let [number, setNumber] = useState("0");
    // let plus = () => setNumber(number+1);
    // let minus = () => setNumber(number-1);
    // let reset = () => setNumber(0);


    // return(
    //     <>
    //     <h3>카운트: {number}</h3>
    //     <button onClick={plus}>증가</button>
    //     <button onClick={minus}>감소</button>
    //     <button onClick={reset}>초기화</button>   
    //     </>
    // )


    // < 선생님 답안 >
    const [number, setNumber] = useState("0");  // number++로 state를 직접 수정하는 것은 불가능 
                                               
    return(
            <>
            <h3>카운트: {number}</h3>
            <button onClick={() => setNumber( number + 1 )}>증가</button>
            <button onClick={() => setNumber( number - 1 )}>감소</button>
            <button onClick={() => setNumber(0)}>초기화</button>   
            </>
    )
  }

export default StateComponentQ;

 

const [number, setNumber] = useState("0");   // number++로 state를 직접 수정하는 것은 불가능

 

🙋‍♀️ 그런데 const키워드로 선언해서 값을 직접 수정하는 것이 불가능한 거야, 아님 state라서 그런 거야?

👩‍💻 state라서! state는 값을 직접 수정하게 되면 자동으로 렌더링이 일어나지 않게 돼

       그래서 보통 state 값을 수정하지 못하도록 const 키워드로 변수를 선언하곤 하지!

 

 

이벤트 핸들링

 

 이벤트 규칙

 

     1️⃣ 이벤트의 이름은 전부 카멜 표기법으로 표현됩니다. onkeyup -> onKeyUp

     2️⃣ 이벤트를 전달할 때는 {함수} 형태로 사용합니다

 

 

인풋값 핸들링 해보기

 

     1️⃣인풋의 값이 변화되는 이벤트 onChange 연결

     2️⃣ 이벤트 안 첫번째 매개변수에서 event객체 활용하기 (e.target.속성값)

     3️⃣ setter를 이용해서 state변경하기

 

 

실습 전체 코드

 

userState 함수를 활용한 이벤트 핸들링

 

import { useState } from "react";


const EventComponent = () => {

    //name을 저장할 useState
     const [name, setName] = useState('');

    //이벤트 함수의 첫번째 매개변수에 이벤트에 대한 정보를 넣어준다
    const handleName = (e) =>{
        // console.log(e.target.value); 
        setName(e.target.value); //state체인지
    }
    
    const [topic, setTopic] = useState('');

    const handleTopic = (e) => {
        setTopic(e.target.value);
    }

    const handleClick = () => {
        alert(`${name}님의 주제는 ${topic}입니다`);  //state값
        setName(''); //인풋데이터 초기화
        setTopic(''); //인풋데이터 초기화
    }

    //엔터키의 처리
    const handlePress = (e) => {
        console.log(e);
        if(e.keyCode === 13){
            handleClick();
        }
    }



    return (
        <>
            <h3>리액트의 이벤트 핸들링 (인풋데이터)</h3>
            <input type="text" name="name" onChange={handleName} value={name}/>
            <div>체인지 된 결과: {name}</div>

            <input type="text" name="topic" onChange={handleTopic} onKeyUp={handlePress} value={topic}/>
            <div>체이지 된 결과 : {topic}</div>

            <button type="button" onClick={handleClick}>Click Me</button>
        </>
    )
}

export default EventComponent;

 

 

< 하나하나 코드를 분석해보자! >

 

name을 저장할 useState( );

  //name을 저장할 useState
  const [name, setName] = useState('');

 

 

input태그에서 값이 변경될 때(값이 입력되면) 다음의 hadleName함수가 실행된다

   //이벤트 함수의 첫번째 매개변수에 이벤트에 대한 정보를 넣어준다
    const handleName = (e) =>{
        // console.log(e.target.value); 
        setName(e.target.value); //state체인지
    }

 

 e.target.value 👉 사용자가 input태그에 입력한 값을 저장 ( state 체인지 )

 

 

    const [topic, setTopic] = useState('');

    const handleTopic = (e) => {
        setTopic(e.target.value);
    }

 

topic도 name과 같은 방식으로 설정 

 

    const handleClick = () => {
        alert(`${name}님의 주제는 ${topic}입니다`);  //state값
        setName(''); //인풋데이터 초기화
        setTopic(''); //인풋데이터 초기화
    }

 

버튼을 클릭했을 때, 경고창이 나타나면 input 태그에 작성된 값이 초기화되게 설정했다

 

    //엔터키의 처리
    const handlePress = (e) => {
        console.log(e);
        if(e.keyCode === 13){
            handleClick();
        }
    }

 

버튼을 클릭했을 때뿐 아니라 엔터키를 눌렀을 때에도 실행될 수 있도록 처리를 해주었는데, 이벤트는 onKeyUp을 사용하였고 엔터의 keyCode가13이기 때문에 e.keyCode가13일 때 handleClick함수가 실행되도록 해주었다        

 

 

    return (
        <>
            <h3>리액트의 이벤트 핸들링 (인풋데이터)</h3>
            <input type="text" name="name" onChange={handleName} value={name}/>
            <div>체인지 된 결과: {name}</div>

            <input type="text" name="topic" onChange={handleTopic} onKeyUp={handlePress} value={topic}/>
            <div>체이지 된 결과 : {topic}</div>

            <button type="button" onClick={handleClick}>Click Me</button>
        </>
    )

 


state를 객체로 관리하여 이벤트 핸들링 

 

import { useState } from "react";




const EventComponent2 = () =>{

    //state를 객체로 관리
    const [data, setData] = useState({name: '', topic: ''});

    //한 개의 state로 여러 개의 데이터 관리

    const handleChange = (e) =>{
        //객체 안에서 key를 바꾸는 방법["키"] : 값
        const copy = {...data, [e.target.name] :e.target.value}; //데이터 복사 👉 데이터 형식을 복사해서 데이터 내부의 키에 대한 값만 변경
        setData(copy); //state 변경
    }

    /*
       const handleTopic = (e) =>{
        //객체 안에서 key를 바꾸는 방법["키"] : 값

         console.log(e.target.name);

         const copy = {...data, ["topic"] :e.target.value}; //데이터 복사 👉 데이터 형식을 복사해서 데이터 내부의 키에 대한 값만 변경
         setData(copy);

         }
    */

    const handleClick = () =>{
        alert(`${data.name}님 할 일: ${data.topic}`)
        setData({name:'', topic:''}); //state초기화 (객체라서)
    }

    return (
        <>
            <h3>리액트 이벤트 핸들링(객체로)</h3>
        
            <input type="text" name="name" onChange={handleChange} value={data.name}/>
            <h5>결과: {data.name}</h5>
            <br/> <input type="text" name="topic" onChange={handleChange} value={data.topic}/>
            <h5>결과: {data.topic}</h5>
            <br/><button type="button" onClick={handleClick}>ClickMe</button>
        </>
    )

}

export default EventComponent2;

 

 

구체적으로 코드를 하나씩 뜯어봅시다!

 

state를 객체로 관리할 것이기 { key : value }로 작성 - 한 개의 state로 여러 개의 데이터를 관리할 수 있음! 

    //state를 객체로 관리
    const [data, setData] = useState({name: '', topic: ''});

    //한 개의 state로 여러 개의 데이터 관리

 

input태그의 value가 입력되었을 때 실행될 함수  : name에 대한 함수 

    const handleChange = (e) => {
        //객체 안에서 key를 바꾸는 방법["키"] : 값
        const copy = {...data, [e.target.name] :e.target.value}; //데이터 복사 👉 데이터 형식을 복사해서 데이터 내부의 키에 대한 값만 변경
        setData(copy); //state 변경
    }

 

데이터 형식은 똑같은데 key에 대한 값만이 바뀔 때 위와 같이 사용하면 된다

  

 

input태그의 value가 입력되었을 때 실행될 함수  : topic 에 대한 함수 

     const handleTopic = (e) = >{
     //객체 안에서 key를 바꾸는 방법["키"] : 값

     console.log(e.target.name);
     const copy = {...data, ["topic"] :e.target.value}; //데이터 복사 👉 데이터 형식을 복사해서 데이터 내부의 키에 대한 값만 변경
     setData(copy);
   }

 

name과 함수 기능이 동일하기 때문에 굳이 함수가 두 번 사용될 필요가없다 👉 그래서 하나의 함수로 해결     

 

 

   const handleClick = () => {
       alert(`${data.name}님 할 일: ${data.topic}`)
       setData({name:'', topic:''}); //state초기화 (객체라서)
    }

 

버튼을 클릭했을 때 경고창을 띄우고, input 태그에 사용자가 작성한 값을 초기화 시켜주는 함수인데, state를 객체로 구현했기에 초기화 역시 위와 같이 초기화시켜야 한다    

 

 

  return (
        <>
            <h3>리액트 이벤트 핸들링(객체로)</h3>
        
            <input type="text" name="name" onChange={handleChange} value={data.name}/>
            <h5>결과: {data.name}</h5>
            <br/> <input type="text" name="topic" onChange={handleChange} value={data.topic}/>
            <h5>결과: {data.topic}</h5>
            <br/><button type="button" onClick={handleClick}>ClickMe</button>
        </>
    )

 

name과 topic 모두 사용할 함수의 기능이 동일하기 때문에 handleChange라는 함수로 통일하여 메모리 낭비를 방지했다 

 

 


나와의 싸움 Time

 

1️⃣ 셀렉트 태그 이벤트 핸들링

import { useState } from "react";

const EventComponentQ = () =>{
    
    const [food, setFood] = useState("메뉴를 선택하셈");

    //셀렉트 태그에서는 option태그가 기본 value가 된다
    const change = (e) => {
        setFood(e.target.value);
        // console.log(e.target.value);
    }    

    return (
        <>
        <hr/>
        <h3>셀렉트 태그 핸들링(실습)</h3>
        <p>셀렉트 태그가 체인지 될 때 선택한 결과를 아래에 출력</p>
        <select onChange={change}>
            {/* <option value="">선택</option> */}
            <option>곱창</option>
            <option>닭발</option>
            <option>엽떡 오리지널</option>
            <option>엽떡 로제</option>
        </select>
        <h3>선택한 결과</h3>
        <p> {food}</p>
        </>
    )
}

export default EventComponentQ;

 

 

2️⃣ input 데이터 핸들링 - state를 객체로 관리 

import { useState } from "react";

const EventComponentQ2 = () =>{

    

    const [text, setText] = useState({input: '', result:''});

    const handleClick = () => {
        //INPUT은 공백, RESULT는 data로 변경
        const copy = {...text, ['result'] : text.input, ['input']: ''};
        //setText({input:'', result:text.input});
        setText(copy);
    }
    
    const handleChange = (e) =>{
        //INPUT은 사용자의 입력값으로, RESULT는 유지
        const copy = {...text, ['input'] : e.target.value};
        //setText({input:e.target.value, result:text.result});
        setText(copy);
    }

    const handlePress = (e) => {  //엔터키 처리
        console.log(e);
        if(e.keyCode === 13){
            handleClick();
        }
    }

    return(
        <>
            <hr/>
            <h3>인풋데이터 핸들링(실습)</h3>
            <p>클릭시 데이터는 공백으로, 결과는 인풋이 입력한 값으로 처리</p>
            <p>힌트는? 아마도 state는 두 개가 필요할듯?</p>
            
            <input type="text" name="name" onChange={handleChange} onKeyUp={handlePress} value={text.input}/>
            <button type="button" onClick={handleClick}> 추가하기 </button>

            <h3>결과</h3>
            <p>{text.result}</p>   
        </>
    )
}

export default EventComponentQ2;

 

 

2️⃣ input 데이터 핸들링 - 두 개의 state로 관리 

import { useState } from "react";

const EventComponentQ2_answer = () =>{

    const [data, setData] = useState('');      //인풋데이터
    const [result, setResult] = useState('');  //결과데이터

    
    const handleChange = (e) =>{
        setData(e.target.value); // 비동기적으로 변경 (기존 state값이 출력)
        //    console.log(data);  // 이전 값이 출력됨 (정상!)
    }
    
    const handleClick = (e) => {
       setResult(data); // 사용자가 입력한 값으로 변경
       setData('');
    }

    return(
        <>
            <hr/>
            <h3>인풋데이터 핸들링(실습)</h3>
            <p>클릭시 데이터는 공백으로, 결과는 인풋이 입력한 값으로 처리</p>
            <p>힌트는? 아마도 state는 두 개가 필요할듯?</p>
            
            <input type="text" name="name" onChange={handleChange} value={data}/>
            <button type="button" onClick={handleClick}>추가하기</button>

            <h3>결과</h3>
            <p>{result}</p>   
        </>
    )
}

export default EventComponentQ2_answer;