@회고록~~
- 주특기 숙련 단계는 그 어느 때보다 바쁘고 손이 아픈 주인 것 같습니다. 회고록을 작성하는 당일은 주특기 3주차 3일째 되는 날입니다. 코딩 자체는 많이 어렵지 않지만 그 양이 많아 어려움이 있습니다. 군대에서 하루하루는 빠르지만 그 하루가 겁나 많다고 한 적이 있는데 이번 주와 다르지 않다는 생각이 들었습니다. 하지만 한걸음 한걸음 과제를 해결해 나아가는 부분이 힘든 와중에 힘이 나고 있습니다.
###ORM이란?
ORM은 Object Relational Mapping 즉, 객체-관계 매핑의 줄임말이다. 객체-관계 매핑을 풀어서 설명하자면 우리가 OOP(Object Oriented Programming)에서 쓰이는 객체라는 개념을 구현한 클래스와 RDB(Relational DataBase)에서 쓰이는 데이터인 테이블 자동으로 매핑(연결)하는 것을 의미한다. 그러나 클래스와 테이블은 서로가 기존부터 호환가능성을 두고 만들어진 것이 아니기 때문에 불일치가 발생하는데, 이를 ORM을 통해 객체 간의 관계를 바탕으로 SQL문을 자동으로 생성하여 불일치를 해결한다. 따라서 ORM을 이용하면 따로 SQL문을 짤 필요없이 객체를 통해 간접적으로 데이터베이스를 조작할 수 있게 된다.
##장점
완벽한 객체지향적인 코드
ORM을 이용하면 SQL문이 아닌 클래스의 메서드를 통해 데이터베이스를 조작할 수 있어, 개발자가 객체 모델만 이용해서 프로그래밍을 하는 데 집중할 수 있게한다. SQL 문을 사용하면서 같이 필요한 선언문, 할당, 종료 같은 부수적인 코드가 사라지거나 줄어들며, 각종 객체에 대한 코드를 별도로 작성하여 코드의 가독성을 높일 수 있다. 객체지향적 접근과 SQL의 절차적/순차적 접근이 혼재되어있던 기존 방식과 달리 오직 객체지향적 접근만 고려하면 되기때문에 생산성이 증가한다.
재사용, 유지보수, 리팩토링 용이성
ORM은 기존 객체와 독립적으로 작성되어있고, 객체로 작성되었기 때문에 재활용할 수 있다. 또한, 매핑하는 정보가 명확하기 때문에 ERD를 보는 의존도를 낮출 수 있다.
DBMS(DataBase Management System) 종속성 하락
객체 간의 관계를 바탕으로 SQL문을 자동으로 생성하고, 객체의 자료형 타입까지 사용할 수 있기 때문에 RDBMS의 데이터 구조와 객체지향 모델 사이의 간격을 좁힐 수 있다. 객체에만 집중할 수 있기 때문에 DBMS를 교체하는 큰 작업에도 리스크가 적고 드는 시간도 줄어든다. 예들 들어 자바에서 가공할 경우 equals, hashCode의 오버라이드 같은 자바의 기능을 이용할 수 있고, 간결하고 빠르게 가공할 수 있다.
##단점
ORM이 모든 걸 해결해줄 수 없다.
ORM을 사용하는 것은 매우 편리하지만 그만큼 신중하게 설계해야한다. 프로젝트의 복잡성이 커질 수록 난이도도 올라가고 부족한 설계로 잘못 구현되었을 경우 속도 저하 및 일관성을 무너뜨리는 문제점이 생길 수 있다. 또한 일부 자주 사용되는 대형 SQL문은 속도를 위해 별도의 튜닝이 필요하기 때문에 결국 SQL문을 써야할 수도 있다.
객체-관계 간의 불일치
다음과 같은 특성에서 객체-관계 간의 불일치가 생긴다.
세분성(Granularity)
경우에 따라서 데이터베이스에 있는 테이블 수보다 더 많은 클래스를 가진 모델이 생길 수 있다.
상속성(Inheritance)
RDBMS는 객체지향 프로그래밍 언어의 특징인 상속 개념이 없다.
일치(Identity)
RDBMS는 기본키(primary key)를 이용하여 동일성을 정의한다. 그러나 자바는 객체 식별(a==b)과 객체 동일성(a.equals(b))을 모두 정의한다.
연관성(Associations)
객체지향 언어는 방향성이 있는 객체의 참조(reference)를 사용하여 연관성을 나타내지만 RDBMS는 방향성이 없는 외래키(foreign key)를 이용해서 나타낸다.
탐색(Navigation)
자바와 RDBMS에서 객체를 접근하는 방법이 근본적으로 다르다. 자바는 그래프형태로 하나의 연결에서 다른 연결로 이동하며 탐색한다. 그러나 RDBMS에서는 일반적으로 SQL문을 최소화하고 JOIN을 통해 여러 엔티티를 로드하고 원하는 대상 엔티티를 선택하는 방식으로 탐색한다.
###SQL 이란?
SQL(Structured Query Language, 구조적 질의 언어)은 관계형 데이터베이스 시스템(RDBMS)을 제어하는 컴퓨터 언어입니다.
일반적인 프로그래밍 언어(범용 언어)와 달리 대화식 언어이기 때문에, 명령문이 짧고 간결합니다.
SQL 자체는 범용 언어에 비해 한계가 있기 때문에, 단독으로 사용하기 보단 C#, Java, Python, PHP와 같은 고수준 언어와 함께 쓰는 것이 일반적입니다.
##SQL 쿼리 문의 분류
SQL 쿼리문은 역할에 따라 3가지로 분류되며 아래와 같습니다.
- DDL(Data Definition Language, 데이터 정의어)
DB 오브젝트를 생성, 삭제, 변경하는 역할을 하며, DB 설계 단계에서 주로 사용됩니다. (CREATE, DROP, ALTER...) - DML(Data Manipulation Language, 데이터 조작어)
DB를 조회, 삽입, 삭제, 변경하는 역할을 하며, 관리 목적의 쿼리문입니다. (SELECT, INSERT, UPDATE...) - DCL(Data Control Language, 데이터 제어어)
사용자의 권한을 관리하는 역할을 합니다. (GRANT, DENY, REVOKE...)
이중 개발자가 일반적으로 DB를 사용할 때 사용하는 언어의 중요도는 DML > DDL > DCL 순입니다.
DB를 조회하고, 관리하는 DML을 가장 많이 사용하며, DB의 테이블의 스키마(설계 수준)를 수정하는 DDL을 그다음으로 많이 씁니다.
DCL은 DBA(DataBase Administration, 데이터베이스 관리자)가 주로 사용하며 일반 개발자는 사용할 일이 드뭅니다.
##SQL의 규칙
SQL은 DBMS에 따라 규칙이 약간은 달라질 수 있습니다. 하지만, 일반적인 규칙들은 아래와 같습니다.
( tTable이라는 가상의 테이블이 있다고 가정하고 예시를 작성하였습니다. )
###MVC란
MVC 는 Model, View, Controller의 약자 입니다. 하나의 애플리케이션, 프로젝트를 구성할 때 그 구성요소를 세가지의 역할로 구분한 패턴입니다.

