본문 바로가기
  • think normal
새로워지기/서른의 생활코딩

[생활코딩 따라가기] React 16 event 풀~패키지 챕터

by 청춘만화 2019. 5. 27.

https://www.youtube.com/watch?v=kviidk347nU&list=PLuHgQVnccGMCRv6f8H9K5Xwsdyg4sFSdi&index=19

 

수업의 목표 

TOC(목차)를 클릭하면 클릭한 항목에 해당 콘텐츠가 목록 아래 영역에 출력될 수 있도록 할 수 있다.

 

1. Subject의 콘텐츠를 변경하는 이벤트 구현하기 

1. React의 특징 이해하기 

1) state(또는 props) 값이 바뀌면 화면이 다시 그려진다.

- state(또는 props) 값이 바뀌면 그 state(또는 props)를 가지고 있는 컴포넌트의 render()가 다시 호출된다. 
- render() <-- 어떤 html을 그릴것인가를 결정한다.

2) app.js

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('app 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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[0].desc;
    }
    
    return (
      <div className="App">
         <Subject title={this.state.subject.title} sub={this.state.subject.sub}></Subject>
         <TOC data={this.state.content}></TOC>
         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

3) render()를 확인하기 위해 Content/TOC/Subject.js 의 render() 아래 콘솔로그를 넣어본다 

console.log('TOC render');   //TOC.js의 예

 

2. 클릭 이벤트로 구현하기 

디버그 모드에서 직접 수정하는 것이 아니라, 버튼을 클릭하면 state의 값 바꾸기 

1) 리액트는 html이 아니다! onClick

<a href ="" onClick={function(){ alert('hi');  }}>{this.state.subject.title}</a>

app.js (1)

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('app 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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[0].desc;
    }
    
    return (
      <div className="App">
        {/* <Subject title={this.state.subject.title} sub={this.state.subject.sub}></Subject> */}
        {/* 일단 가볍게 만들어보기 */}
        <header>  
          <h1><a href ="" onClick={function(){
            alert('hi'); //확인 버튼을 누르면, react와 다르게 리로드 된다. 
          }}>{this.state.subject.title}</a></h1>
          {this.state.subject.sub}
        </header>

         <TOC data={this.state.content}></TOC>
         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

        // 얼럿창의 '확인' 버튼을 누르면, react의 특성?에 반하게 페이지 전체가 리로드 된다.

 

2) preventDefault()

<a href ="" onClick={function(e){ e.preventDefault(); }}>{this.state.subject.title}</a>

app.js (2)

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('app 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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[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);
            //debugger;
            e.preventDefault();
          }}>{this.state.subject.title}</a></h1>
          {this.state.subject.sub}
        </header>

         <TOC data={this.state.content}></TOC>
         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

 

3. 클릭 이벤트를 통해 모드값 변경하기 

1) onClick={function(e){ ...여기에... }} 이벤트 함수 안에  state 값에 대한 코드(this.state.mode='welcome';)  추가

                               //  에러 Cannot read property 'state' of undefined <--  여기서의 this는 지정하는 값, 컴포넌트 가 없다.  

 

2) onClick={function(e){ ... this.state.mode='welcome'; ... }.bind(this)}  

                             // 에러는 안나는데 클릭에 대한 이벤트로 state가 변경되지는 않는다. 

3) onClick={function(e){ ... this.setState({ mode:'welcome' }); ... }.bind(this)}  

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('app 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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[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);
            e.preventDefault();
            this.setState({
              mode:'welcome'
            })
          }.bind(this)}>{this.state.subject.title}</a></h1>
          {this.state.subject.sub}
        </header>

         <TOC data={this.state.content}></TOC>
         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

4. bind() 함수 이해하기 

render(){ ...a... } 안에 있는 this 는 컴포넌트 자신을 가르키지만,
- 가설 1) render(){ ... return(...b...); } 안에 있는 this 는 undifined 이다.  // <-- 확인할 수 있는 방법이 있나? 
- 가설 2) render(){ ... return( function(){...c...} ); } 안에 있는 this 는 undifined 이다.   // <-- 지역-전역의 문제인가?

