TIL | #10 React | 생명주기 LifeCycle, Hook

trevor1107·2021년 4월 8일
0

2021-04-01(목)

LifeCycle

리액트에서 컴포넌트는 생성 → 업데이트 → 제거 순서로 생명 주기를 가지고 있다.

Will 접두사가 붙은 메서드는 어떠한 작업을 시작하기 전에 동작한다.
Did 접두사가 붙은 메서드는 어떠한 작업을 마친 후 동작한다.

클래스 컴포넌트에서만 사용할 수 있고, 함수에서는 Hook 기능을 이용하여 비슷한 작업을 할 수 있다.

Mount

DOM이 생성되고 웹 브라우저에 나타나는 것을 말한다.
마운트 순서는 다음과 같다.

component create → constructor() → getDerivedStateFromProps() → render() → componentDidMount()

Update

업데이트는 다음과 같은 상황에서 호출된다.

  • props가 변경될 때
  • state가 바뀔 때
  • 부모 컴포넌트 리렌더링
  • this.forceUpdate

getDerivedStateFromProps() → shouldComponentUpdate() → render()→ getSnapshotBeforeUpdate() → componentDidUpdate()

UnMount

componentWillUnmount()

componentDidMount : 컴포넌트가 마운트 된 직후에 호출된다. 이 안에서 다른 JS 러이브러리, 프레임 워크 함수를 호출하거나 이벤트 등록,  setTimeout, setInterval, 네트워크요청 같은 비동지 작업을 처리하기 좋다.

shouldComponentUpdate : 이 메서드 안에서 현재 props와 state는 this.props와 this.state로 접근하고 새로 설정될 props또는 state nextProps와 nextState로 접근할 수 있다.
프로젝트 성능을 최적화 할때 상황에 맞는 알고리즘을 작성하여 리렌더링을 방지할때는 false값을 반환하게 한다.

getSnapshotBeforeUpdate : render에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출이 메서드에서 반환하는 값은 componentDidUpdate에서 세번째 파라미터 snapshot값을 전달받을수 있다.
주로 업데이트 하기 직전의 값을 참고할 일이 있을 때 활용한다.

componentDidUpdate : 리렌더링을 완료한후 실행. 업데이트가 끝난 직후 이므로 DOM관련 처리를 해도 무방하다.

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

componentDidCatch  : 컴포넌트 렌더링 도중에 에러가 발생했을때 애플리케이션이 먹통이 되지 않고 오류UI를 보여준다. 이 메서드를 사용할 때는 컴포넌트 자신에게 발생하는 에러를 잡아낼 수 없다. 자신의 this.props.children으로 전달되는 컴포넌트에서 발생하는 에러만 잡아낼 수 있다.

// App.js
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
// ErrorBoundary.js
import React, { Component } from 'react';

class ErrorBoundary extends Component {
    state = {
        error: false,
    };

    componentDidCatch(error, info) {
        this.setState({
            error: true,
        });
        console.log({ error, info });
    }
    render() {
        if (this.state.error) return <div>에러가 발생해뜨아</div>;
        return this.props.children;
    }
}