출처: 오픈듀토리얼스
위의 그림처럼 사용자가 controller를 조작하면 controller는 model을 통해서 데이터를 가져오고 그 정보를 바탕으로 시각적인 표현을 담당하는 View를 제어해서 사용자에게 전달하게 됩니다. 저건 하나의 로직을 설명하기 위해 만든 그림이고 사실 MVC 패턴의 구조는

출처:XESCHOOL
이 그림이 더 어울릴 것입니다. Controller가 view에도 영향을 미치는(화살표를 보자) 부분이 있어야 합니다.

위의 그림을 보면서 다시 MVC 패턴이 뭔지 감을 잡도록 해봅시다. 모델은 컨트롤러에 컨트롤러는 뷰에 뷰는 다시 유저 유저는 다시 컨트롤러를 향해서 갑니다.
모델, Model
애플리케이션의 정보, 데이타를 나타냅니다. 데이타베이스, 처음의 정의하는 상수, 초기화값, 변수 등을 뜻합니다. 또한 이러한 DATA, 정보들의 가공을 책임지는 컴포넌트를 말합니다.
이 모델은 다음과 같은 규칙을 가지고 있습니다.
1. 사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 한다.
즉, 화면안의 네모박스에 글자가 표현된다면, 네모박스의 화면 위치 정보, 네모박스의 크기정보, 글자내용, 글자의 위치, 글자의 포맷 정보 등을 가지고 있어야 한다는 것입니다.
2. 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야 한다.
데이터 변경이 일어났을 때 모델에서 화면 UI를 직접 조정해서 수정할 수 있도록 뷰를 참조하는 내부 속성값을 가지면 안 된다는 말입니다.
3. 변경이 일어나면, 변경 통지에 대한 처리방법을 구현해야만 한다.
모델의 속성 중 텍스트 정보가 변경이 된다면, 이벤트를 발생시켜 누군가에게 전달해야 하며, 누군가 모델을 변경하도록 요청하는 이벤트를 보냈을 때 이를 수신할 수 있는 처리 방법을 구현해야 합니다. 또한 모델은 재사용가능해야 하며 다른 인터페이스에서도 변하지 않아야 합니다.
뷰, View
input 텍스트, 체크박스 항목 등과 같은 사용자 인터페이스 요소를 나타냅니다. 다시 말해 데이터 및 객체의 입력, 그리고 보여주는 출력을 담당합니다. 데이타를 기반으로 사용자들이 볼 수 있는 화면입니다.
뷰에서는 다음과 같은 규칙들이 있습니다.
1. 모델이 가지고 있는 정보를 따로 저장해서는 안된다.
화면에 글자를 표시 하기 위해, 모델이 가지고 있는 정보를 전달받게 될텐데, 그 정보를 유지하기 위해서 임의의 뷰 내뷰에 저장하면 안됩니다. 단순히 네모 박스를 그리라는 명령을 받으면, 화면에 표시하기만 하고 그 화면을 그릴 때 필요한 정보들은 저장하지 않아야 합니다.
2. 모델이나 컨트롤러와 같이 다른 구성요소들을 몰라야 된다.
모델과 같은 자기 자신의 빼고는 다른 요소는 참조하거나 어떻게 동작하는지 알아서는 안됩니다. 그냥 뷰는 데이터를 받으면 화면에 표시해주는 역할만 가진다고 보면 됩니다.
3. 변경이 일어나면 변경통지에 대한 처리방법을 구현해야만 한다.
모델과 같이 변경이 일어났을 때 이른 누군가에게 변경을 알려줘야 하는 방법을 구현해야 합니다. 뷰에서는 화면에서 사용자가 화면에 표시된 내용을 변경하게 되면 이를 모델에게 전달해서 모델을 변경해야 할 것이다. 그 작업을 하기 위해 변경 통지를 구현합니다.
그리고 재사용가능하게끔 설계를 해야 하며 다른 정보들을 표현할 때 쉽게 설계를 해야 합니다.
컨트롤러,Controller
데이터와 사용자인터페이스 요소들을 잇는 다리역할을 합니다.
즉, 사용자가 데이터를 클릭하고, 수정하는 것에 대한 "이벤트"들을 처리하는 부분을 뜻합니다.
컨트롤러 또한 다음과 같은 규칙을 이해해야 합니다.
1. 모델이나 뷰에 대해서 알고 있어야 한다.
모델이나 뷰는 서로의 존재를 모르고, 변경을 외부로 알리고, 수신하는 방법만 가지고 있는데 이를 컨트롤러가 중재하기 위해 모델과 그에 관련된 뷰에 대해서 알고 있어야 합니다.
2. 모델이나 뷰의 변경을 모니터링 해야 한다.
모델이나 뷰의 변경 통지를 받으면 이를 해석해서 각각의 구성 요소에게 통지를 해야 합니다.
또한, 애플리케이션의 메인 로직은 컨트롤러가 담당하게 됩니다.
MVC패턴을 사용 이유
사용자가 보는 페이지, 데이터처리, 그리고 이 2가지를 중간에서 제어하는 컨트롤, 이 3가지로 구성되는 하나의 애플리케이션을 만들면 각각 맡은바에만 집중을 할 수 있게 됩니다. 공장에서도 하나의 역할들만 담당을 해서 처리를 해서 효율적이게 됩니다. 여기서도 마찬가지입니다.
서로 분리되어 각자의 역할에 집중할 수 있게끔하여 개발을 하고 그렇게 애플리케이션을 만든다면, 유지보수성, 애플리케이션의 확장성, 그리고 유연성이 증가하고, 중복코딩이라는 문제점 또한 사라지게 되는 것입니다. 그러기 위한 MVC패턴입니다.
MVC패턴의 예
그렇다면 MVC패턴을 사용하는 프레임워크나 라이브러리는 뭐가 있을까요?
바로 다음과 같습니다!


