React (3) event

2020-06-24

props, state, 그리고 event

링크를 걸어놓고, 그 링크에다가 event를 설치한다
→ 링크를 클릭하면 그에 따라서 해당 component의 state가 바뀐다
→ 바뀐 state가 하위 component의 props 값으로 전달된다

이런 식으로 event, state, props의 상호작용을 통해 동적 웹페이지를 구현한다.

state에 관한 중요한 사실

react에서는 (props의 값이나)state의 값이 바뀌면
그 state를 가지고 있는 component의 render 메소드가 다시 호출된다.

→ 그 render 함수가 재호출됨에 따라,
그 render 함수 하위에 있는 component들도 각자의 render 함수를 가지는데,
걔네들까지 전부 싹 다시 호출된다.

→ 결국, props나 state가 변경되면 화면이 다시 그려지게 된다는 뜻.

class App extends Component {
  constructor(props) {
    this.state = { ... };
  }
  render() {
    return (
      <div className="App">
        <Subject ... ></Subject>
        <Navigation ... ></Navigation>
        <Content ... ></Content>
      </div>
    );
  }
}

요기서 보면, App의 state에 변화가 생기면 App의 render가 다시 호출된다.
그러면 App의 render 함수 내부에 있는
Subject, Navigation, Content의 render 함수까지 싹 다 다시 호출된다.

render 함수

render 함수는 ‘어떤 HTML을 화면에 그릴 것인가’를 결정하는 함수.
정확하게는 render 함수의 return문 안에 들어간다.

별도로 자바스크립트 로직을 쓰고싶으면, return문 밖에다가 써주면 됨.

event

Subject component 안에서 event가 발생했을 때, Subject 바깥에 있는
즉, Subject의 상위 component인 App의 state를 바꾸는 것이 최종 목적.

그것에 앞서, 일단은 Subject component를 풀어 써서 props를 거치지 않고
state를 직접 가져다 써서 event를 프로그래밍 해보자.

Subject 내의 a 태그에 onclick event를 붙여줄건데, 여기서 주의할 점.
React는 JSX를 사용하기 때문에 자바스크립트와 문법이 다르다.

  • 자바스크립트 : onclick = “클릭 이벤트 내용”
  • JSX : onClick = {클릭 이벤트 내용} (onclick의 c가 대문자임)
    <a href = "/" onClick = {function() {
    alert('Hi'); 
    }}></a>
    

preventDefault

이벤트를 달고, a 링크를 클릭해보면 페이지가 새로고침 됨.
이는 a라는 태그에 원래 붙어있던 기본 동작 때문.
이 기본 동작을 방지하는 메소드가 바로 preventDefault.

자바스크립트에서는 이벤트를 통해 함수가 실행될 때,
그 함수의 인자로 이벤트 객체를 받을 수가 있음(보통 e로 받음).
preventDefault 메소드는 이 이벤트 객체의 메소드.

<a href = "/" onClick = {function() {
  e.preventDefault();
  alert('Hi'); 
}}></a>

요렇게 하면, a 태그의 기본 동작이 사라져서
페이지가 새로고침되지 않는다.

state 변경하기

이제, onClick 이벤트 내에서 state를 변경할 차례.

<a href = "/" onClick = {function(e) {
  e.preventDefault();
  this.state.mode = 'welcome';
}}></a>

에러가 발생할 것이다.
여기에는 두 가지의 문제점이 있다.

  1. 이벤트가 실행됐을 때 호출되는 함수 안에서는 ‘this’가 component를 가리키지 않는다. 정확하게는, 아무 값도 세팅되어있지 않아서 ‘default’값을 가질 것이다.
  2. state의 값을 직접 변경해선 안된다. state의 값을 직접 변경하면 React에서 state값이 바뀐 것을 인식하지 못하기 때문.

bind

우선, 첫 번째 문제의 해결책.
event를 붙여줄 때 this를 찾을 수 없어서 발생한 에러.
이럴 때는 이벤트 함수 끝에 bind() 메소드를 붙여주면 된다.

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

이렇게 하면, 해당 함수 안에서 this는 바깥의 component를 가리키게 된다.

