리액트 다루는 기술 정리 (1)

1.1 왜 리액트인가

슬랙, 아톰, vs code등도 일렉트론으로 만들었다.

일렉트론은 자바스크립트로 데스크톱 애플리케이션 만드는 프레임워크

자바스크립트의 활용도가 높아지며, 프론트엔드에서도 더 큰 프로젝트를 효과적으로 관리할 수 있도록 프레임워크가 등장하기 시작했다. ex) angular, vue.js, react.js 등

이러한 프레임워크는 주로 MVC 아키텍처나 MVVM 아키텍처를 사용. AngularJS의 경우 MVW(model-view-whatever)를 사용한다. 이러한 아키텍처가 가진 공통점은 모델과 뷰가 존재한다는 것이다. 모델은 앱의 데이터를 관리하는 부분이고, 뷰는 사용자에게 보이는 부분이다. 그리고 프로그램이 사용자에게서 작업을 받으면 컨트롤러가 모델 데이터를 조회하거나 수정해서 뷰에 반영한다.

그렇기 때문에 애플리케이션이 커질 수록 뷰를 찾아서 바꾸는 부분이 복잡해진다. 그래서 등장한 것이 리액트이다. 뷰의 특정 부분을 바꾸기보다는 기존 뷰를 날리고 처음부터 새로 렌더링하는 방식을 고안한 것이다. 그럼 앱 구조가 간단해지고, 코드량도 줄어든다. 하지만 계속 새로 렌더링을 하면 CPU사용량이나 성능적인 부분에서 우려가 될 수 있다. DOM은 느리고 새로 렌더링하면 데이터가 초기화되거나 끊김이 있을 수 있다.

1.1.1 리액트 이해

리액트는 JS 라이브러리로 MVC가 아닌 오직 View만 신경쓰는 라이브러리이다. 리액트는 컴포넌트를 활용하고, 컴포넌트는 재사용이 가능한 API로 많은 기능들을 가지고 있다. 위에서 말했던 리렌더링에 대한 한계와 우려를 리액트가 어떻게 해결했는지 알아보려면 리액트 컴포넌트에 대해 알아야 한다.

처음 보이기 위해 초기 렌더링 역할을 하는 함수가 render() 함수이다. 이 함수를 통해 컴포넌트가 어떻게 생겼는지 정의된다. 이 함수를 실행하면 그 내부의 컴포넌트들도 재귀적으로 렌더링된다.

  • 렌더링 → HTML 마크업 → DOM 주입

컴포넌트에서 데이터의 변화가 일어날 때는 뷰가 변형되는 것이 아니라, 새로 갈아끼워진다. 이것도 render() 함수가 담당한다. 이때 바로 DOM에 반영하지 않고, 이전의 render() 함수에서 만든 컴포넌트와 현재의 컴포넌트를 비교한다. 그리고 둘의 차이를 알아내서 최소한의 연산으로 DOM 트리를 업데이트 한다. 더 구체적으로 알기 위해서는 Virtual DOM을 알아야 한다.

1.2 리액트의 특징

1.2.1 Virtual DOM

기존에 웹 브라우저는 DOM을 활용하여 객체에 JS와 CSS를 적용했다. 하지만 DOM API의 치명적인 단점은 동적 UI에 최적화되어 있지 않다. 그렇기 때문에 대규모 앱일수록 DOM을 통해 직접 접근하여 변화를 주면 느려진다. DOM 자체는 빠르지만 DOM의 변화가 일어나면 브라우저는 CSS를 다시 계산해서 레이아웃을 다시 구성(Reflow)하고, 다시 그리는 작업(Repaint)을 하기 때문에 허비한다.

그렇기 때문에 리액트는 Virtual DOM을 사용해서 DOM 업데이트를 추상화해서 DOM 처리 횟수를 최소화하고 효율적으로 진행한다. 리액트가 실제 DOM을 업데이트하는 단계는 아래와 같다.

  1. 데이터를 업데이트 하면 UI를 Virtual DOM에 리렌더링
  2. 이전 Virtual DOM과 비교
  3. 바뀐 부분만 실제 DOM에 적용

1.2.2 기타 특징

다른 웹 프레임워크들과 다르게 리액트는 뷰만 담당하기 때문에 Ajax, 데이터모델링, 라우팅 등을 내장하고 있지 않다. 하지만 다른 라이브러리들과 함께 쓰면된다.

그리고 리액트는 다른 웹 프레임워크와 혼용할 수도 있다. (React + AngularJS 등)

2. JSX

웹팩과 같은 번들러 도구를 사용하면 import로 모듈을 불러왔을 때, 불러온 모듈을 합쳐서 하나의 파일로 생성이 가능하다. 모듈뿐만 아니라 CSS파일이나 SVG 파일도 웹팩의 로더(loader)를 통해 불러올 수 있다.

JSX는 자바스크립트의 확장 문법으로 공식적인 자바스크립트 문법은 아니다. 하지만 JSX를 이용해서 UI를 편하게 렌더링할 수 있다.

2.1 ESLint와 Prettier

2.1.1 ESLint

문법 검사 도구로 코드를 작성할 때 실수를 하면 에러 또는 경고 메시지를 IDE에서 확인할 수 있게 한다.

2.1.2 Prettier

코드 스타일 자동 정리 도구로 코드의 가독성과 코드 스타일 통일을 위해 사용한다.

3. 컴포넌트

3.1 클래스형 컴포넌트

클래스 문법을 사용한 클래스형 컴포넌트는 state 기능과 라이프사이클 기능, 임의 메서드를 정의할 수 있다는 부분에서 함수형 컴포넌트와 차이가 있다.

클래스형 컴포넌트에서는 render() 함수가 필수이다.

