본문 바로가기

학습노트/React

[React] (9) 이벤트 state, props, event 만들기, render function, bind 함수, setState

우리의 어플리케이션을 역동적으로 만들어주는 기술이 이벤트다. state, props, event 3개가 어울어져 만들어진다. react는 props나 state가 바뀌면 render 함수가 다시 호출된다. 따라서 그 render에 포함된 모든 것들이 다시 호출된다. 

 

 

App.js

import React, { Component } from 'react';
import TOC from "./components/TOC"
import Content from "./components/Content"
import Subject from "./components/Subject"
import './App.css';

class App extends Component {
// render 함수보다 먼저 실행이되면서 초기화시켜주고 싶은 것은 constructor안에다가 짜라
// 부모
  constructor(props) {
    super(props);
    // 초기화 끝내고 state를 초기화 시킨다.
    this.state = {
      mode: "welcome", 
      subject: {title: "Web", sub: "World Wide Web!"},
      welcome: {title:"Welcome", decs:"Hello, React!!!"},
      contents:[
        {id: 1, title: 'HTML', desc: 'HTML is TT'},
        {id: 2, title: 'CSS', desc: 'CSS is AA'},
        {id: 3, title: 'JAVASCRIPT', desc: 'JAVASCRIPT is Hi'}
      ]
    }
  }

  render() {
    console.log('App render')
    let _title, _desc = null;
    if(this.state.mode === "welcome") {
      _title = this.state.welcome.title;
      _desc = this.state.welcome.desc;
    } else if(this.state.mode === "read") {
      _title = this.state.contents[0].title;
      _desc = this.state.contents[0].desc;
    }
    return (
    <div className="App">
      <Subject 
        title = {this.state.subject.title} 
        sub = {this.state.subject.sub}>
      </Subject>
      {/* 자식 */}
      <TOC data={this.state.contents}></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>
    );
  }
}

export default App;

  mode값으로 변경할 수 있는 값을 줘서 welcome이면 state.welcome을 참조하고 read면 state.contents[0]을 참조하게 한다.

 

이제 우리는 subject.title인 web을 눌렀을 때 App state의 mode 값을 변경 가능하게 만들고 싶은데 그러기 위해서는 어려운 과정을 거쳐야한다. 이유는 App의 하위에 있는 subject태그를 이용해서 상위에 있는 것을 바꾸기가 힘들기 때문이다. 그래서 우리는 아직 초보이니까 content.js에 있는 태그들을 옮겨서 실행하기로 하였다. 그리고 그곳에 href태그를 주고 누를 수 있게한다.

 

class App extends Component {
// render 함수보다 먼저 실행이되면서 초기화시켜주고 싶은 것은 constructor안에다가 짜라
// 부모
  constructor(props) {
    super(props);
    // 초기화 끝내고 state를 초기화 시킨다.
    this.state = {
      mode: "welcome", 
      subject: {title: "Web", sub: "World Wide Web!"},
      welcome: {title:"Welcome", decs:"Hello, React!!!"},
      contents:[
        {id: 1, title: 'HTML', desc: 'HTML is TT'},
        {id: 2, title: 'CSS', desc: 'CSS is AA'},
        {id: 3, title: 'JAVASCRIPT', desc: 'JAVASCRIPT is Hi'}
      ]
    }
  }

  render() {
    console.log('App render')
    let _title, _desc = null;
    if(this.state.mode === "welcome") {
      _title = this.state.welcome.title;
      _desc = this.state.welcome.desc;
    } else if(this.state.mode === "read") {
      _title = this.state.contents[0].title;
      _desc = this.state.contents[0].desc;
    }    
    return (
    <div className="App">
      {/* <Subject 
        title = {this.state.subject.title} 
        sub = {this.state.subject.sub}>
      </Subject> */}
       <header>
        <h1><a href="/" onClick={function(e) {
          console.log(e);
          //기본적인 동작인 href="/" 을 막는다.
          e.preventDefault();
        }}>{this.state.subject.title}</a></h1>
        {this.state.subject.sub}
      </header>
      {/* 자식 */}
      <TOC data={this.state.contents}></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>
    );
  }
}

 

  e.preventDefault()의 경우 href="/"로 가는 것을 막아준다. 그래야 새로고침이 안된다.

 

  이제 mode를 변경하기 위해 이런 코드를 작성하면 error가 뜬다.

