[React] React - 이벤트

인프런 - 생활코딩 React 수업

React

Inflearn 생활코딩

이벤트에서 state 변경하기

state와 event 작업을 연결한다.
a 태그를 클릭했을 때 App 이라고 하는 컴포넌트의 mode 를 ‘welcome’으로 바꾸려고 한다.

e.preventDefault(); 아래에
this.state.mode = 'welcome';을 넣어본다.
이 값은 두 가지의 문제를 가지고 있기 때문에 오류가 뜬다.

이 onClick 되었을 때 호출되는 함수는 this의 값이 아무 값도 셋팅되어 있지 않았다.
그래서 state를 읽을 수 없다고 나온다.
그래서 앞으로 이벤트를 설정할 때, this가 정의되지 않았다고 하면
함수가 끝난 직후에 bind(this)를 넣어준다.

그러면 this는 우리의 컴포넌트가 된다.

//Subject.js < h1>< a href="/" onClick={function(e){ console.log(e); e.preventDefault(); this.state.mode = 'welcome'; }.bind(this)}>{this.state.subject.title}< /a>< /h1> {this.state.subject.sub} < /header> </pre> 하지만 이렇게 짜면 state의 값이 바뀐 것을 리액트는 모른다. 그래서 `this.state.mode = 'welcome';`이 아닌 `this.steState({mode:'welcome'})'`을 넣어준다.
//Subject.js < h1>< a href="/" onClick={function(e){ e.preventDefault(); this.setState({ mode:'welcome' }); }.bind(this)}>{this.state.subject.title}< /a>< /h1> {this.state.subject.sub} < /header> </pre>
이러면 WEB을 눌렀을 때 welcome과 Hello, React가 나타나게 된다.
###### 이벤트 bind 함수 이해하기 `bind();`
묶어주는 함수 render()가 호출될 때의 this는 render() 함수가 속해 있는 App 컴포넌트 자신 스스로를 가리킨다. onClick에서 실항하는 function에는 this값이 정의되지 않았다. 그래서 this를 bind 시켜주면 이 function에서 사용할 수 있게 되는 것.
bind로 인해서 괄호 안의 객체를 참조한다. 그래서 bind(this)하면 this는 App이라고 하는 컴포넌트 자체를 가리키는 객체를 function(e)안으로 주입해서 그 객체가 되도록 한다.
###### 이벤트 setState 함수 이해하기 `this.setState();` 함수를 통해 함수의 값을 변경해야 하는 이유 constructor()라는 생성자 함수에서는 this.state에서 수정하면 된다. 그러나 이미 컴포넌트가 생성된 이후에 동적으로 state의 값을 수정하기 위해서는 setState() 함수를 사용해야 한다. (`this.state.mode = 'welcome` 처럼 해서는 안된다.)
`this.setState()` 함수에 값을 객체 형태로 주는 것을 통해 수정해야 한다.
리액트 입장에서는 `this.state.mode`처럼 바꾸면 바꿨는지 알 수가 없다. 그래서 렌더링 해주지 않는다. 함수가 내부적으로 일하면서 state의 내용을 바꿔주는 것. state 값은 **setState()**로 바꿔줘야 한다!
###### 컴포넌트 이벤트 만들기 1 이벤트를 만들어서 태그나 컴포넌트를 사용하는 사람들이 이벤트를 사용할 수 있도록 생산자가 되어본다.
Subject 컴포넌트를 사용하는 사용자가 a 링크를 클릭했을 때 발생할 이벤트를 설치하고 싶다면 **onChange** 이벤트를 사용한다. 이 이벤트에 함수를 설치해두면 클릭할 때 설치한 이벤트의 함수를 실행한다.
//App.js

  < Subject 
    title={this.state.subject.title} 
    sub={this.state.subject.sub}
    onChangePage={function(){
      alert('hihihi');
    }.bind(this)}
    >
  < /Subject>

이 `onChangePage`라고 하는 이 함수는 props 형태로 Subject 컴포넌트에 전달된다. 그래서 Subject.js에서는 `onClick` 함수를 추가한다. 이 `onClick` 함수는 클릭할 때 실행되는 이벤트를 추가하면 되고, props로 전달 된 onChangePage의 함수를 넣어준다.
//Subject.js

class Subject extends Component {
    render() {
      return(
      < header>
          < h1>< a href="/" onClick={function(e){
            e.preventDefault();
            this.props.onChangePage();
          }.bind(this)}>{this.props.title}< /a>< /h1>
          {this.props.sub}
        < /header>
      );
    }
  }

  export default Subject;

Subject 컴포넌트에 onChangePage라고 하는 이벤트를 만들었다.
###### 컴포넌트 이벤트 만들기 2 지금까지 Subject 컴포넌트에 onChangePage 함수를 만들어 사용자에게 이벤트를 제공했다. 이번에는 글 목록을 클릭했을 때 App 컴포넌트의 state의 mode를 read 로 바꾸고 contents 라는 state가 본문에 나오게 하자. 글 목록은 TOC 컴포넌트로 TOC에 onChangePage 함수를 넣는다.
//App.js

< TOC onChangePage={function(){
          alert('hi');
        }.bind(this)} 
        data={this.state.contents}>< /TOC>

