본문 바로가기

React

20210604 React02 : Event Handling, Component Lifecycle, Lifecycle 변경된 사항 (16.3버전 이후), getDerivedStateFromProps, getSnapshotBeforeUpdate, ComponentDidCatch

React 02

Event Handling


  • HTML DOM 에 클릭하면 이벤트가 발생하고, 그에 맞는 변경이 일어남
  • JSX에 이벤트를 설정할 수 있음
  • camelCase로만 사용가능
    • 예) onClick, onMouseEnter
  • 이벤트에 연결된 자바스크립트 코드는 함수임
    • 이벤트 = {함수}와 같이 씀
  • 실제 DOM요소들에만 사용 가능함
    • 리액트 컴포넌트에 사용하면, 그냥 props로 전달함

Function Component 이벤트 연결


  • JSX에 바로 이벤트 적고, 해당 이벤트에 함수를 붙여줌
  • 함수 컴포넌트에서는 state, 라이프 사이클을 사용하지 않기 때문에, 상태값을 활용할 수는 없다.
function Component() {
  return (
    <div>
      <button
        onClick={() => {
          console.log("clicked");
        }}
      >
        클릭
      </button>
    </div>
  );
}
ReactDOM.render(<Component />, document.querySelector("#root"));


Class Component 이벤트 연결


  • 상태값 state을 활용하는 경우
  • 이벤트에 달 함수를 이원화 시켜 관리하는 2가지 방법
    • 해당 함수를 Arrow 함수로 표기하는 방법
    • 그냥 class에서의 메소드 정의 형식 표기방법(prototype)

해당 함수를 Arrow 함수로 표기하는 방법


  • 객체의 값으로 이전 state를 spread를 사용해서 가져오고, 값을 덮어씌워서 setState로 state값을 변경시킴
// 이벤트에 달 함수를 Arrow 함수로 표기하는 경우
class Component extends React.Component {
  state = {
    count: 0,
  };

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.click}>클릭</button>
      </div>
    );
  }

  click = () => {
    console.log("clicked");
    this.setState((state) => ({
      ...state,
      count: state.count + 1,
    }));
  };
}
ReactDOM.render(<Component />, document.querySelector("#root"));


class 메소드 정의 표기방법(prototype)


  • 일반적인 prototype method처럼 사용하지만, 해당 method의 경우 method가 this 바인딩 하는데 문제가 생김
  • 해당 형태로 표기하면 constructor에서 해당 method에 this를 바인딩 시켜줘야 함
class Component extends React.Component {
  // method가 this를 찾을 수 있게 바인딩 해줘야함
  constructor(props) {
    super(props);
    this.click = this.click.bind(this);
  }

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.click}>클릭</button>
      </div>
    );
  }

  click() {
    console.log("clicked");
    this.setState((state) => ({
      ...state,
      count: state.count + 1,
    }));
  }
}
ReactDOM.render(<Component />, document.querySelector("#root"));



Component Lifecycle


  • 리책트 컴포넌트는 탄생부터 죽음까지 여러지점에서 개발자가 작업이 가능하도록 메서드를 오버라이딩 할수 있게 해줌

Declarative 성질


컴포넌트가 그려지는 때 (탄생)

  • lnitialization (생성) : constructor 가 불리는 상태로 props, state의 초기값이 설정되는 구간
  • Mounting :
    • componentWillMount : render가 되기 전
    • render (최초 render, mount)
    • componentDidMount : render가 된 후

컴포넌트가 사라지는 때 (죽음)

  • Unmounting :
    • componentWillUnmount : 컴포넌트가 사라지기 전

컴포넌트가 업데이트 되는 때

  • Updation : props 나 states가 변경 되는 때
    • props
      • componentWillReceiveProps
      • shouldComponentUpdate : 컴포넌트가 업데이트 되어야 하는지 아닌지를 판단 (true, false)
        • 불필요하게 render되는 것을 방지하기 위함
      • componentWillUpdate : render 되기 직전
      • render
      • componentDidUpdate
    • states
      • shouldComponentUpdate : 컴포넌트가 업데이트 되어야 하는지 아닌지를 판단 (true, false)
        • 불필요하게 render되는 것을 방지하기 위함
      • componentWillUpdate : render 되기 직전
      • render
      • componentDidUpdate
  • Props만 should 전에 componentWillReceiveProps가 존재함