react는 mvc 패턴만을 사용하다가 flux 패턴도 씁니다.(redux)
react는 mvc 프레임워크는 아니고 View만 신경쓰는 라이브러리입니다. 다른 웹프레임워크와는 달리 ajax, 데이터 모델링, 라우팅 같은 것이 없습니다. 그저 뷰만 신경쓰지요.
리액트는 단방향 데이터 흐름으로 데이터 변경에 관한 DOM객체만 변경해주는 체계, 데이타가 변경되면 양방향 데이터 바인딩처럼 모델 변경 > 뷰변경이 아니라 특정함수를 실행시킴으로써 DOM객체를 갱신합니다.
MVC패턴의 의의
MVC패턴은 결국 "어떻게 나눌 것인가"에 대한 해답 중 하나입니다. 어떤 특정한 역할들에 대해 역할분담을 할 때 가이드라인을 제시하는 방법 중 하나가 바로 MVC패턴이라는 것입니다.
그리고 이 패턴을 사용한 라이브러리나 프레임워크로 프로그래밍을 한다면 정말 쉽고 그리고 재밌는 경험을 느낄 수 있으며 아름다운 코드가 탄생하게 됩니다. 물론 우리는 그러한 라이브러리나 프레임워크를 만들 수 있는 실력또한 길러야 하지만요.
라이프사이클
모든 리액트 컴포넌트에는 라이프사이클이 존재한다. 컴포넌트의 라이프 사이클은 페이지 렌더링 전 준비과정에서 시작하여 페이지가 사라질 때 끝이 나게 된다. 리액트 프로젝트에서 컴포넌트를 처음 렌더링할 때 어떤 작업을 처리해야 하는지, 또는 컴포넌트를 업데이트 하기 전후로 어떤 작업을 처리해야 하는지를 알아야 불필요한 업데이트를 방지할 수가 있다. 리액트의 클래스 컴포넌트는 컴포넌트 라이프사이클 메서드가 있어서 이를 사용하고, 함수형 컴포넌트는 Hook을 사용한다. 이번 포스팅에서는 컴포넌트 라이프사이클에 대해서 다뤄보도록 한다.
라이프사이클 메서드의 종류는 총 9가지이다. Will 접두사가 붙은 메서드는 어떤 작업을 하기 전에 실행이 되며, Did 접두사가 붙은 메서드는 어떤 작업을 작동한 후에 실행이 된다. 이러한 메서드는 컴포넌트 클래스에서 덮어 써서 선언하고 사용할 수 있다. 라이프사이클은 총 세가지 카테고리로 나누어진다. 각각은마운트, 업데이트, 언마운트 카테고리로 나뉘며 지금부터 각각에 대해서 알아보도록 하자.