export default ErrorBoundary;
// LifeCycleSample.js
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, nextState) {
        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>
                {this.props.missing.value}
                <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;

HOOK

useState :  가장 기본적인 hook, 함수형 컴포넌트에서도 가변적인 상태를 지니게 해준다. 함수형에서 상태를 관리해야 한다면 이녀석을 사용한다.

useEffect : 리액트 컴포넌트가 렌더링 될때마다 특정 작업을 수행할수 있도록 설정하는 hook이다.
클래스형 컴포넌트에서 componentDidMount와 componentDidUpdate를 합친 형태 봐도 무방하다. 기본적으로 렌더링이 되고 난 직후마다 실행된다. 두번째 파라미터 배열에 무엇을 넣는지에 따라 실행조건이 달라진다.

위에서 설명한 생명주기 함수들은 클래스 컴포넌트에서만 사용이 가능하므로 함수 컴포넌트에서는 useEffect함수를 사용한다.

useEffect가 하는 일은 무엇일까? useEffect Hook을 이용하여 우리는 리액트에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는 지를 말한다. 리액트는 우리가 넘긴 함수를 기억했다가(이 함수를 ‘effect’라고 한다) DOM 업데이트를 수행한 이후에 불러낼 것이다. 위의 경우에는 effect를 통해 문서 타이틀을 지정하지만, 이 외에도 데이터를 가져오거나 다른 명령형(imperative) API를 불러내는 일도 할 수 있다.

useEffect를 컴포넌트 안에서 불러내는 이유는 무엇일까? useEffect를 컴포넌트 내부에 둠으로써 effect를 통해 count state 변수(또는 그 어떤 prop에도)에 접근할 수 있게 된다. 함수 범위 안에 존재하기 때문에 특별한 API 없이도 값을 얻을 수 있는 것이다. Hook은 자바스크립트의 클로저를 이용하여 리액트에 한정된 API를 고안하는 것보다 자바스크립트가 이미 가지고 있는 방법을 이용하여 문제를 해결한다.

useEffect는 렌더링 이후에 매번 수행되는 걸까? 그렇다. 기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행된다. 마운팅과 업데이트라는 방식으로 생각하는 대신 effect를 렌더링 이후에 발생하는 것으로 생각하는 것이 더 쉽다. 리액트는 effect가 수행되는 시점에 이미 DOM이 업데이트되었음을 보장한다.

에제를 통해서 알아보자.

// Info.js
import React, { useEffect, useState } from 'react';

const Info = () => {
	const [name, setName] = useState('');
	const [nickName, setNickName] = useState('');

	//마운트 될때만 실행하고 싶을때
	//useEffect에서 설정한 함수를 컴포넌트가 화면에 맨처음 렌더링이 될때만
	//실행하고 업데이트 될때는 실행하지 않으려면...
	//함수의 두번째 파라미터에 비어있는 배열을 넣어준다.
	// useEffect(() => {
	//     console.log('마운트 될때만 실행된다');
	// }, []);

	//특정값이 업데이트 될때만 실행하고 싶을때
	//두번째 파라미터로 전달되는 배열안에 검사하고 싶은 값을 넣는다.
	//배열 안에는 usetState를 통해 관리하고 있는 상태를 넣어줘도 됨.
	//props로 전달받은 값을 넣어줘도 된다.
	// useEffect(() => {
	//     console.log(name);
	// }, [name]);

	//컴포넌트가 언마운트 되기 전이나 업데이트 되기 직전에 
  // 어떤 작업을 수행하고 싶다면 useEffect에서 함수를 반환해줘야 한다.
	useEffect(() => {
		console.log('effect');
		console.log(name);

		return () => {
			console.log('클린');
			console.log(name);
		};
	}, [name]); 
// 만약 언마운트 될때만 호출하고 싶다면 빈배열을 넣으면 된다. 
// [name]---->[]

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

	return (
		<div>
			<div>
				<input value={name} onChange={onChangeName} />
				<input value={nickName} onChange={onChangeNickName} />
			</div>
			<div>
				<div>
					<b>이름 :</b>
					{name}
				</div>
				<div>
					<b>닉네임 :</b>
					{nickName}
				</div>
			</div>
		</div>
	);
};

export default Info;
// App.js

import React, { useState } from 'react';
import Info from './Info';

const App = () => {
	const [visible, setVisible] = useState(false);
	return (
		<div>
			<button
				onClick={() => {
					setVisible(!visible);
				}}
			>
				{visible ? '숨기기' : '보이기'}
			</button>
			<hr />
			{visible && <Info />}
		</div>
	);
};
export default App;
profile
프론트엔드 개발자

0개의 댓글