<div className="App">
      {/* <Subject 
        title = {this.state.subject.title} 
        sub = {this.state.subject.sub}>
      </Subject> */}
       <header>
        <h1><a href="/" onClick={function(e) {
          console.log(e);
          //기본적인 동작인 href="/" 을 막는다.
          e.preventDefault();
          this.state.mode = "welcome";
        }}>{this.state.subject.title}</a></h1>
        {this.state.subject.sub}
      </header>
      {/* 자식 */}
      <TOC data={this.state.contents}></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>

 

  이유는 onClick으로 실행한 함수 안에서 this를 찾지 못하기 때문이다. 그래서 그 함수 뒤에 .bind(this)를 넣어준다. 하지만 이렇게 해도 바뀌지 않는다. 이유는 this.state.mode로 바꿔주면 바뀐걸 모르기 때문이다. react가 모르게 바꿔주는 것이다.(아래 setState란에서 정확히 확인 가능) 그래서 react에서 말해주는데로 setState를 쓴다.

 

<div className="App">
      {/* <Subject 
        title = {this.state.subject.title} 
        sub = {this.state.subject.sub}>
      </Subject> */}
       <header>
        <h1><a href="/" onClick={function(e) {
          console.log(e);
          //기본적인 동작인 href="/" 을 막는다.
          e.preventDefault();
          // this.state.mode = "welcome";
          this.setState({
            mode: "welcome"
          });
        }.bind(this)}>{this.state.subject.title}</a></h1>
        {this.state.subject.sub}
      </header>
      {/* 자식 */}
      <TOC data={this.state.contents}></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>

 


 

bind라는 함수란?

  bind는 엮는다, 묶는다라는 뜻이다. 기본적으로 render()라는 함수가 호출될 때 this는 자신이 속한 component 자체를 가르킨다. component에서 this를 못찾을 때만 bind로 쓰면 된다. 왜 못찾는지는 egoing선생님도 모르신다고 하신다.

 

 

bind() 의 기본원리

let obj = { name: 'ukcasso' };

function bindTest() {
	console.log(this.name);
}

bindTest(); // 당연히 undefined

// 하지만 실행되게 하기위해서 bind()라는 함수를 또 써준다.
let bindTest2 = bindTest.bind(obj);

bindTest2(); // ukcasso가 나온다.

  이 원리를 이용해서 위에서도 .bind(this)를 해준 것이다.

 


setState()라는 함수란?

  이미 component 생성이 끝난 후에는 this.setState라는 함수에 객체를 넣고 변경해주어야한다. 즉, state 값을 바꾸려면 setState()라는 것을 꼭 써서 바꿔야한다.

 


 

Event 만들기

  다시 Subject Component를 살려준다.

 

App.js

<div className="App">
      <Subject 
        title = {this.state.subject.title} 
        sub = {this.state.subject.sub}
        onChangePage = {function() {
          this.setState({ mode: "welcome"});
        }.bind(this)}
      >
      </Subject>
       {/* <header>
        <h1><a href="/" onClick={function(e) {
          console.log(e);
          //기본적인 동작인 href="/" 을 막는다.
          e.preventDefault();
          // this.state.mode = "welcome";
          this.setState({
            mode: "welcome"
          });
        }.bind(this)}>{this.state.subject.title}</a></h1>
        {this.state.subject.sub}
      </header> */}
      {/* 자식 */}
      <TOC data={this.state.contents}></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>

 

 

Subject.js

class Subject extends Component {
  render() {
    console.log('subject render')
    return (
      <header>
        <h1><a href="/" onClick={function(e) {
          e.preventDefault();

          // 얘가 onChangePage에 있는 함수다.
          this.props.onChangePage();
        }.bind(this)}>{this.props.title}</a></h1>
        {this.props.sub}
      </header>
    );
  }
}