마운트
DOM이 생성되고 웹 브라우저 상에 나타나는 것을 마운트(mount)라고 한다.
- constructor : 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메서드
- getDerivedStateFromProps : props에 있는 값을 state에 넣을 때 사용하는 메서드
- render : UI를 렌더링 하는 메서드
- componentDidMount : 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메서드

업데이트
컴포넌트가 업데이트 되는 경우는 다음 네 가지 경우이다.
- props가 바뀔 때
- state가 바뀔 때
- 부모 컴포넌트가 리렌더링 될 때
- this.forceUpdate로 강제로 렌더링을 트리거 할 때
컴포넌트가 다음과 같은 이유로 업데이트가 될 때 호출되는 메서드는 다음과 같다.
- getDerivedStateFromProps : 이 메서드는 마운트 과정에서 호출되며, 업데이트가 시작하기 전에도 호출된다. props의 변화에 따라 state 값에도 변화를 주고 싶은 경우에 사용한다.
- shouldComponentUpdate : 컴포넌트가 리렌더링을 해야할지 말아야 할지 결정하는 메서드. true를 반환하면 다음 라이프사이클 메서드를 계속 실행하고, false를 반환하면 작업 중지. 만약 특정 함수에서 this.forceUpdate() 함수를 호출하면 이 과정을 생략하고 바로 render 함수를 호출.
- render : 컴포넌트 리렌더링
- getSnapshotBeforeUpdate : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출하는 메서드
- componentDidUpdate : 컴포넌트 업데이트 작업이 끝난 후 호출하는 메서드.