컴포넌트 생성 및 마운트 (Mount)


  • constructor -> componentWillMount -> render! -> componentDidMount! -> setInterval! -> render! -> setInterval! -> render! -> ...
class App extends React.Component {
  state = {
    age: 39,
  };
  constructor(props) {
    super(props);
    console.log("constructor", props);
  }
  render() {
    console.log("render!");
    return (
      <div>
        <h2>
          Hello {this.props.name} - {this.state.age}
        </h2>
      </div>
    );
  }
  // 차후에 안쓰여질 것이라서 주의해야함
  componentWillMount() {
    console.log("componentWillMount!");
  }
  componentDidMount() {
    console.log("componentDidMount!");
    setInterval(() => {
      console.log("setInterval");
      this.setState((state) => ({ ...state, age: state.age + 1 }));
    }, 1000);
  }
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));


컴포넌트 props , state 변경 (Update)


  • props, state 변경시 -> componentWillReceiveProps (Props만) -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
  • componentWillReceiveProps
    • props를 새로 지정했을 때 바로 호출
    • state 변경에 반응 X
      • props의 값에 따라 state를 변경해야 하는경우 -> setState를 이용해서 state 변경
      • 다음 이벤트로 각각 가지 않고, shouldComponentUpdate에서 모아 한번에 변경됨
  • shouldComponentUpdate
    • porps만 변경되어도, state만 변경되어도, 둘다 변경되어도 반응함
    • newProps와 new State를 인자로 해서 호출
    • return type이 boolean으로 -> true면 render , false면 render 호출 안함
      • should를 구현하지 않으면 default값으로 true가 되어 있음
  • componentWillUpdate
    • 컴포넌트가 재 랜더링 되기 직전 호출
    • 여기서 setState 같은거 쓰면 안됨
  • componentDidUpdate : 컴포넌트가 재 랜더링을 마치면 불림
class App extends React.Component {
  state = {
    age: 39,
  };
  constructor(props) {
    super(props);
    console.log("constructor", props);
  }
  render() {
    console.log("render!");
    return (
      <div>
        <h2>
          Hello {this.props.name} - {this.state.age}
        </h2>
      </div>
    );
  }
  // 차후에 안쓰여질 것이라서 주의해야함
  componentWillMount() {
    console.log("componentWillMount!");
  }
  componentDidMount() {
    console.log("componentDidMount!");
    setInterval(() => {
      this.setState((state) => ({ ...state, age: state.age + 1 }));
    }, 10000);
  }
  // Update
  componentWillReceiveProps(nextProps) {
    console.log("componentWillReceiveProps", nextProps);
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate", nextProps, nextState);
    return true; // 조건부로 update 제어 가능
  }
  componentWillUpdate(nextProps, nextState) {
    console.log("componentWillUpdate", nextProps, nextState);
  }
  componentDidUpdate(prevProps, prevState) {
    // 이전 props를 보여주니까, 전에 이것을 업데이트 했음을 알려줌
    console.log("componentDidUpdate", prevProps, prevState);
  }
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));


컴포넌트 제거 (Unmount)


  • componentWillUnmount : 요소가 없어지기 전에 실행함

class App extends React.Component {
  state = {
    age: 39,
  };
  interval = null;
  constructor(props) {
    super(props);
    console.log("constructor", props);
  }
  render() {
    console.log("render!");
    return (
      <div>
        <h2>
          Hello {this.props.name} - {this.state.age}
        </h2>
      </div>
    );
  }
  componentWillMount() {
    console.log("componentWillMount!");
  }
  componentDidMount() {
    console.log("componentDidMount!");
    // 요소가 사라지더라도 함수는 계속 돌고 있으므로 함수를 클래스 멤버변수에 담아
    // willUnmount에서 clear해서 없애줌
    this.interval = setInterval(() => {
      this.setState((state) => ({ ...state, age: state.age + 1 }));
    }, 10000);
  }
  componentWillReceiveProps(nextProps) {
    console.log("componentWillReceiveProps", nextProps);
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate", nextProps, nextState);
    return true; // 조건부로 update 제어 가능
  }
  componentWillUpdate(nextProps, nextState) {
    console.log("componentWillUpdate", nextProps, nextState);
  }
  componentDidUpdate(prevProps, prevState) {
    console.log("componentDidUpdate", prevProps, prevState);
  }
  componentWillUnmount() {
    // 해당 함수를 함수에 연결해서 없앰
    clearInterval(this.interval);
  }
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));