그리고 TOC 컴포넌트에 onClick 함수를 넣는다. onClick 함수에는 `e.preventDefault();`를 넣어준다. `this.setState({mode : 'read'});`로 state를 바꿔준다.

//App.js

< TOC onChangePage={function(){
          this.setState({mode: 'read'});
        }.bind(this)} 
        data={this.state.contents}>< /TOC>


###### 컴포넌트 이벤트 만들기 3 글 목록에서 클릭한 메뉴에 맞는 contents가 화면에 표시되게 만들자. App의 state에 selected_content_id 같은 이름을 주어서 현재 활성화 된 컨텐트를 표시한다.
constructor 생성자 함수가 실행될 떄, this.state에 selected_content_id를 주고 기본적으로 2가 선택되도록 한다.
  constructor(props){
    super(props);
    this.state = {
      mode: 'read', 
      selected_content_id:2, //기본적으로 2번 컨텐트가 선택

      subject:{title:'WEB', sub: 'world wide web!'},
      welcome:{title:'welcome', desc: 'Hello, React'},
      contents:[
        {id: 1, title: 'HTML', desc: 'HTML is information...'},
        {id: 2, title: 'CSS', desc: 'CSS is design...'},
        {id: 3, title: 'Javascript', desc: 'Javascript is interactive...'}
      ]
    }
  }

render()함수가 실행될 때 `this.state.mode`의 값이 read 일 때 state의 title과 desc를 받아오도록 조건문을 생성한다.
  render() {
    var _title, _desc = null;
    if(this.state.mode === 'welcome'){
      _title = this.state.welcome.title;
      _desc = this.state.welcome.desc;
    }else if(this.state.mode === 'read'){
      var i = 0;
      while(i < this.state.contents.length){
        var data = this.state.contents;
        if(data.id === this.state.contents.selected_content_id){
          _title = data.title;
          _desc = data.desc;
          break;
        }
        i = i+1;
      }
    }

    return (
      < div className="App">
        < Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}
        onChangePage={function(){
          this.setState({mode: 'welcome'});
        }.bind(this)}
        >
        < /Subject>
        < TOC onChangePage={function(){
          this.setState({mode: 'read'});
        }.bind(this)} 
        data={this.state.contents}>< /TOC>
        < Content title={_title} desc={_desc}></ Content>
      < /div>
    );

  }

이러면 TOC.js에 있는 props를 실행시키는 것을 통해서 App.js의 onChangePage함수를 실행시키게 되는데 실행할 때의 인자로 클릭한 항목의 id값을 보내주면 된다.
`data-id = {data[i].id}`로 data-id라는 속성을 만들고 속성값을 준다.
이벤트 객체는 target이라는 속성을 가진다. target 속성은 이벤트가 발생한 태그(지금은 a)를 가리킨다. 그래서 e.target은 이벤트가 소재한 태그(지금은 a를) 가리키고 이것을 통해 a 태그가 가진 id 값에 접속할 수 있다.
`data-`로 시작하는 속성은 **dataset**이라고 하는 특수한 곳을 통해 알 수 있다. 따라서 `e.target.dataset.id`로 id값을 알아낼 수 있다. 이 `e.target.dataset.id`는 onChangePage 의 인자로 App.js의 onChangePage 함수로 넘어가게 되고, 함수의 인자 값을 id라고 주었을 때 this.setState의 selected_content_id의 값을 넘어온 인자인 id 값으로 설정한다.
//TOC.js

render() {
    var lists = [];
    var data = this.props.data;
    var i = 0;
    while(i< data.length){
      lists.push(
      < li key={data[i].id}>
        < a 
        href={"/contents/"+data[i].id}
        data-id = {data[i].id}
        onClick={function(e){
          e.preventDefault();
          this.props.onChangePage(e.target.dataset.id);
        }.bind(this)}
        >{data[i].title}< /a>
      < /li>);
      i = i+1;
    }

//App.js
  < TOC onChangePage={function(id){
      this.setState({
        mode: 'read',
      selected_content_id:id
    });
    }.bind(this)} 
  data={this.state.contents}>< /TOC>

이러면 id값이 문자로 들어오기 때문에 숫자로 변환하는 Number로 감싸주면 숫자가 된다.
    < TOC onChangePage={function(id){
        this.setState({
          mode: 'read',
        selected_content_id:Number(id);
      });
      }.bind(this)} 
    data={this.state.contents}>< /TOC>

혹은 bind에 두 번째 인자를 주면 묶여있는 함수에 매개변수의 값으로 전달하기 때문에 기존 매개변수 e를 두 번째로 주고 id를 첫 번째 매개변수를 주어서 쓸 수도 있다.
render() {
      var lists = [];
      var data = this.props.data;
      var i = 0;
      while(i < data.length){
        lists.push(
        < li key={data[i].id}>
          < a 
          href={"/contents/"+data[i].id}
          onClick={function(id, e){
            e.preventDefault();
            this.props.onChangePage(e.target.dataset.id);
          }.bind(this, data[i].id)}
          >{data[i].title}< /a>
        < /li>);
        i = i+1;
      }

Made with by Álvaro