내가 한 노력들

[ React ] setState()를 사용해야하는 이유, 이벤트 전달하기 본문

IT 공부/Javascript

[ React ] setState()를 사용해야하는 이유, 이벤트 전달하기

JONGI-N CHOI 2022. 1. 14. 12:00

전 포스팅에서는 props와 state를 이용해서 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 것을 해봤습니다.

 

그러면, 하위 컴포넌트에서 상위 컴포넌트에 영향을 주는 것은 불가능한가?? 

이벤트를 발생시켜 영향을 주는 것이 가능합니다. 

 

list 목록을 만들어서, 클릭을 할 때 해당 리스트의 내용을 화면에 보여주는 이벤트를 만들어 볼려고 합니다.

우선 list를 만들어 보겠습니다.


리스트 컴포넌트 추가하기

app.js

this.state = { 
      header: {title: 'WEB', subject: 'WORLD WIDE WEB'},
      contents: [
        {id: 1, title: "HTML", desc: "HTML IS ..."},
        {id: 2, title: "CSS", desc: "CSS IS ..."},
        {id: 3, title: "JAVASCRIPT", desc: "JAVASCRIPT IS ..."}
      ]
    };

app.js의 state에 list에서 사용될 title과 description의 데이터를 추가합니다. 

import List from './components/List';

...생략

<div>
    <Header title={this.state.header.title} subject={this.state.header.subject}/>
    <List content={this.state.contents} />
</div>

또한 위의 코드를 추가해줍니다. 새로운 List 컴포넌트 추가를 위해서 

 

List.js

function TOC(props) {
  let lists = [];
  let data = props.content;
  for (let i = 0; i < data.length; i++) {
    lists.push(
    <li key={data[i].id}>
      <a href={"/content/"+data[i].id}>{data[i].title}</a>
    </li>);
   }
  }
   return (
     <nav>
       <ul>        
         {lists}
       </ul>
     </nav>
   );
 }

export default TOC;

List 컴포넌트를 추가합니다. 

 

let lists = [];
   let data = props.content;
   for (let i = 0; i < data.length; i++) {
     lists.push(
       <li key={data[i].id}>
         <a href={"/content/"+data[i].id}>{data[i].title}</a>
       </li>);
   }

이 부분은, App.js에서 보낸 데이터를 가지고 list로 만들어주는 코드입니다. 

여기서 중요한 점은 li태그 안에 key라는 속성이 추가되어있다는 점입니다. 

key를 추가하지 않으면, 위와 같이 콘솔에서 에러가 발생하는 것을 확인할 수 있습니다. 

 

브라우저를 확인하면, 정상적으로 list가 추가된 것을 확인할 수 있습니다. 


컨텐츠 컴포넌트 추가하기

ReadContent.js

function ReadContent(props) {
  return (
    <article>
      <h3>{ props.title }</h3>
      <div>
        { props.desc }
      </div>
    </article>
  );
}

export default ReadContent;

content를 표시하기 위한, component를 추가 

App.js

render() {    
    let _title, _desc = null;
    let contents = this.state.contents;
    let selected_content_id = this.state.selected_content_id;
    for (let i = 0; i < contents.length; i++) {
      if (contents[i].id === selected_content_id) {
        _title = contents[i].title;
        _desc   = contents[i].desc;
      }
    }
    return (
      <div>
        <Header title={this.state.header.title} subject={this.state.header.subject}/>
        <TOC 
          content={this.state.contents} 
          onChangePage = {this.onChangePage} 
        />
        <ReadContent title={_title} desc={_desc}/>
      </div>
    );
}

새롭게 추가한 content component를 추가하고, selected_content_id 값에 따라서 컨텐츠 값을 변경하기 위한 코드를 추가했다. 

 

let _title, _desc = null;
let contents = this.state.contents;
let selected_content_id = this.state.selected_content_id;
for (let i = 0; i < contents.length; i++) {
  if (contents[i].id === selected_content_id) {
    _title = contents[i].title;
    _desc   = contents[i].desc;
  }
}

위의 코드를 따로 잘라서 확인해보면, 

contents에 있는 값을 for문으로 돌려서 selected_content_id 값과  일치하는 id 값을 가진 content의 title과 desc값을 저장해서, ReadContent 컴포넌트의 props로 보낸다. 


이벤트 생성

 

이젠 리스트를 클릭했을 때 이벤트를 추가해보겠습니다. 

<a 
    href={"/content/"+data[i].id}
    onClick={function() {      
      alert('이벤트');
    }}
>{data[i].title}</a>

원래 기존의 html에서는 onclick 속성을 통해서 javascript의 함수를 실행시킬 수 있었는데, 

React에서 사용되는 태그들은 html태그와 비슷하지만 html태그와 다릅니다. 

JSX라는 것을 이용하기 때문에, 문법이 조금씩 다릅니다. 

그중에서 onclick대신 onClick이라는 속성을 사용합니다. 

 

위의 코드는 a 태그가 클릭이 되었을 때, alert를 표시하는 코드입니다. 

 

실제로 브라우저에서 리스트를 클릭해보면 정상적으로 동작을 합니다. 

 

그런데 위의 코드에서의 문제점은, a태그이기 때문에 클릭하고 난뒤에 페이지가 리로드 된다는 점입니다. 

이것은 React를 사용하는 의미가 없습니다. SPA로 설계하기 위해서는 리로드 되는 것을 막을 필요가 있습니다. 

 