테스트를 위한 코드 (크롬 개발자 도구, http://localhost:3000)

//* 원하는 값 세팅 
var value = {name:'peter'}

//* this 출력 시도 
function thisValue(){
    console.log(this.name);
}

//* 결과 보기
thisValue();
// undefined


//* bind 함수에 원하는 값의 변수를 인자로 세팅
var bindValue = thisValue.bind(value);
// undefined

//* 함수를 답은 변수를 출력하여 결과보기 
bindValue();
// peter

function A(){ ... }.bind(인자) 로 작성하면 인자 값이 A()의 this가 된다. 

 

5.setState() 함수로 값을 지정/변경해야 하는 이유 

스테이트 값을 직접 바꾸지( this.state.mode='welcome' ) 않고  함수를 이용( this.setState( ) )하는 이유
-> 그냥 직접 바꾸게 되면 리액트가 바뀐 상황을 알지 못한다. 

 

6. 이벤트의 사용자에서 생산자로 거듭나기

임시로 사용한 <header> 태그를 지우고, 원래 사용하던 subject 컴포넌트 재사용하기 

1) 사용자가 선언한 EVENT 를 실행해보기

(1) subject 컴포넌트에 onChangePage 이벤트를 만들고 그 안에 함수를 설치 <-- 이벤트 공급자 되기 !

<Subject title={...} sub={...} onChangePage={ function(){ alert('Hi hi hi~'); }.bind(this)} </Subject>

app.js

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('this in render : ' , this);
    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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[0].desc;
    }
    
    return (
      <div className="App">

        <Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}

        onChangePage={function(){
          alert('Hi hi hi~');
        }.bind(this)}
        >
        </Subject>
       
        {/* <header>  
          <h1><a href ="" onClick={function(e){
            console.log(e);
            e.preventDefault();
            console.log('this in function : ' , this)
          }}>{this.state.subject.title}</a></h1>
          {this.state.subject.sub}
        </header> */}

         <TOC data={this.state.content}></TOC>
         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

 

(2) subject 컴포넌트에 있는onChangePage 이벤트 사용하기   <-- 이벤트 사용하기 !