App.js 내부에선 onChangePage가 Subject.js에서는 this.props.onChangePage로 받아야한다.

 

 

Component 이벤트 만들기( ex. TOC 바꾸기)

  App.js에서 이 부분을 바꿔준다.

<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

import React, { Component } from 'react';

class TOC extends Component {
  render() {
    let lists = [];
    let data = this.props.data;
    for(let i = 0; i < data.length; i++) {
      lists.push(
      <li key = {data[i].id}>
        <a 
          href={"/content/"+data[i].id}
          onClick = {function(e) {
            e.preventDefault();
            this.props.onChangePage();
          }.bind(this)}
        >{data[i].title}</a>
      </li>);
    }
    console.log('TOC render')
    return (
      <nav>
        <ul>
          {lists}
        </ul>
      </nav>
    ); 
  }
}

export default TOC;

 이렇게 해주면 TOC안에 내용을 클릭하면 mode가 read로 바뀐다.

 


 

이제 각 TOC안에 contents들이 각각에 맞게 변하는 것을 해야한다.

 

TOC.js

import React, { Component } from 'react';

class TOC extends Component {
  render() {
    let lists = [];
    let data = this.props.data;
    for(let i = 0; i < data.length; i++) {
      lists.push(
      <li key = {data[i].id}>
        <a 
          href={"/content/" + data[i].id}
          // data-id = {data[i].id} 이건 속성 값을 이용할 때 onChangePage(e.target.dataset.id)로 해야한다.
          
          // 여기 부분에 첫 번째로 받아오는 것이 bind에서 두번째 인자이다.
          onClick = {function(id, e) {
            e.preventDefault();
            this.props.onChangePage(id);
          }.bind(this, data[i].id)}
        >{data[i].title}</a>
      </li>);
    }
    console.log('TOC render')
    return (
      <nav>
        <ul>
          {lists}
        </ul>
      </nav>
    ); 
  }
}

export default TOC;

이것은 속성을 이용한 것이 아니라 bind에 두번째 인자 값을 활용하면 위에 function에서 한 값씩 밀어서 그 값을 받아올수 있다.

 

 

App.js

import React, { Component } from 'react';
import TOC from "./components/TOC"
import Content from "./components/Content"
import Subject from "./components/Subject"
import './App.css';

class App extends Component {
// render 함수보다 먼저 실행이되면서 초기화시켜주고 싶은 것은 constructor안에다가 짜라
// 부모
  constructor(props) {
    super(props);
    // 초기화 끝내고 state를 초기화 시킨다.
    this.state = {
      mode: "read",
      selected_content_id: 1,
      subject: {title: "Web", sub: "World Wide Web!"},
      welcome: {title:"Welcome", decs:"Hello, React!!!"},
      contents:[
        {id: 1, title: 'HTML', desc: 'HTML is for information'},
        {id: 2, title: 'CSS', desc: 'CSS is for design'},
        {id: 3, title: 'JAVASCRIPT', desc: 'JAVASCRIPT is for interactive'}
      ]
    }
  }

  render() {
    console.log('App render')
    let _title, _desc = null;
    if(this.state.mode === "welcome") {
      _title = this.state.welcome.title;
      _desc = this.state.welcome.desc;
    } else if(this.state.mode === "read") {
      for(let i = 0; this.state.contents.length; i++) {
        let data = this.state.contents[i];
        if(data.id === this.state.selected_content_id) {
          _title = data.title;
          _desc = data.desc;
          break;
        }
      }
    }    
    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(id) {
          this.setState({ 
            mode: "read",
            selected_content_id: Number(id)
          });
        }.bind(this)} 
        data = {this.state.contents}
      ></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>
    );
  }
}

export default App;

 

 


혹시나 console.log가 2번씩 나오는 경우에는 

 

index.js에서 <React.StrictMode> 태그를 지워주면 된다.