WEB🔨/React

리액트를 다루는 기술 요약#4

최문경 블로그 2021. 6. 23. 17:30

해당 시리즈는 제목 그대로 김민준님의 '리액트를 다루는 기술'을 요약한 글입니다.

 

목차

7장 컴포넌트 라이프사이클 메서드

8장 Hooks

 

 

7장 컴포넌트 라이프사이클 메서드

라이프사이클 메서드의 이해

  • 라이프사이클 메서드의 종류는 총 아홉 가지
  • 마운트, 업데이트, 언마운트의 세 가지 카테고리로 나눌 수 있음

 

마운트

DOM이 생성되고 웹 브라우저상에 나타나는 것을 의미함.

 

호출되는 메서드

  1. constructor
  2. getDerivedStateFromProps
  3. render
  4. componentDidMount

 

 

업데이트

컴포넌트는 다음과 같은 총 네 가지 경우에 업데이트

  1. props가 바뀔 때
  2. state가 바뀔 때
  3. 부모 컴포넌트가 리렌더링될 때
  4. this.forceUpdate로 강제로 렌더링을 트리거할 때

 

호출되는 메서드

  1. getDerivedStateFromProps
  2. shouldComponentUpdate
    true 반환 시 render 호출, false 반환 시 여기서 작업 취소
  3. render
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate

 

 

언마운트

마운트의 반대 과정, 컴포넌트를 DOM에서 제거하는 것

 

호출되는 메서드

  • componentWillUnmount

 

 

라이프사이클 메서드 살펴보기

1. render() 함수

라이프사이클 메서드 중 유일한 필수 메서드.

이 메서드 안에서 this.props와 this.state에 접근할 수 있으며, 리액트 요소를 반환함.

요소는 div 같은 태그가 될 수도 있고, 따로 선언한 컴포넌트가 될 수도 있음. 아무것도 보여 주고 싶지 않다면 null 값이나 false 값을 반환하면 됨.

 

주의) 이 메서드 안에서는 이벤트 설정이 아닌 곳에서 setState를 사용하면 안 되며, 브라우저의 DOM에 접근해서도 안됨. DOM 정보를 가져오거나 state에 변화를 줄 때는 componentDidMount에서 처리해야 함.

 

 

2. constructor 메서드

컴포넌트의 생성자 메서드로 컴포넌트를 만들 때 처음으로 실행.

초기 state를 정할 수 있음.

 

 

3. getDerivedStateFromProps 메서드

리액트 v16.3 이후에 새로 만든 메서드.

props로 받아 온 값을 state에 동기화시키는 용도로 사용.

컴포넌트가 마운트될 때와 업데이트될 때 호출.

 

 

4. componentDidMount 메서드

컴포넌트를 만들고, 첫 렌더링을 다 마친 후 실행.

이 메서드에서 다른 자바스크립트 라이브러리 또는 프레임워크 함수를 호출하거나 이벤트 등록, setTimeout, setInterval, 네트워크 요청 같은 비동기 작업을 처리하면 됨.

 

 

5. shouldComponentUpdate 메서드

shouldComponentUpdate(nextProps, nextState) { ... }

props 또는 state를 변경했을 때, 리렌더링을 시작할지 여부를 결정하는 메서드.

반드시 true 또는 false를 반환해햐 함.

따로 생성하지 않으면 기본적으로 true를 반환함. (false 값을 반환하면 여기서 중지)

이 메서드 안에서 현재 props와 state는 this.props와 this.state로 접근하고, 새로 설정될 props 또는 state는 nextProps와 nextState로 접근 가능.

 

 

6. getSnapshotBeforeUpdate 메서드

getSnapshotBeforeUpdate(prevProps, prevState) { ... }

리액트 v16.3 이후 만든 메서드.

render에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출.

이 메서드에서 반환하는 값은 componentDidUpdate에서 세 번째 파라미터인 snapshot 값으로 전달받을 수 있음.

주로 업데이트하기 직전의 값을 참고할 일이 있을 때 활용.

 

 

7. componentDidUpdate 메서드

componentDidUpdate(prevProps, prevState, snapshot) { ... }

리렌더링을 완료한 후 실행.

업데이트가 끝난 직후이므로 DOM 관련 처리를 해도 무방.

prevProps 또는 prevState를 사용하여 컴포넌트가 이전에 가졌던 데이터에 접근할 수 있음.

 

 

8. componentWillUnmount 메서드

컴포넌트를 DOM에서 제거할 때 실행