라이프사이클 변경된 사항 (16.3버전 이후)


라이프 사이클
constructor
componentWillMount -> getDerivedStateFromProps
render
componentDidMount
componentWillReceiveProps -> getDerivedStateFromProps
shouldComponentUpdate
render
componentWillUpdate -> getSnapshotBeforeUpdate
DOM에 적용
componentDidUpdate
componentWillUnmount



getDerivedStateFromProps


  • componentWillMount에 대체해서 나온 라이프사이클 method
  • props를 받아서 state를 조절할 수 있었는데 이제는 그 기능을 메인으로 하고 있음
  • 일반 라이프사이클과는 다르게 클래스 안에서 static method로 만들어 주어야 함
  • return 부분에 새로운 state를 설정해 줄 수 있음
  • 새로운 props가 들어오면 state가 return으로 바뀜
  • update에 의해서 render가 실행되기 전에도 실행됨 (즉, render실행전에 무조건 실행된다고 보면 됨)
  • 시간의 흐름에 따라 변하는 props에 state 가 의존하는 경우 주로 사용됨
class App extends React.Component {
  state = {
    age: 39,
  };
  interval = null;
  constructor(props) {
    super(props);
    console.log("constructor", props);
  }
  render() {
    console.log("render!");
    return (
      <div>
        <h2>
          Hello {this.props.name} - {this.state.age}
        </h2>
      </div>
    );
  }
  // static method로 설정
  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("getDerivedStateFromProps", nextProps, prevState);
    return {
      age: 390,
    }; // return 부분에 새로운 state를 설정할 수 있음
  }
}


getSnapshotBeforeUpdate


  • render메서드가 호출되고, DOM에 적용되기 직전에 실행되는 라이프사이클
  • prevProps, prevState 를 인자로 사용하여 변한 props, states와 그전의 값들을 비교한 결과를 DidUpdate때 사용하고자 하는 경우 사용함
let i = 0;
class App extends React.Component {
  state = { list: [] };
  render() {
    return (
      <div id="list" style={{ height: 100, overflow: "scroll" }}>
        {this.state.list.map((i) => {
          return <div>{i}</div>;
        })}
      </div>
    );
  }
  componentDidMount() {
    setInterval(() => {
      this.setState((state) => ({
        list: [...state.list, i++],
      }));
    }, 3000);
  }
  // 과거 state 와 변한 현재 state를 비교하여 snapshot 값으로 null 또는 차이를 넘김
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.list.length === this.state.list.length) return null;
    const list = document.querySelector("#list");
    return list.scrollHeight - list.scrollTop;
  }
  // snapshot값을 받아서 변경함
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("DidUpdate-snapshot", snapshot);
    if (snapshot === null) return;
    const list = document.querySelector("#list");
    list.scrollTop = list.scrollHeight - snapshot;
  }
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));


ComponentDidCatch (컴포넌트 에러 캐치)


  • 기존에는 부분적인 에러 발생시 앱이 모두 동작을 못했음
  • ComponentDidCatch를 통해 문제발생시 error 화면을 보여줄수 있게 되었음
  • 하위 컴포넌트에서 error발생시 해당 부모 컴포넌트에서 error말고 다른 것을 보여 줄 수 있음
  • 단, 자기자신에 대한 error는 catch를 못함 -> 최상위 컴포넌트에 달아야 함
  • Error Boundaries라는 라이브러리를 보통 사용함
class App extends React.Component {
  state = {
    hasError: false,
  };
  render() {
    if (this.state.hasError) {
      return <div>예상치 못한 에러 발생함!!</div>;
    }
    return <div>정상 페이지</div>;
  }
  componentDidCatch(error, info) {
    this.setState({ hasError: true });
  }
}
ReactDOM.render(<App name="Mark" />, document.querySelector("#root"));