언마운트
마운트의 반대과정으로 컴포넌트를 DOM에서 제거하는 과정이다.
언마운트 시에는 componentWillUnmount 메서드 하나만 호출된다. 이 메서드는 컴포넌트가 웹 브라우저 상에서 사라지기 전에 호출하는 메서드이다.
지금부터는 컴포넌트 라이프사이클 메서드 9가지에 대해서 좀 더 자세하게 알아본다.
render() 메서드 : 컴포넌트의 모양을 결정한다. 이 메서드 안에서 this.props, this.state에 접근할 수 있으며, 리액트 요소를 반환한다. 주의할 점은 이 메서드 안에서는 setState를 사용할 수 없으며, DOM에 접근해서도 안된다. state를 바꾸거나 DOM의 정보를 가져오는 작업은 componentDidMount 메서드에서 해야 한다.
constructor 메서드 : 컴포넌트를 처음 만들 때 실행되며, 초기 state를 정할 수 있다. 리액트 컴포넌트의 생성자는 해당 컴포넌트가 마운트 되기 전에 호출된다. React.Component를 상속한 컴포넌트의 생성자를 구현할 때에는 다른 구문에 앞서 super(props)를 호출해야 한다.
리액트에서 생성자의 역할은 다음 두 가지가 있다.
- this.state에 객체를 할당하여 지역 state를 초기화
- 인스턴스에 이벤트 처리 메서드를 바인딩
constructor(props) {
super(props);
// 여기서 this.setState()를 호출하면 안 됩니다!
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
getDerivedStateFromProps 메서드 : props로 받아온 값을 state에 동기화 시키는 용도로 사용되며, 컴포넌트가 마운트되고 업데이트 될 때 호출된다.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.value !== prevState.value) { // 조건에 따라 특정 값 동기화
return {
value: nextProps.value
};
}
return null; // state를 변경할 필요가 없다면 null 반환
}
componentDidMount 메서드 : 컴포넌트를 만들고, 첫 렌더링을 마친 후에 실행된다. 이 안에서 다른 JS 라이브러리 또는 프레임워크의 함수를 호출하거나 이벤트 등록, setTimeout, setInterval, 네트워크 요청 등 비동기 작업을 처리하면 된다. 데이터 구독은 이 위치에서 해주는 것이 바람직하다.
이 위치에서 setState()를 호출하는 경우도 발생한다. 이로 인해 추가적인 렌더링이 발생하지만, 브라우저가 화면을 갱신하기 전에 이루어진다. 이 때 render()가 두 번 호출되나, 사용자는 그 중간과정을 볼 수가 없다. 이후 성능 문제로 이어질 수 있기 때문에 주의가 필요하다.
shouldComponentUpdate 메서드 : props 또는 state를 변경했을 때, 리렌더링을 시작할지 여부를 지정하는 메서드이다. 이 메서드에서는 반드시 true 혹은 false 값을 반환해야 한다. 컴포넌트를 만들 때 이 메서드 값을 따로 생성하지 않으면 기본적으로 true를 리턴하며, 이 메서드가 false를 반환하면 업데이트 과정은 여기서 중지된다.
shouldComponentUpdate(nextProps, nextState) { ... }
이 메서드 안에서 현재 props와 state는 this.props와 this.state로 접근하고, 새로 설정될 props와 state는 nextProps와 nextState로 접근할 수 있다. 프로젝트 성능을 최적화 할 때는 리렌더링을 방지하기 위해서 false 값을 반환하도록 한다.
getSnapshotBeforeUpdate 메서드 : 이 메서드는 render에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출된다. 이 메서드에서 반환하는 값은 componentDidUpdate에서 세 번째 파라미터인 snapshot 값으로 전달받을 수 있다. 주로 업데이트하기 직전의 값을 참고할 일이 있을 때 활용한다.
getSnapshotBeforeUpdate(prevProps, prevState) {
if(prevState.array !== this.state.array) {
const { scrollTop, scrollHeight } = this.list
return { scrollTop, scrollHeight };
}
}
componentDidUpdate 메서드 : 리렌더링을 완료 후 실행한다. 업데이트가 끝난 직후이므로 DOM 관련해서 처리를 해도 무방하다. 여기서는 prevProps 또는 prevState를 사용하여 컴포넌트가 이전에 가졌던 데이터에 접근할 수도 있다. 그리고 getSnapshotBeforeUpdate에서 반환한 값이 있다면 snapshot 값을 전달받을 수도 있다.
componentDidUpdate(prevProps, prevState, snapshot) { ... }
이 메서드에서 setState()를 즉시 호출할 수도 있지만, 조건문으로 감싸주지 않을 경우 아래처럼 무한 반복이 발생할 수도 있음에 주의한다. 또한 추가적인 렌더링을 유발하여 컴포넌트 성능에 영향을 미칠 수도 있다.
componentDidUpdate(prevProps) {
// 전형적인 사용 사례 (props 비교를 잊지 마세요)
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
componentWillUnmount 메서드 : 컴포넌트를 DOM에서 제거할 때 실행한다. 이후에 컴포넌트는 다시 렌더링되지 않으므로 여기에서 setState()를 호출해 주면 안된다.
componentDidCatch 메서드 : 컴포넌트 렌더링 도중 에러가 발생했을 때 어플리케이션이 오류 UI를 보여줄 수 있게 해준다.
componentDidCatch(error, info) {
this.setState({
error: true
});
console.log({ error: info });
}
여기에서 error는 파라미터에 어떤 에러가 발생했는지를 알려주며, info 파라미터는 어디에 있는 코드에서 오류가 발생했는지에 대한 정보를 준다.

리액트 훅(Hook)
리액트 훅을 도입하게 된 목적은 여러가지가 있다. 먼저 컴포넌트에서 상태관련 로직을 사용할 때 레이어 변화 없이 재사용할 수 있게하기 위함이 첫번째 목적이다. 기존에는 여러가지 레이어로 둘러 쌓여있어서 구조가 복잡했기 때문이다. 두 번째 목적은 기존의 라이프사이클 메서드 기반이 아닌 로직 기반으로 나눌 수 있어서 컴포넌트를 함수 단위로 잘게 쪼갤 수 있다는 이점 때문이다. 그 외에도 클래스 기반 컴포넌트를 지양하고자 하는 목적 등도 있다.
useState
useState는 가장 기본적인 Hook이며, 함수형 컴포넌트에서 가변적인 상태를 지닐 수 있게 해 준다. 함수형 컴포넌트에서 상태를 관리해야 할 때 사용한다.
import React, { useState } from 'react';
function Example() {
// 새로운 state 변수를 선언하고, count라 부르겠습니다.
// count의 초깃값은 0으로 지정합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
state는 원시타입 뿐만 아니라 객체로 사용할 수도 있다. 여러개의 useState를 사용할 수도 있지만, 이와 같이 하나의 state에 여러 프로퍼티를 추가해서 두 가지 이상의 상태를 관리할 수도 있다.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = useState({
name: "John",
id: 0
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<h3>name: {state.name} </h3>
<h3>id: {state.id} </h3>
<button onClick={() => setState({ ...state, id: state.id + 1 })}>
Increase
</button>
</div>
);
}
useEffect
useEffect는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다. 클래스형 컴포넌트의 componentDidMount와 componentDidUpdate, componentWillUnmount를 합친 형태로 보아도 된다. 이 훅을 통해서 함수형 컴포넌트에서 사이드 이펙트(side effect)를 수행할 수 있는데, 여기서 사이드 이펙트는 데이터 가져오기, 구독 설정, 수동으로 DOM 조작 등을 말한다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 같은 방식으로
useEffect(() => {
// 브라우저 API를 이용하여 문서 타이틀을 업데이트합니다.
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect는 리액트에서 컴포넌트 렌더링 이후 어떠한 일을 수행해야 하는지 말해준다. 우리가 넘긴 함수(effect라고 부름)를 기억했다가, DOM 업데이트 이후 불러온다. 이렇게 컴포넌트 안에서 불러오게 될 경우 effect를 통해 state나 props에 접근할 수 있게 된다. useEffect는 컴포넌트의 첫 번째 렌더링과 그 이후 모든 업데이트에서 수행이 된다.
만약에 useEffect에 설정한 함수를 매번 업데이트마다 수행시키지 않으려면 어떻게 해야 할까? 업데이트 될 때 실행하지 않으려면 함수의 두 번째 파라미터로 비어 있는 배열을 넣어 주면 된다. 그리고 만약 특정 값이 업데이트 될 때만 useEffect를 실행하고 싶다면 두 번째 파라미터 배열에 해당 값들을 넣어주면 된다.
// 첫 렌더링 때만 useEffect 실행
useEffect(() => {
console.log('마운트 될 때만 실행');
}, []);
// 특정 값(name)이 바뀔 때만 useEffect 실행
useEffect(() => {
console.log(`${name}이 바뀔 때만 실행`);
}, [name]);
컴포넌트가 언마운트 되기 전이나 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리 함수(clean-up)를 반환해 주어야 한다. 예를 들면 외부 데이터에 구독(subscription) 설정을 해야 하는 경우 메모리 누수가 발생하지 않도록 clean-up을 해 주어야 한다.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
리액트에서 뒷정리를 하는 시점은 컴포넌트가 마운트를 해제하는 시점이다. effect는 렌더링이 실행될 때마다 실행되는데, 그렇기 때문에 다음 effect를 실행하기 전에 이전 렌더링에서 파생된 effect를 정리해 주어야 할 필요가 있다.
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook이다. (state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 state를 반환한다. 하윗값이 복잡한 정적 로직을 만들거나, 다음 state가 이전 state에 의존적인 경우 보통 useState 대신 useReducer를 사용한다. 또한 useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화 할 수 있는데, 이것은 Callback 대신 dispatch를 전달할 수 있기 때문이다.
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useReducer의 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어준다. 이 Hook을 사용하면 state값과 dispatch 함수를 받아온다. 여기서 state는 현재 가리키고 있는 상태고, dispatch는 액션을 발생시키는 함수이다. dispatch(action)과 같은 형태로 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출되는 구조이다.
초기화를 조금 지연할 수도 있는데 init 함수를 세 번째 인자로 전달하면 된다. 이는 reducer 외부에서 초기 state를 계산하는 로직을 추출할 수 있도록 한다. 또한 어떤 행동에 대한 대응으로 나중에 state를 재설정하는 데에도 유용하다.
useMemo
useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다. 이 Hook은 메모이제이션 된 값을 반환한다. useMemo는 의존성이 변경되었을 때만 메모이제이션 된 값을 다시 계산한다. 이 최적화는 모든 렌더링 시 고비용 계산을 방지하게 해 준다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo로 전달된 함수는 렌더링 중에 실행이 된다. 따라서 렌더링 중에 하지 않는 작업은 이 함수 내에서 할 수 없다. 예를 들어 사이드 이펙트에 대한 처리는 useEffect에서 해 주는 식으로 말이다. useMemo가 성능 최적화를 위해서 사용하는 것은 맞지만, 가능하면 useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하는 것이 더 바람직하다.
useCallback
useCallback은 메모이제이션 된 콜백을 반환한다. 주로 렌더링 성능을 최적화 해야 하는 상황에서 사용하는데, 이 Hook을 통해서 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
인라인 콜백과 그것의 의존성 값의 배열을 전달하면 useCallback은 콜백의 메모이제이션된 버전을 반환한다. 그 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때만 변경된다. 이는 불필요한 렌더링을 방지하기 위해 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용하다. useMemo와 비슷한 역할을 하고 useCallback은 결국 useMemo로 함수를 반환하는 상황에서 더 편하게 사용할 수 있는 훅이다. 숫자, 문자열, 객체 처럼 일반 값을 재사용하려면 useMemo를 사용하고, 함수를 재사용하려면 useCallback을 사용한다.
useRef
const refContainer = useRef(initialValue);
useRef는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다. useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환한다.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
본질적으로 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 상자와 같다. 일반적으로 DOM의 접근하는 방법으로 refs를 익숙해 한다. 만약 리액트에서 ref 객체를 전달한다면 리액트는 모드가 변경될 때 마다 변경된 DOM 노드에 .current 프로퍼티를 설정할 것이다.
ref 속성을 사용하는 것보다 useRef() 훅을 사용하는게 더 유용한데, 그 이유는 useRef()가 순수 자바스크립트 객체를 생성하기 때문이다. useRef()와 {current: ...} 객체를 생성하는 것의 차이점은 useRef는 매번 렌더링을 할 때 동일한 ref 객체를 제공한다는 점이다.
하지만 useRef는 내용이 변경될 때 그것을 알려주지는 않는다. .current 프로퍼티를 변형하는 것이 리렌더링을 발생시키지는 않기 때문이다. 만약 리액트가 DOM 노드에 ref를 attach 하거나 detach할 때 어떤 코드를 실행하고 싶다면 callback ref를 사용하는 것을 권장한다.
useContext
const value = useContext(MyContext);
useContext는 context 객체(React.createContext)를 받아 그 context의 현재 값을 반환한다. context의 현재 값은 트리 안에서 이 Hook을 호출하는 컴포넌트의 가장 가까이에 있는 <MyContext.Provider>의 value prop에 의해 결정된다.
컴포넌트에서 가장 가까운 가 갱신되면 useContext는 에게 전달된 가장 가까운 context value를 사용하여 렌더러를 트리거 한다.상위 컴포넌트에서 React.memo나 shouldComponentUpdate를 사용하더라도 useContext를 사용하고 있는 컴포넌트 자체에서부터 다시 렌더링이 된다. 항상 인자는 context 객체 그 자체여야 한다.
useContext를 호출한 컴포넌트는 context 값이 변경되는 항상 리렌더링 된다. 따라서 이 비용이 많이 들면 메모이제이션을 통해 최적화를 할 수도 있다.
'항해99' 카테고리의 다른 글
| [TIL]항해 31일! 그리고 TIL 2일차! (0) | 2022.02.10 |
|---|---|
| [TIL]항해 30일! 그리고 TIL 1일차! (0) | 2022.02.09 |
| [Computer Science] 6-1 저수준I/O (0) | 2022.02.06 |
| [Computer Science] 5-1 기본적인 구조 요소들 (0) | 2022.02.01 |
| [WIL] 3주차 항해 회고록~! (0) | 2022.01.30 |