본문 바로가기

Study/React

Event & useState

Evnet

how to add event listener?  

바닐라 js에서 어플리케이션을 dynamic하게 만드는 가장 흔한 방법은 event listener를 등록하는 것이다. 

 

// Imperative Vanila JS
const deleteBtn = document.querySelector('.delete');

deleteBtn.addEventListener('click', ()=>{
  console.log('you clicked delete btn');
})

 

그러나 리액트는 바닐라 자바스크립트의 명령형이 아니라, 선언형(declarative) 접근방식을 이용해서 코드를 작성한다. 그래서 이벤트를 등록할 때 위의 예제처럼 직접 DOM 요소를 조작하는 것이 아니라, 그 이벤트를 컴포넌트의 property로 전달한다. 

 

const DeleteBtn = () => {
  const clickHandler = ()=> {
    console.log('clicked'); 
  }
  
  return (
    <button type='button' onClick={clickHandler}>
      delete
    </button>
  );
};

 

  1. 등록할 수 있는 모든 이벤트 property의 name은 on으로 시작한다. 
  2. 이벤트 property의 value는 그 이벤트가 발생했을 때 실행할 콜백 함수가 된다. 
    • conventional namimg callback function | event + handler
  3. 일반적으로 이벤트 property를 custom component에 전달할 수 없다(코드로 명시하면 이벤트 등록 가능). 

form event 

form | onSubmit

form이 제출되면(onSubmit 이벤트가 발생하면), 브라우저는 request를 action 속성에 등록된 서버에 전달하며 page를 refresh한다. 이러한 브라우저의 기본 기능을 제어하고 싶을 때에는 아래와 같이 코드를 작성한다. 

 

const NewExpenseForm = () => {
  const submitHandler = (e) => {
    e.preventDefault();
  };
  
  const clickHandler = () => {
  };

  return (
    <form action='#' onSubmit={submitHandler}>
      <!-- ... -->
      <button type='submit' onClick={clickHandler}>Add</button>
    </form>
  );
};

 

input | onChange

const NewExpenseForm = () => {
  const changeHandler = (e) => {
    console.log(e.target.value);
  };

  return (
    <form className='expense-form' action='#'>
      <label htmlFor='title'>Title</label>
      <input
        type='text'
        name='title'
        id='title'
        onChange={changeHandler}
      />
      <button type='submit'>Add</button>
    </form>
  );
};

 

  1. onChange | 모든 input type에 등록할 수 있으며, input의 value에 변화가 생겼을 때 이벤트가 발생한다. 
  2. vanila js에서 처럼, 이벤트가 발생하면 자동으로 이벤트 객체가 생성된다.
    • event.target.value 
  3. input에 전달된 값을 저장하고, 그 값을 렌더링해서 보여주어야 할 때에는 useState를 이용할 수 있다. 

State

why we need state? 

const Description = (props) => {
  let title= props.title;
  const clickHandler = () => {
    title = 'updated!'; 
  };

  return (
      <section className='item-description'>
        <h1 className='description-title' onClick={clickHandler}>{title}</h1>
        <p className='description-price'>{props.price}</p>
      </section>
  );
};

 

  1. 클릭 이벤트가 발생하면, clickHandler가 실행된다. => title 변수의 값이 'updated'로 바뀐다. 
  2. 그러나 위의 예제코드는 예상한 대로 동작하지 않는다. => 화면에 'updated'가 출력되지 않는다. 
  3. why? 
    • component는 JSX를 반환하는 함수이다. 
    • component 함수를 실행하는 것은 우리가 아니라, 리액트이다.
    • 리액트는 더이상 반환할 JSX 코드가 없을 때 까지 모든 component 함수를 실행한다. 
      • app이 시작될 때 index.js가 가장 먼저 실행된다.
      • 이 때 index의 <App /> 컴포넌트 함수를 실행하고, <App /> 컴포넌트의 자식 컴포넌트를 실행하고 ... (반복)
    • JSX 코드가 DOM element로 변환되어 브라우저에 렌더링된다. 
    • 리액트는 어플리케이션이 처음 렌더링 될 때에만 컴포넌트 함수를 실행한다. 즉, 컴포넌트 함수가 반복적으로 실행되지 않기 때문에 변화된 상태가 화면에 렌더링되지 않는 것이다. 
    • state는 컴포넌트 함수를 재실행함으로써 UI를 업데이트하기 위해 필요한 개념이다. 

how to use state? | useState

import { useState } from 'react';