componentDidMount에서 등록한 이벤트, 타이머, 직접 생성한 DOM이 있다면 여기서 제거해야함.

 

 

9. componentDidCatch 메서드

리액트 v16에서 새로 도입.

컴포넌트 렌더링 도중에 에러가 발생했을 때 애플리케이션이 먹통이 되지 않고 오류 UI를 보여줄 수 있게 해줌.

그러나 자신에게 발생하는 에러는 잡아낼 수 없고 자신의 this.props.children으로 전달되는 컴포넌트에서 발생하는 에러만 잡아낼 수 있음.

 

 

라이프사이클 메서드 사용하기

import React, { Component } from 'react';
class LifeCycleSample extends Component {
  state = {
    number: 0,
    color: null,
  };

  myRef = null; // ref를 설정할 부분

  constructor(props) {
    super(props);
    console.log('constructor');
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps');
    if (nextProps.color !== prevState.color) {
      return { color: nextProps.color };
    }
    return null;
  }

  componentDidMount() {
    console.log('componentDidMount');
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate', nextProps, nextState);
    // 숫자의 마지막 자리가 4면 리렌더링하지 않습니다.
    return nextState.number % 10 !== 4;
  }

  componentWillUnmount() {
    console.log('componentWillUnmount');
  }

  handleClick = () => {
    this.setState({
      number: this.state.number + 1,
    });
  };

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('getSnapshotBeforeUpdate');
    if (prevProps.color !== this.props.color) {
      return this.myRef.style.color;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('componentDidUpdate', prevProps, prevState);
    if (snapshot) {
      console.log('업데이트되기 직전 색상: ', snapshot);
    }
  }

  render() {
    console.log('render');
    
    const style = {
      color: this.props.color,
    };
    
    return (
      <div>
        <h1 style={style} ref={(ref) => (this.myRef = ref)}>
          {this.state.number}
        </h1>
        <p>color: {this.state.color}</p>
        <button onClick={this.handleClick}>더하기</button>
      </div>
    );
  }
}
export default LifeCycleSample;
import React, { Component } from 'react';
import LifeCycleSample from './LifeCycleSample';
import ErrorBoundary from './ErrorBoundary';

// 랜덤 색상을 생성합니다.
function getRandomColor() {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}
class App extends Component {
  state = {
    color: '#000000',
  };
  handleClick = () => {
    this.setState({
      color: getRandomColor(),
    });
  };
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>랜덤 색상</button>
        <ErrorBoundary>
          <LifeCycleSample color={this.state.color} />
        </ErrorBoundary>
      </div>
    );
  }
}
export default App;

첫 실행


랜덤 색상 클릭 후


더하기 클릭 후

 

 

 

정리

라이프사이클 메서드들은 서드파티 라이브러리를 사용하거나 DOM을 직접 건드려야 하는 상황에서 유용함.

추가로 컴포넌트 업데이트의 성능을 개선할 때는 shouldComponentUpdate가 중요하게 사용됨.

 

 

 

8장 Hooks

Hooks는 리액트 v16.8에 새로 도입된 기능으로 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해줍니다.

 

useState

가장 기본적인 Hook이며, 상태를 관리할 수 있게 해줌.

 

 

useEffect

컴포넌트가 마운트, 언마운트, 업데이트될 때 해야할 작업을 설정할 수 있는 Hook.

클래스형 컴포넌트의 componentDidMount와 componentDidUpdate를 합친 형태로 보아도 무방.

import React, { useEffect, useState } from 'react';

const App = () => {
  const [name, setName] = useState('');
  const [nickname, setNickname] = useState('');
  useEffect(() => {
    console.log('렌더링이 완료되었습니다.');
    console.log({ name, nickname });
  });

  const onChangeName = (e) => {
    setName(e.target.value);
  };

  const onChangeNickname = (e) => {
    setNickname(e.target.value);
  };

  return (
    <div>
      <div>
        <input type="text" value={name} onChange={onChangeName} />
        <input type="text" value={nickname} onChange={onChangeNickname} />
      </div>
      <div>
        <div>
          <b>이름: </b> {name}
        </div>
        <div>
          <b>닉네임: </b> {nickname}
        </div>
      </div>
    </div>
  );
};

export default App;


useEffect의 첫 번째 파라미터는 작업할 함수(callBack)이고 두 번째 파라미터는 의존성 배열(deps)이다.

 

1. deps에 빈 배열을 넣어주면 해당 컴포넌트가 마운트 될 때만 함수를 실행 (언마운트 또는 업데이트될 때는 실행하지 않음)