<a href ="/" onClick={ function(e){ 
     e.preventDefault();                     
// 재로드 방지
     this.props.onChangePage
();       // 내가 생성한 이벤트(함수) 호출 
}.bind(this>

subject.js

import React, {Component} from 'react';

//컴포넌트(사용자정의 테그)를 만드는 코드영역
class Subject extends Component{
    //class 안에서는 function을 생략할 수 있다.
    render(){
      console.log('sub 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;
  

        // web을 클릭하자, 얼럿이 떳고, 확인을 클릭해도 리로드되지 않아, 페이지가 바뀌지 않는다. 

 

2) 최종화

(1) setState( { mode: ' value ' } )

app.js

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('this in render : ' , this);
    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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[0].desc;
    }
    
    return (
      <div className="App">

        <Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}

        onChangePage={function(){
          //this.state.mode = 'welcome';
          this.setState(
            {mode:'welcome'}
          );
        }.bind(this)}
        >
        </Subject>

         <TOC data={this.state.content}></TOC>
         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

(2) this.props.onChangePage()

<a href ="/" onClick={function(e){ e.preventDefault()this.props.onChangePage(); }.bind(this)}>

Subject.js

 

import React, {Component} from 'react';

//컴포넌트(사용자정의 테그)를 만드는 코드영역
class Subject extends Component{
    //class 안에서는 function을 생략할 수 있다.
    render(){
      console.log('sub 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;
  

(3) 결과 

 

 

2. TOC 리스트별 Content 내용 변경하기 

2. TOC에 이벤트 주고 받기 

1)  TOC 클릭 시 State mode를 'read'로 바꾸기 

(1) app.js

import React, {Component} from 'react';
import './App.css';

//분리된 컴포넌트들 import 
import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"

//*컴포넌트들이 '들어가는(나열되는)' 영역
class App extends Component{
  constructor(props){ //컴포넌트가 실행될때, render 보다 먼저 '초기화'하기 위해 선언, 그안에 초기화할 요소들을 넣는다.
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('this in render : ' , this);
    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'){
      _title=this.state.content[0].title;
      _desc=this.state.content[0].desc;
    }
    
    return (
      <div className="App">

        <Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}

        onChangePage={function(){
          //this.state.mode = 'welcome';
          this.setState(
            {mode:'welcome'}
          );
        }.bind(this)}
        >
        </Subject>

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

         <Content title={_title} desc={_desc}></Content>
      </div>
    );

  }
}

export default App;

(2) this.props = onChangePage

TOC.js

 

import React, {Component} from 'react';  //<-- Component class를 로딩하기위해 

//컴포넌트(사용자정의 테그)를 만드는 코드영역
class TOC extends Component{
    //class 안에서는 function을 생략할 수 있다.
    render(){
      console.log('TOC render');
      var lists=[];
      var data = this.props.data;
      var i=0;
      while(i<data.length){
        //Each child in a list should have a unique "key" prop.
        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>);
        i = i+1;
      }
      return(
        <nav>
          <ul>
            {lists}
          </ul>
        </nav>
      );
    }
  }

  export default TOC; // 외부에서 TOC 안에 있는 요소들을 가져다 쓸 수 있도록 해줌 

 

2)  TOC 클릭 시, 개별 콘텐츠 바꾸기 

TOC.js

import React, {Component} from 'react';  

class TOC extends Component{
    render(){
      console.log('TOC render');
      var lists=[];
      var data = this.props.data;
      var i=0;
      while(i<data.length){
        lists.push(<li key={data[i].id}>
          <a href={"/content/"+data[i].id}

            //var data-id = {data[i].id}
            data-id = {data[i].id}
            onClick={function(e){
              debugger;
              e.preventDefault();
              this.props.onChangePage();

            }.bind(this)}
            
          > {data[i].title}
          </a> </li>);
        i = i+1;
      }
      return(
        <nav>
          <ul>
            {lists}
          </ul>
        </nav>
      );
    }
  }

  export default TOC;

 

이벤트 객체에는 target 이라는 중요한 속성이 있다. 

e.target  -> a tag 를 가르키고 이를 통해 a tag의 특정 값에 접근할 수 있다.   = e.target.dataset.id

target의 속성을 확인했으면 적용해보자  ->  onChangePage(e.target.dataset.id)

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

TOC.js

import React, {Component} from 'react';  

class TOC extends Component{
    render(){
      console.log('TOC render');
      var lists=[];
      var data = this.props.data;
      var i=0;
      while(i<data.length){
        lists.push(<li key={data[i].id}>
          <a href={"/content/"+data[i].id}

            //var data-id = {data[i].id}
            data-id = {data[i].id}
            onClick={function(e){
              //debugger;
              e.preventDefault();
              this.props.onChangePage(e.target.dataset.id);

            }.bind(this)}
            
          > {data[i].title}
          </a> </li>);
        i = i+1;
      }
      return(
        <nav>
          <ul>
            {lists}
          </ul>
        </nav>
      );
    }
  }

  export default TOC;

app.js

import React, {Component} from 'react';
import './App.css';

import Subject from "./component/Subject"
import TOC from "./component/TOC"
import Content from "./component/Content"


class App extends Component{
  constructor(props){ 
    super(props); 
    this.state={
      mode :"read", 
      welcome : {title:'welcome', desc:'hello react'},

      selected_content_id : 0,

      subject:{title:'WEB', sub:'world wide web !'},
      content:[
        {id:1, title:"HTML", desc:"HTML is HyperText "},
        {id:2, title:"CSS", desc:"Css is for design "},
        {id:3, title:"JS", desc:"javascript is for intrective "}
      ]
    }
  }

  render(){
    console.log('APP render & this is : ' , this);
    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.content.length){
        var data = this.state.content[i];
        if(data.id === this.state.selected_content_id){
          _title=data.title;
          _desc=data.desc;
          break;
        }
        i++; 
      }
    }
    
    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 data={this.state.content}
          onChangePage={function(id){
            this.setState({
              mode : 'read',
              selected_content_id : Number(id)
            })
          }.bind(this)}
         ></TOC>

         <Content title={_title} desc={_desc}></Content>
      </div>
    );
  }
}
export default App;

target 속성을 이용하지 않고, bind()의 인자로 활용하기 

onClick={
      function
(id, num, e){ 
              e.preventDefault(); 
              this.props.onChangePage(id, num); 
      }.bind(this, data[i].id, 10)
}

TOC.js

import React, {Component} from 'react';  

class TOC extends Component{
    render(){
      console.log('TOC render');
      var lists=[];
      var data = this.props.data;
      var i=0;
      while(i<data.length){
        lists.push(<li key={data[i].id}>
          <a href={"/content/"+data[i].id}
            data-skdjfnsdkfj = {data[i].id}

            onClick={function(id,e){
              e.preventDefault();
              this.props.onChangePage(id);
            }.bind(this, data[i].id)}
          > {data[i].title}
          </a> </li>);
        i = i+1;
      }
      return(
        <nav>
          <ul>
            {lists}
          </ul>
        </nav>
      );
    }
  }

  export default TOC;

 

댓글