함수 뒤에다가 bind(obj)를 붙여주면 그 함수와 내용은 같지만, 함수 내부에서 this가 obj를 가리키도록 하는 새로운 함수를 복제한다.

setState

두 번째 문제의 해결책.
React가 state의 값이 바뀌었다는 것을 인식하기 위해선 setState 함수를 써야 함.
이게 react 사용 설명서에 나와 있는 방법이기 때문에 그냥 그렇게 해야 함.

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

setState(2)

생성자, 즉 component가 생성될 때 맨 처음에 constructor 함수가 호출되는데,
그 함수에서는 this.state = { 대충 state 내용 } 이런식으로 state를 수정할 수 있다.

하지만, 이미 component의 생성이 끝난 다음에 동적으로 state의 값을 바꾸고 싶다면
직접적으로 state를 수정하면 안 된다.

setState라는 함수에, 우리가 변경하고싶은 값을 객체의 형태로 전달해야 한다.
그래야 react가 state의 변화를 인지할 수 있다.

우리가 setState를 통해 react에게 state의 변화를 알리면
render함수가 재호출되면서, 그 하위 component들의 render 함수도 재호출됨.

기억할 것은, constructor 바깥에서는 항상 setState를 사용해야한다라는 것.

전체 코드

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      subject:{title:'WEB', sub:'World Wide Web!'},
      mode: 'welcome',
      welcome: {title:'welcome', desc: '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() {
    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.subject.title;
      _desc = this.state.subject.desc;
    }
    return (
      <div className="App">
        <header>
          <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>
        <Navigation data={this.state.contents}></Navigation>
        <Content title={_title} desc={_desc}></Content>
      </div>
    );
  }
}

a태그 클릭 이벤트 발생(클릭으로 인한 mode 변경)
→ state가 변했으므로 App component의 render가 다시 호출됨.

이 때, mode가 바뀌면서 _title, _desc의 값이 바뀌게 되고,
그러면 Content의 props로 들어가는 값들이 바뀌게 된다.

→ 바뀐 props를 기반으로 해서 Content의 render함수가 다시 호출된다.

→ Content 뿐만 아니라 App의 모든 하위 component들이
다 이런 식으로 다시 렌더링 된다(render가 다시 호출된다).

event 만들어서 하위 component로 전달하기

component의 이벤트를 만들어서
그 event를 사용하는 사람들이 내가 만든 이벤트로
이벤트 프로그래밍을 할 수 있게 해보자.

지금까지는 Subject를 풀어 써서 이벤트를 프로그래밍 했으니,
이제 Subject를 다시 사용할 차례.
(event를 만들어서 하위 component에 전달함으로써,
그 component를 풀어쓰지 않고도 event를 사용하는 게 가능해짐.)

class App extends Component {
  constructor(props) {
    {/* 대충 constructor 내용 */}
  }
  render() {
    {/* 대충 mode에 따라 _title, _desc 바꾸는 내용 */}
    return (
      <div className="App">
        <Subject 
          title={this.state.subject.title} 
          sub={this.state.subject.sub}
          onChangePage =(function() {
            this.setState({mode : 'welcome'});
          }.bind(this)} 

          {/* onChangePage라는 event를 만들어, 이 안에 설치한 함수를 호출하도록 한다.
              그러면 이 함수는 component의 사용자가 직접 정의해서 사용할 수 있는 것. */}

        >
        </Subject>
        {/* 대충 나머지 component들 */}
      </div>
    );
  }
}

{/* 여기서, onChangePage라는 이벤트는 props의 형태로 Subject에 전달될 것이라는 것을 알 수 있다.
    결국, 얘도 props의 일부임. 그냥 props 안에 함수를 전달해주는 형태인 듯. */}

class Subject extends Component {
  render() {
    return (
      <header>
        <h1><a href = "/" onClick={function(e) {
          e.preventDefault();
          this.props.onChangePage(); {/* 전달된 onChangePage라는 함수를 호출 */}
        }.bind(this)>{this.props.title}</a></h1>
        {this.props.sub}
      </header>
    );
  }
}