const Description = (props) => {
  const [title, setTitle] = useState(props.title);
  
  const clickHandler = () => {
    setTitle('updated'); 
    console.log(title); // 처음 이벤트가 발생했을 때에는 updated가 아니라 초기값이 출력된다. 
  };

  return (
      <section className='item-description'>
        <h1 className='description-title' onClick={clickHandler}>{title}</h1>
        <p className='description-price'>{props.price}</p>
      </section>
  );
};

 

  1. re-rendering
    • 리액트는 컴포넌트 내부에서 등록한 이벤트가 발생하거나, 선언된 변수의 값이 변경된다고 해서 그 컴포넌트 함수를 다시 실행하지 않는다.
    • 대신 useState를 실행함으로써, state로 관리하는 특별한 변수를 만들 수 있다. 이 값이 변경되면 컴포넌트 함수를 재실행한다. 
    • 컴포넌트 함수가 재실행된다는 것은, 그 컴포넌트를 다시 렌더링 할 수 있다는 것을 의미한다. 즉 state를 이용해서 component를 조작하고, 최종적으로 화면에 렌더링 되는 DOM을 조작할 수 있다.   
      • 이 때 useState(init value)도 다시 실행되기 때문에, set 함수를 이용해서 업데이트한 state 값이 덮어씌워질 것 같지만, 그런 일은 발생하지 않는다. 
      • 리액트가 하는 일은 state를 관리하고, 그 state와 컴포넌트를 연결해 주는 것이다. 
      • state를 관리하는 프로세스에서 컴포넌트 함수가 최초로 실행될 때에만 초기값을 고려하기 때문에, 초기 값이 계속 덮어 씌워지는 일은 발생하지 않는다. 
      • 단, 컴포넌트가 dom에서 제거되었다가 다시 나타나는 경우는 컴포넌트 함수가 최초로 실행되는 때로 간주한다. 
  2. const [title, setTitle] = useState(props.title) 
    • useState 함수는 배열을 반환한다.
    • 배열의 첫번째 아이템은 인자로 전달받은 값을 초기값으로 갖는 특별한 변수가 된다.
    • 배열의 두번째 아이템은 해당 변수의 값을 업데이트하고, 값이 업데이트 되었을 때 re-rendering이 일어나도록 지시하는 함수이다. 
    • 배열의 첫번째, 두번째 아이템을 array destructuring을 이용해서 변수에 할당한다.  
    • useState 함수는 컴포넌트 함수의 바로 안쪽에서 호출해야 한다(must call directly inside of the function).
      • 함수 밖에서 호출하거나, 중첩된 컨텍스트에서 호출하면 안됨.
      • ??? 예외가 있긴 있음
  3. setTitle 
    • set 함수는 단순히 값을 재할당하는 것이 아니라, 값을 업데이트하고 컴포넌트 함수의 재실행(re-rendering)을 지시하는 함수이다. 따라서 title이라는 특별한 변수가 만들어지긴 했지만, title에 값을 재할당하는 것이 아니라 set 함수를 이용해서 값을 업데이트해야 한다. 
      • title 변수는 const로 선언되었기 때문에, 어차피 등호(=)를 이용해서 값을 재할당하는 것은 불가능하다. 
      • set함수가 title의 값을 업데이트 하는 방식도 등호를 이용하는 것은 아니다. 
    • 이 때 set 함수는 바로 값을 업데이트 하는 것이 아니라, 업데이트를 예약하는 함수이다(scheduling). => console에 업데이트한 값이 바로 출력되지 않는다. 
  4. instance based state | 각 컴포넌트는 자신만의 독립적인 state를 갖는다.
    • 어떤 컴포넌트가 여러 개 재사용 된다고 하더라도, 각 컴포넌트의 state는 독립적으로 관리된다.
    • 예를 들어, 어떤 Description 컴포넌트의 title을 클릭한다고 해서, 다른 모든 Description 컴포넌트의 title이 함께 업데이트되지는 않는다. => 컴포넌트 함수가 실행(렌더링)될 때 마다 console에 메세지를 출력하게 만들면, 이를 가시적으로 확인해 볼 수 있다. 

+) useState 함수는 React Hook의 하나이다. 

+) props와 state의 차이점 | state는 컴포넌트 안에서 관리되는 data이고, props는 컴포넌트 전달되는 data이다. props는 부모 컴포넌트의 state를 받을 수도 있다. 이 때 state를 직접 수정하는 것은 지양한다(shallow comparison 때문). 

scheduling

set 함수를 이용해서 state의 값을 업데이트 할 수 있다. 이 때 set 함수 자체가 state를 업데이트 하는것이 아니라 다음의 단계를 거친다.

 

  1. set 함수를 호출하면, 
  2. set 함수는 state 업데이트 함수를 호출하고, (schedule a state update)
  3. 이 함수가 state를 업데이트한다. 

stateful vs. stateless

  • stateful component | state를 가지고 있는 컴포넌트 
  • stateless component | props을 통해서 data를 받아오고, 단지 data를 출력하기만 하는 컴포넌트. presentational component라고 부르기도 한다. 

 

거의 모든 어플리케이션에서 stateless 컴포넌트가 stateful 컴포넌트보다 많다. 

'Study > React' 카테고리의 다른 글

Dynamic List  (0) 2022.06.17
prevState  (0) 2022.06.16
JSX  (0) 2022.06.16
Component & Props  (0) 2022.06.16
What is React?  (0) 2022.06.16