함수형 컴포넌트는 클래스형 컴포넌트보다 선언하기 편하고, 메모리 자원도 덜 사용한다. 그렇기 때문에 결과물의 파일 크기도 더 작다. (이 부분은 미미하기 때문에 고려하지 않아도된다.) 함수형 컴포넌트의 경우, state와 라이프사이클 API 사용이 불가한 것이 단점이었지만, Hooks가 도입되면서 같은 역할의 기능을 사용이 가능해졌다.

3.2 state

state는 컴포넌트 내부에서 바뀔 수 있는 값이다. props는 부모 컴포넌트가 설정하는 값이고 컴포넌트 자신은 props를 읽기 전용으로만 사용 가능하다.

4. 이벤트 핸들링

4.1 리액트 이벤트 사용시 주의사항

  1. 이벤트 이름은 카멜 표기법으로 작성(onClick, onPress, onKeyUp등)
  2. 이벤트에 실행할 함수 형태의 값을 전달한다.
  3. DOM 요소에만 이벤트를 설정할 수 있다. (컴포넌트에 불가, 컴포넌트엔 props로 전달됨)

4.2 input 여러개 다루기

input이 여러개일 때, 여러개의 메서드를 만드는 방법도 있지만, event 객체를 활용하는 방법이 있다. [e.target.name](http://e.target.name) 값을 사용하면 된다.

handleChange = (e) => {
	this.setState({
		[e.target.name] : e.targe.value
	});
}

---

<input
	type="text"
	name="username"
	value={this.state.username}
	onChange={this.handleChange}
/>

<input
	type="text"
	name="message"
	value={this.state.username}
	onChange={this.handleChange}
/>

객체 안에서 key를 [ ]로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key 값으로 사용된다.

5. ref : DOM에 이름 달기

HTML에서 DOM의 id는 유일(unique)해야 하는데, 리액트에서는 컴포넌트를 여러번 사용하면 id가 중복되게 되기때문에 유일성이 사라진다. ref는 전역적으로 작동하지 않고 컴포넌트 내부에서만 작동한다.

5.1 ref는 어떤 상황에서 사용해야 할까?

DOM을 꼭 직접적으로 건드려야 할 때 사용한다. 일반적으로는 state를 사용하면 해결할 수 있지만, 아래와 같은 상황에서는 state만으로 해결이 어렵다. 이때는 DOM에 직접적으로 접근해야하기 때문에 ref를 사용한다.

  • 특정 input에 포커스 주기
  • 스크롤 박스 조작하기
  • Canvas 요소에 그림 그리기 등

5.2 ref 사용

5.2.1 콜백 함수를 통한 ref 설정

<input ref={(ref) => {this.input=ref}}/>

5.2.2 createRef를 통한 ref 설정

input = React.createRef();

handleFocus = () => {
	this.input.current.focus();
}

render() {
	return (
		<div>
			<input ref={this.input}/>
		</div>
	);
}

ref 설정을 한 이후에 ref를 설정해 준 DOM에 접근하려면 this.input.current를 조회하면 된다. 콜백함수와 차이점은 .current 를 넣어 주어야 한다.

5.3 컴포넌트에 ref 달기

컴포넌트에도 ref를 달 수 있다. 주로 컴포넌트 내부의 DOM을 컴포넌트 외부에서 사용할 때 사용한다. 방법은 DOM에 ref를 다는 방법과 동일하다. 컴포넌트의 ref를 통해 컴포넌트 내부의 메서드 및 멤버 변수에도 접근할 수 있다.

<div>
	<ScrollBox ref={(ref} => this.scrollBox=ref/>
	<button onClick={() => {this.scrollBox.scrollToBottom()}}>
		맨 밑으로
	</button>
</div>

여기서 buttononClick 부분을 주목해봐야 하는데, 여기 화살표 함수를 사용한 것은 그냥 this.scrollBox.scrollToBottom 값을 읽어온다면 값이 undefined 이므로 오류가 발생하지만 화살표 함수를 사용함을 통해서 아예 새로운 함수를 만들고 그 내부에서 함수를 실행하면 값을 읽어와서 실행하므로 오류가 발생하지 않는다.

5.4 정리

ref는 컴포넌트 내부에서 DOM에 직접 접근할때 사용하는 것이지 서로 다른 컴포넌트끼리 데이터를 주고 받을때 사용하는 것이 아니다. 서로 다른 컴포넌트간에 ref를 사용하면 꼬여버릴 수 있다. 리덕스 혹은 Context API를 사용하면 효과적으로 데이터를 주고받을 수 있다. 함수형 컴포넌트에서는 useRef 라는 Hook 함수를 사용한다.

6. 컴포넌트 반복

6.1 key

리액트에서 컴포넌트 배열을 렌더링할 때는 key가 필요하다. key를 통해 어떤 원소에 변동이 있었는지 알아내려고 하기 때문이다. key가 없다면 Virtual DOM에서 리스트를 순차적으로 비교하지만 key가 있다면 이 값으로 어디서 변화가 일어났는지 더 빠르게 감지할 수 있다.

6.1.1 key 설정

key 값은 항상 유일해야 한다. 고유한 값이 없을 때만 index를 키로 사용해야 한다. index를 key로 사용할 경우 배열이 변경될 때 효율적으로 리렌더링될 수 없다.

상태 안에서 배열을 변형할 때는 배열에 직접 접근하여 수정하지말고 concat, filter등의 내장 함수를 사용해서 새로운 배열을 만들어 새로운 상태로 설정해 주어야 한다.


참고

  1. 리액트를 다루는 기술

Written by@[Ykss]
고이게 두지 않고 흘려보내는 개발자가 되자.

GitHubInstagramLinkedIn