<a 
    href={"/content/"+data[i].id}
    onClick={function(e) {      
      alert('이벤트');
      e.preventDefault();
    }}
>{data[i].title}</a>

그 방법이, event인수를 사용하는 것입니다. 

javascript에서는 이벤트 함수가 실행될 때 event 객체를 인수로 받는데, event에는 많은 내용과 함수가 내제되어있는데 

그중 하나가 preventDefault()함수입니다. 

이 함수는 기존의 태그의 기능을 막는 기능을 합니다 .

 

즉 a 태그는 href속성에 있는 URL로 화면을 리로드 시키는 것인데 이 기능을 막는 것 입니다. 

다시 브라우저에서 실행해보면, 화면이 리로드되는 문제가 해결된 것을 확인할 수 있습니다. 

 

그러면, 이번엔 상위 컴포넌트에서 함수를 생성해야합니다. 

App.js

onChangePage(id) {
	alert(id);
}

위와 같은 함수를 추가합니다. 

<List 
  content={this.state.contents} 
  onChangePage = {this.onChangePage.bind(this)} 
/>

그리고, List 컴포넌트에 onChangePage라는 속성에 위에서 생성한 onChangePage 함수를 전달합니다. 

이때 그냥 함수만 전달하는 것이아니라 bind(this)를 붙혀주는  것을 볼 수 있는데, 이 이유는 Javascript에서 클래스의 메소드는 바인딩 되어있지 않기 때문입니다. 그래서 this를 사용해도 undefined 가 뜨게 됩니다. 

그래서 bind()는 꼭 붙혀야 합니다. 

 

bind()를 사용안해도 되는 방법도 따로 있습니다. 

 

List.js

<a 
    href={"/content/"+data[i].id}
    onClick={function(id, e) {
      e.preventDefault();
      props.onChangePage(id);
    }.bind(this, data[i].id}
>{data[i].title}</a>

그럼, List 컴포넌트의 onClick메소드를 다시한번 새롭게 정의해보겠습니다. 

e.preventDefault()와, props로 보낸 onChangePage()함수를 선언했습니다. 

그리고, 여기도 bind를 통해서 this와 data[i].id값을 보내는 것을 볼 수 있는데, bind를 통해서 전달하는 인수 (this제외)는 function에서 인수로써 사용이 가능합니다. 

 

즉 function (id, e) { }.bind(this, id) 

는 bind에서 보낸 id가 fucntion의 첫번째 인수로써 id로 들어오는 것입니다. 

 

그래서 위의 코드를 짧게 설명하면, content의 id값을 onChagngePage 함수의 인수로써 전달하고 있는 것 입니다. 

 

App.js

this.state = {
  selected_content_id: 0,
  header: {title: 'WEB', subject: 'WORLD WIDE WEB'},
  contents: [
    {id: 1, title: "HTML", desc: "HTML IS ..."},
    {id: 2, title: "CSS", desc: "CSS IS ..."},
    {id: 3, title: "JAVASCRIPT", desc: "JAVASCRIPT IS ..."}
  ]
};

선택된, 컨텐츠의 본문 내용을 화면에 보여주기 위해서, selected_content_id 라는 변수를 state에 추가합니다. 

onChangePage(id) {
    this.setState({
        selected_content_id: id
    });  
}

onChangePage함수도 변경을 하는데, 여기서 또 새롭게 등장하는 것이 setState 함수 입니다. 

 

setState() 를 사용해야하는 이유

React에서 props는 읽기만 가능해서 변경이 불가능하고, state는 가능하다는 것은 앞서서 설명했지만, 

state값을 변경하기 위해서는 setState 함수를 사용해서 변경을 해야합니다. 

 

즉, this.state.selected_content_id = 2; 와 같은식으로 변경을 해서는 안된다는 것 입니다. 

그 이유는 무엇이냐면, 위와 같이 변경하게 되면 값은 변경이 되지만 React에서는 state값이 변경되었다는 사실을 인지하지 못합니다. 

 

이게 어떤 문제가 발생하는냐면, react는 props의 값이나 state값이 변경될때마다 render 함수를 실행합니다. 

그래야지만 변경된 데이터가 반영이되기 때문입니다. 

근데, 위와같이 직접적으로 접근하여 값을 변경하면 react에서는 변경된 사실을 인지하지 못해 render함수가 실행되지 않아 실시간으로 값이 반영되지 않습니다. 

 

그래서 setState 함수를 사용해서 state값을 변경해야합니다. 

 

 

이젠 브라우저에서, 리스트를 클릭해봅니다. 

왼쪽은 아무것도 선택하지 않았을 경우, selected_content_id값을 0으로 초기화 했기 때문에 0값인 것을 볼 수 있습니다.

오른쪽 사진은 첫번째 리스트를 클릭했을 경우입니다. 

selected_content_id값이 contentd의 id값이 1로 변경된 것을 확인할 수 있습니다. 

 

그리고, 어떻게 브라우저에서 react에 사용되는 state값을 실시간으로 확인할 수 있는지는 크롬의 확장자인 React Developer tools을 설치했기 때문입니다. 


List 값을 클릭할 때마다, 그 컨텐츠의 내용을 아래에서 확인할 수 있도록 설계되었습니다. 

 

 

이번 시간에는 state값을 변경하기 위해서 setState 함수를 사용해야하는 이유와 

하위 컴포넨트에서 이벤트를 발생해서 상위 컴포넌트의 state값을 변경하는 방법에 대해 실습했습니다.