useEffect(() => {
    console.log("마운트될 때만 실행');
}, []);

 

 

2. deps에 특정 값을 넣어주면 해당 값이 마운트 또는 업데이트될 때마다 실행.

useEffect(() => {
    console.log("name');
}, [name]);

 

 

3. deps배열을 넣지 않고 뒷정리 함수를 반환하면 컴포넌트가 언마운트되기 전(언마운트될 때) 또는 업데이트되기 직전에 함수를 실행.

useEffect(() => {
    console.log('effect');
    console.log(name);
    return () => {
        console.log('cleanup');
        console.log(name);
    };
});

 

 

4. deps에 빈배열을 넣고 뒷정리 함수를 넣으면 해당 컴포넌트가 언마운트될 때만 함수를 실행.

useEffect(() => {
    console.log('effect');
    return () => {
        console.log('cleanup');
    };
}, []);

 

 

5. deps에 값을 넣고 뒷정리 함수를 사용하면 해당 값이 업데이트 되기 전과 언마운트될 때 실행

useEffect(() => {
    console.log('값이 설정됨');
    console.log(user);
    return () => {
      console.log('업데이트 되기 전');
      console.log('cleanup');
    };
  }, [user]);

 

 

useReducer

useState보다 더 다양한 상태를 업데이트해주고 싶을 때 사용하는 Hook.

 

리듀서(reducer)는 현재 상태(state)와 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태를 반환하는 함수입니다. 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜 주어야 합니다.

import React, { useReducer } from 'react';

function reducer(state, action) {
    switch (action.type) {
    	case 'INCREMENT':
            return { value: state.value + 1 };
        case 'DECREMENT':
            return { value: state.value - 1 };
        default:
            return state;
    }
}

const Counter = () => {
    const [state, dispatch] = useReducer(reducer, { value: 0 });
    
    return (
    	<div>
            <p>현재 카운터 값은 {state.value}입니다.</p>
            <button onClick={() => dispatch({type: 'INCREMENT'})}+1</button>
            <button onClick={() => dispatch({type: 'DECREMENT'})}-1</button>
        </div>
    );
}

export default Counter;

useReducer의 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어 줍니다. 이 Hook을 사용하면 state값과 dispatch 함수를 받아 오는데요. 여기서 state는 현재 가리키고 있는 상태고, dispatch는 액션을 발생시키는 함수입니다. dispatch(action)과 같은 형태로, 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출되는 구조입니다.

 

useReducer를 사용했을 때의 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 것입니다.

 

 

8.3.2 인풋 상태 관리하기

기존에는 인풋이 여러 개여서 useState를 여러 번 사용했는데요. useReducer를 사용하면 기존에 클래스형 컴포넌트에서 input 태그에 name값을 할당하고 e.target.name을 참조하여 setState를 해 준 것과 유사한 방시으로 작업을 처리할 수 있습니다.

import React, { useReducer } from 'react';

function reducer(state, action) {
    return {
    	...state,
        [action.name]: action.value
    };
}

const Info = () => {
    const [state, dispatch] = useReducer(reducer, {
    	name: '',
        nickname: '',
    });
    const [name, nickname] = state;
    const onChange = e => {
    	dispatch(e.target);
    }
    
    return (
        <div>
            <div>
            	<input name="name" value={name} onChange={onChange} />
            	<input name="nickname" value={nickname} onChange={onChange} />
            </div>
            <div>
            	이름: {name}, 닉네임: {nickname}
            </div>
        </div>  
    );
};

useReducer에서의 액션은 어떤 값도 사용할 수 있기 때문에 이번에는 이벤트 객체가 지니고 있는 e.target 값 자체를 액션 값으로 사용했습니다. 이런 식으로 인풋을 관리하면 아무리 인풋의 개수가 많아져도 코드를 짧고 깔끔하게 유지할 수 있습니다.

 

 

useMemo

returns a memorized value

최적화할 때 사용하는 Hook.

const count = useMemo(() => countActiveUsers(users), [users]);

첫 번째 파라미터에는 실행할 함수

두 번째 파라미터는 deps

-> users값이 변할 때만 함수를 실행하고 변하지 않았을 때는 이전 값을 사용!

 

 

useCallback

returns a memorized callback

최적화할 때 사용.

두 번째 파라미터인 deps 의 값이 변할 때만 첫 번째 파라미터로 들어온 함수를 재선언. 변하지 않았을 때는 함수를 재사용.

 

 

useRef

ref를 쉽게 사용할 수 있게 해주는 Hook.