[Kakao Cloud School] 6번째 회고록

lango·2022년 12월 13일
0
post-thumbnail

Intro


협업하기 위한 자세를 갖추자.

스터디 모임을 하던 동료들과 미니 프로젝트를 진행하기로 하여 함께 팀을 꾸리게 되었다.

이전에 프로젝트 경험이 없던 것은 아니지만 이번 팀 프로젝트는 제대로 준비하고 협업하여 기대한 만큼의 산출물을 만들어내고 싶은 마음이 가장 크다. 왜냐? 개발자로 지내오면서 제대로 된 팀 프로젝트를 진행해본 적이 없어 설레기도 하고 부담도 되었지만 설계부터 제대로 짜보고 싶었다.

화면 설계부터 UI/UX 설계, 데이터 모델링, API 설계 등 실제 키보드에 손을 올리고 코딩하기 전부터 할 것이 너무나도 많다. 먼저 팀원들과 무엇을 하고 싶은지, 왜 하고 싶은지, 추후 확장할 수 있는지를 중점으로 이야기해보면서 첫단추를 잘 끼울 것이다.

팀원들과 협업하는 과정 속에서 의견 충돌이나, 잘못된 방향으로 흘러갈 수도 있다. 그때마다 객관적으로 현 상황을 바라볼 수 있도록 노력하고 알고 있는 지식들이 레거시하지는 않은지에 대해서 먼저 리소스를 검색하고 공유하는 습관을 들이자.




Day - 24

view 단을 구현할 수 있는 react를 배우기 시작했다. 백엔드 분야를 집중적으로 배우고 있지만 프론트엔드 개발자와의 협업을 위해선 프론트엔드에서 활용하는 다양한 기술들을 꼭 어느정도 알고 있어야 한다고 생각한다.


React.js 입문

원래 react는 프론트엔드에서 필수적으로 다루야 하는 라이브러리 중 하나로써 SPA 구현을 쉽게 해주는 정도로 알고 있었다.

그런데 react가 JavaScript를 통해 어떤 흐름으로 구성되는지. 서버 단과 맞물려 데이터를 어떻게 주고받는지 등에 대한 구체적인 내용에 대해서는 잘 몰랐다. 그래서 react에 대해서 배우고 궁금했던 것들에 대해서 알아보려 한다.

React.js란?
JavaScript를 통해 view를 만들기 위한 라이브러리이다. SPA을 기반으로 하고 있으며, Dirty checking과 Virtual DOM을 활용하여 업데이트 해야하는 DOM 요소를 찾아서 해당 부분만 업데이트하기 때문에, 리렌더링이 잦은 동적인 모던 웹에서 엄청나게 빠른 퍼포먼스를 내는게 가능하다.

SPA(Single Page Application)란?
단일 페이지 애플리케이션이라는 뜻으로 전체 화면을 리렌더링하지 않고 변경된 일부분만을 리렌더링 할 수 있는 컴포넌트 기반의 애플리케이션 구현을 위한 라이브러리이다. 화면을 만드는 부분을 제외하고 별도의 패키지를 설치하여 애플리케이션을 제작하는 방식이다.

React 구성요소에는 무엇이 있을까?

react의 출력과정을 간단하게 설명하면 Virtual DOM이라는 개념을 사용하여 메모리 상에 DOM을 만들고, 현재 화면에 출력된 DOM과 비교를 하여 변경된 부분만 리랜더링 하는 방식으로 진행된다. 이러한 react에서의 Component, State와 같은 핵심적인 요소들을 알아보고 정리해보자.

Component(컴포넌트)란?

react로 만들어진 애플리케이션을 이루는 최소한의 단위, 화면을 구성하는 단위이다. 컴포넌트는 데이터(props)를 입력받아 View(state) 상태에 따라 DOM에 출력하는 함수로서의 역할을 수행하며 UI를 재사용 가능한 개별적인 여러 조각으로 나누고, 각 조각을 개별적으로 나누어서 조작한다.

View(화면) > Component(Segment-Control을 1개 이상 모아서 만든 논리적인 화면 구성 단위) > Control(작은 부품 하나)

Component를 생성하는 방법은?

  • Class Component(클래스 구성)
    Component라는 클래스로부터 상속을 받아 render라는 함수에 출력할 내용을 리턴하는 형식으로 생성한다. 멤버 변수나 수명주기 메서드를 사용하는 것이 편리하다.

  • Function Component(함수 구성)
    출력할 내용을 리턴한다. 클래스로 만드는 것보다 가볍고 속도가 빠르다. 최근에는 함수로 구성하는 방식을 주로 사용한다.

props란?

props는 properties의 줄임말인데, 특정값을 컴포넌트에게 전달해야할 때 즉, 상위 컴포넌트가 하위 컴포넌트에 값을 전달할때 사용한다. 또한, props는 읽기전용 데이터로써 수정할 수 없다는 특징이 있다.

State란?

State는 일반적으로 컴포넌트의 내부에서 변경 가능한 데이터를 관리해야할 때에 사용된다. props가 읽기전용 데이터인데 비해, 값을 변경해야 할 때 state를 사용한다고 생각하면 된다. 기본값을 설정할 수 있으며 setter 메서드를 이용해 데이터 변경 작업도 가능하다.

State는 변수?

state는 간단하게 말하자면 하나의 변수로써 사용되는데, 그렇다면 var, let, const와 같은 키우드로 선언한 변수와 state로 선언한 변수는 무슨 차이가 있을지 궁금할 것이다.

state는 일반 변수와 다르게 값이 변하게 되면 렌더링이 발생한다. 이 말은 값이 변하게 되면 연관있는 컴포넌트들이 다시 렌더링이되어 화면이 바뀌게 된다는 것이다.

결국 특정 값에 변경점이 생긴다면 리렌더링 하지 않고 조작할 수 있어야 하기에 값을 변경할 때는 react에서 제공하는 함수를 사용하는 것이다. 렌더링 여부가 위에서 말했던 var, let const 키워드로 선언한 변수와 state와의 가장 큰 차이점이 아닐까 생각이 들었다.

setState와 useState란?

클래스형 컴포넌트에서 사용하는 Hook인 setState와 함수형 컴포넌트에서 사용할 수 있는 Hook인 useState가 있는데 무슨 차이가 있는지 궁금해서 찾아보았다.

his.state.xxx = xxx과 같은 구문으로 변경하는 것이 아니라 state
setState와 useState와 같이 state값을 변경할 때 사용하는 함수들을 사용한다. setState는 state를 변경시켜주는 함수이고, useState는 state, setState를 return 하면서 초기값을 설정해주는 Hook이다.

클래스형 컴포넌트에서는 this.state.이름 형식으로 생성하고 this.setState(이름:수정할 값)으로 수정한다. 함수형 컴포넌트에서는 const [이름, setter이름] = useState(초기값) 형식으로 생성하고 setter이름으로 수정한다.

Ref란?

ref는 컴포넌트를 조작하고자 할 때 사용한다.
HTML에서는 DOM요소에 어떤 작업을 수행하고자 하는 경우 <div id="my-id"> 와 같이 id를 부여하고 document.getElementById 함수를 통해 접근하였는데, react에서는 ref로 id를 대체하여 DOM을 직접적으로 핸들링할 수 있다. 다만, 직접 DOM을 조작한다면 속도가 느린 편이기에 권장하지는 않는다.

어떤 작업에서 ref를 사용할까?
React에서 state로만 해결할 수 없고, DOM을 반드시 직접 건드려야 할 때 사용하게 됩니다. 예를 들어서 특정 input에 focus 주거나 스크롤 박스 조작하고, Canvas 요소에 그림을 그리는 등의 작업에 사용할 수 있다.

Reac는 무엇이고 어떤 요소들을 가지는지 간단하게 알아보았다. 추후 React로 프론트엔드 단을 구축할 일이 많을 것 같은데, 프론트엔드 동료와 협업하기 위해 기본적인 내용 정도는 알아두는 것이 좋을 것 같다.



yarn과 npm의 차이점은?

yarn은 node.js 런타임 환경을 위해서 FaceBook이 개발한 패키지 관리 시스템이다. 그런데 NPM이 존재하는데도 YARN이 개발된 이유가 있지 않을까? yarn은 npm과 무슨 차이가 있는 걸까?

  • 병렬 설치 가능
    NPM에서는 여러 패키지를 설치할 때 하나의 패키지가 완전히 설치될 때까지 기다리고 다음 패키지를 설치하는데, YARN은 설치 작업을 병렬로 진행하기 때문에 훨씬 빠른 설치 속도를 보여준다.

  • lock 파일 자동화
    NPM은 종속성이 추가될 경우 lock 파일을 만들지 않고 별도의 명령어를 통해서 만들 수 있다. YARN은 yarn.lock 파일을 자동으로 추가해준다.

  • 보안 이슈
    NPM은 다른 패키지를 즉시 포함시킬 수 있는 코드를 자동으로 실행하므로, 보안 시스템에 여러 가지 취약성이 발생한다. 반면에, YARN은 yarn.lock 또는 package.json 파일에 있는 파일만 설치한다. 따라서 YARN이 NPM 패키지보다 보안이 강화된 것으로 추측되고 있다.

어쨋든 npm이 가지고 있는 기능들을 그대로 사용할 수 있으면서도 npm보다 강화된 기능들을 사용할 수 있기 때문에 yarn을 사용해도 좋을 것 같다고 느꼈다.



객체들 간의 관계 is-a? has-a?

is-a(상속관계)

is-a는 말 그대로 A는 B이다일 때의 ~이다와 같다. 위키피디아에서는 다음과 같이 설명하고 있다.

is-a는 추상화(형식이나 클래스와 같은)들 사이의 포함 관계를 의미하며, 한 클래스 A가 다른 클래스 B의 서브클래스(파생클래스)임을 이야기합니다. 다른 말로, 타입 A는 타입 B의 명세(specification)를 암시한다는 점에서 타입 B의 서브타입이라고도 할 수 있습니다.

is-a 관계방식은 상위 클래스의 인스턴스가 만들어지고 하위 클래스의 인스턴스가 만들어지는데 하위클래스에는 상위클래스에 대한 포인터(super)가 존재한다.

상속관계를 사용할 때 주의할 점은 하위클래스가 상위클래스에 종속되기에 유의해야하며, 단순히 코드를 재사용할 목적으로 서로 관련이 없는 개념의 클래스를 상속 관계로 사용하는 것은 좋지 않고 클래스들의 관계가 명확할 경우 사용하는 것이 바람직하다.

has-a 포함(포함관계)

has-a 관계를 정리하면 A는 B를 가지고 있다라는 뜻으로, 다른 객체를 받아서 그 객체의 기능을 사용하는 것인데 즉, 다른 클래스의 기능(변수 혹은 메서드)을 받아들여 사용하는 것이다. 위키피디아를 살펴보면 다음과 같이 설명하고 있다.

has-a는 구성 관계를 의미하며 한 오브젝트(구성된 객체, 또는 부분/멤버 객체라고도 부릅니다)가 다른 오브젝트(composite type이라고 부릅니다)에 "속한다(belongs to)"를 말합니다. 단순히 말해, has-a 관계는 객체의 멤버 필드라고 불리는 객체를 말하며, Multiple has-a 관계는 소유 계층구조를 형성하기 위해 결합하는 경우를 말합니다.

has-a 관계방식은 하나의 인스턴스 안에 다른 인스턴스를 생성하는 방식이다. 주의할 점은 외부 인스턴스 안에서 내부 인스턴스가 생성되기에 외부에서 내부를 사용할 땐 큰 문제가 없지만, 내부에서 외부의 인스턴스를 사용할 경우 내부 인스턴스를 생성할 때 외부 인스턴스의 참조를 알려주어야 한다.

is-a VS has-a

is-a 관계를 통해 생성된 클래스나 객체는 상속 관계를 가지게 되어 높은 결합도를 지닌다. 둘 중에 하나의 클래스에서 변경점이 발생하면 코드가 손상될 위험이 있다. 대신, 상위클래스의 기능을 하위클래스가 물려받아 사용할 수 있는 장점도 있기에 클래스 계층구조 측면에서 안정적이다.

또한, has-a 방식으로 생성된 클래스나 객체는 낮은 결합관계를 가지게 된다. 관계된 클래스 간 변경점이 발생하더라도 구성 요소를 쉽게 변경할 수 있다는 것이다. 이는 유지보수 차원에서 더 많은 유연성을 제공한다. 하지만, is-a 상속관계보다 좋은 성능을 보여주지는 않으며 더 복잡한 구조가 될 수도 있다.

is-a 관계 생성과 has-a 관계 생성에 있어서 정답이 있다면 좋겠지만 어떤 경우에 상속을 사용하고, 어떤 경우에 구성 관계를 선택하는지에 따라 is-a 관계방식이 정답이 될수도 has-a 관계방식이 정답이 될 수도 있다.

클래스 간의 상속관계가 명확하다면 is-a 관계방식을 사용하는 것이 좋다. 예시는 다음과 같다.

사람은 인간이다.
강아지는 동물이다.
게시글은 엔티티이다.

has-a 관계방식은 하나의 클래스가 다른 클래스를 가지고있는 경우에 사용하는 것이 좋다. 예시는 다음과 같다.

사탕은 단맛을 가지고 있다.
전투기는 미사일을 가지고 있다.
사람은 뇌를 가지고 있다.

예시와 같이 객체간 is-a 상속관계가 애매하거나 확실치 않다면, has-a 관계를 통해 구성하는 것이 더 나은 솔루션이 될 수 있다고 생각이 들었다.



Day - 25

React의 Component를 통해 화면의 일부분을 즉각적으로 변경시킬 수 있다는 것을 알게 되었다. 전체 화면을 다시 렌더링하지 않고 즉, 새로고침 없이 동적으로 페이지를 바꾼다는 것이 흥미롭고 재미있었다.


map() 메소드

어떤 배열에 있는 모든 요소들의 값을 변경해서 만든 새로운 배열을 써야 할 경우가 종종 발생하는데 반복문을 사용하여 배열에 대해 수동으로 반복 처리하는 대신, Array 패키지에서 제공하는 map() 메소드를 사용하는 것이 더 좋다.

Array.map() 메소드는 콜백 함수를 이용해 요소마다 호출(실행)되어 그 값을 배열로 변환하여 반환해준다.

let arr = [1, 2, 3, 4, 5];

위와 같은 배열이 있다고 했을 때 각 원소에 +5를 해야한다고 하면 보통은 반복문을 활용하게 된다.

let size = arr.length;
for(let i=0; i<size; i++) {
	arr[i] = arr[i] + 5;  
}
// [6, 7, 8, 9, 10]

그런데 Array.map() 메소드를 사용한다면 더 가독성 좋은 코드로 동일한 결과를 얻을 수 있다.

let result = arr.map(data => data + 5);
console.log(result);
// [6, 7, 8, 9, 10]

map 함수의 구조

arr.map(callback, [thisArg])

  • callback 함수의 파라미터
    • currentValue: 현재 처리하고 있는 요소
    • index: 현재 처리하고 있는 요소의 index
    • array: 현재 처리하고 있는 배열
  • thisArg: callback 함수 내부에서 사용할 this의 참조

항상 습관처럼 for문을 통한 반복문을 주로 사용했었는데, map 메서드를 사용해보니 확실히 코드 라인수도 줄일 수 있을 뿐만 아니라 작성한 코드의 가독성도 향상되었음을 알 수 있었다.



Component의 Life Cycle

리액트의 컴포넌트에는 라이프사이클(생명 주기)이 존재하는데 컴포넌트의 수명은 페이지에 렌더링되기 전인 준비과정에서 생성되어 페이지에서 사라질 때 소멸한다. 생성은 생성자(Constructor)가 담당하고, 소멸은 소멸자(Destructor)가 담당한다.

컴포넌트를 최초 렌더링시 특정한 작업을 처리하거나, 컴포넌트를 변경하기 전후로 작업을 처리하거나, 불필요한 변경을 막는 등의 작업을 해야 할 수도 있는데 이 때 컴포넌트의 생명주기 메서드를 사용하게 된다.

Component의 수명 주기

Mount > Update > Unmount

  • Mount: 컴포넌트가 메모리 할당을 받아서 출력
    • constuctor(생성자): 가장 먼저 호출됨.
    • getDerivedStateFromProps: props에 있는 값을 state에 동기화하는 메서드
    • render: UI를 렌더링하는 메서드
    • componentDidMount: Component가 웹 브라우저상에 나타난 후 호출하는 메서드, 비동기 작업(네트워크 사용이나 타이머 작업) 수행
  • Update: 컴포넌트 정보를 업데이트하는 것으로 리렌더링
    • getDerivedStateFromProps: 이 메서드는 마운트 과정에서도 호출하며 props가 바뀌어서 업데이트할 때도 호출
    • shouldComponentUpdate: Component가 리렌더링을 해야 할지 말아야 할지를 결정하는 메서드로 false를 리턴하면 아래 메서드들을 호출하지 않음
    • render: Component를 리렌더링
    • getSnapshotBeforeUpdate: Component 변화를 DOM에 반영하기 바로 직전에 호출하는 메서드
    • componentDidUpdate: Component의 업데이트 작업이 끝난 후 호출하는 메서드
  • Unmount: 컴포넌트가 화면에서 제거
    • componentWillUnmount: Component가 웹 브라우저상에서 사라지기 전에 호출하는 메서드, momory leak이 발생할 수 있는 객체의 제거 작업을 수행한다.(타이머 등)

Life Cycle 적용시 주의할 점
React의 개발모드가 React.StrictMode로 설정되어 있으면 개발 환경에서는 Mount를 2번씩 하게 된다.



Hooks(훅)

Class Component의 기능을 Function Component에서 사용할 수 있도록 하는 기능이다.

  • useState
    state를 함수형 컴포넌트 내에서 사용할 수 있게 해주는 훅이다. useState의 매개변수는 state의 초기값이 되며 리턴하는 데이터는 state와 state의 값을 변경할 수 있는 setter함수의 배열이다.

  • useRef
    ref란 컴포넌트를 조작하기 위해서 붙이는 일종의 id와 같은 변수를 뜻하며 useRef로 만들어진 변수는 값이 변경되도 컴포넌트가 리렌더링되지 않는다.

  • useEffect
    useEffect란 state가 변경된 후 수행할 side effect를 설정하는 훅이다. state 값을 읽을 수 있고 수정할 수 있다. Mount될 때, Update 될 때, Unmount될 때를 처리한다.

    useEffect(() => {수행할 내용} ,deps배열 []) 형식으로 작성하면 되는데, deps 배열을 생략하면 mount된 경우와 모든 state가 변경될 때마다 호출되고, desps 배열을 비워두면 mount된 후에만 호출된다. 또한, deps 배열에 state를 설정하게 되면 state 값이 변경될 때도 호출된다.

    수행할 내용에서 함수를 리턴하면 cleanup함수로 동작한다. (cleanup함수는 Unmount될 때 호출되는 함수이다.)

  • useMemo
    함수 호출의 효율성을 위해서 사용하는데, 함수를 호출하는 부분을 useMemo로 감싸고 두번째 매개변수로 데이터를 설정하면 변경된 경우는 함수를 호출하지만 데이터가 변경되지 않았다면 함수를 호출하지 않고 함수의 결과를 재사용한다.

    연산된 값을 제공하는 훅으로써, 성능 최적화를 위해 사용된다. 매개변수로 연산을 수행하는 함수와 배열을 대입받는데, 배열에 변경점이 있는 경우에만 함수를 수행하고 그렇지 않다면 함수를 호출해도 결과만 제공한다.

  • useCallback
    특정 함수를 새로 만들지 않고 재사용하고자 할 때 사용한다. 컴포넌트에 구현한 함수들은 컴포넌트가 렌더링될 때마다 다시 생성되는데, 컴포넌트가 많아지고 렌더링이 자주 발생한다면 함수들을 다시 만드는 것은 효율적이지 않다. 이 때 useCallback을 활용해 데이터가 변경된 경우에만 함수를 다시 만들도록 할 수 있다.

  • React.memo
    컴포넌트의 props 가 바뀌지 않았다면 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있는 함수이다. 컴포넌트에서 리렌더링이 필요한 상황에서만 리렌더링을 하도록 설정할 수 있으며 사용법도 간단하다.



Class Component VS Function Component

rseact에서는 컴포넌트를 구성할 때 2가지 방식으로 선언할 수가 있다. 바로 클래스형 컴포넌트와 함수형 컴포넌트이다.

이전에는 클래스형 컴포넌트를 주로 사용했지만, 최근에는 함수형 컴포넌트에서 훅(hook)을 지원하기 때문에 현재는 함수형 컴포넌트와 훅을 사용하는 것을 권장하고 있다. 그래도 무작정 함수형 컴포넌트만 사용해야 하는 것이 아니라 두 가지 방법에 대해 모두 다 잘 알고 있어야한다. 결국 필요한 상황에 맞춰 컴포넌트 타입을 결정하고 사용하는 것이 중요하다고 생각한다.

클래스형 컴포넌트와 함수형 컴포넌트 사용법을 간단하게 살펴보기 위해 두가지 방식으로 간단한 코드를 작성하였다.


Class Component(클래스형 컴포넌트)

import React, {Component} from 'react';

class App extends Component {
  render() {
    const intro = "Hello World!";
  	return(
      <>
      	<h1>{intro}</h1>	
      </>
  	); 
  }
}

export default App;

Function Component(함수형 컴포넌트)

import React from 'react';

const App = () => {
  return(
    const intro = "Hello World!";
  	<>
    	<h1>{intro}</h1>	
    </>
  );
}

export default App;

코드 자체로는 두가지 방식 모두 가독성이라던가, 라인 수가 크게 차이나지 않기 때문에 무엇때문에 함수형 컴포넌트가 더 좋은지는 한번에 알 수 없었다.

그래서 구글링을 통해 찾아보니 React Conf 2018에서 Sophie Alpert와 Dan Abramov가 Hook을 소개하면서 React 16.8.0 버전 부터는 Hook(훅)을 지원하는데 공식문서에서는 이 훅 때문에 함수 컴포넌트를 사용하길 권장한다고 한다.

훅(Hook)은 앞에서 살펴보았지만, 다시 설명하면 클래스형 컴포넌트에서만 사용할 수 있었던 state 등의 다양한 react 기능들을 함수형 컴포넌트에서도 사용할 수 있게 해주는 기능이다. 이 훅에 대한 내용들을 공식문서 FAQ에서 찾아보면 클래스형 컴포넌트의 기능들을 훅을 통해 모두 구현할 수 있음을 알 수 있다.

결론을 정리하자면 클래스형 컴포넌트와 함수형 컴포넌트 중에서 최근에는 함수형 컴포넌트가 많이 활용되고 있으니 함수형 컴포넌트를 주로 사용해야겠다고 생각했다.

그렇다고 클래스형 컴포넌트에 대해서 배우지 않는 것이 아니라 클래스형 컴포넌트의 개념과 구현방식 정도는 숙지해야 할 필요가 있다고 느꼈다.



Day - 26

react에서 디자인을 적용하는 여러가지 방식들을 배우게 되었다. jsx 파일과 style 파일을 별도로 두거나 함께 작성하는 등의 방식들을 보며 개발자 입장에서 더 손쉽게 유지보수하거나 관리하기 위해 나오게 되었다고 느꼈다.


리액트에서의 디자인 적용(React Styling)

react에서 styling을 적용하는 방식은 여러가지가 있다. 그 중에서 일반적인 CSS와 CSS Module, classnames, Sass,그리고 style-components에 대해서 알아보려 한다.

일반적인 CSS 적용

webpack의 css-loader를 이용해서 CSS를 불러올 수 있다. react 프로젝트를 생성할 경우 App.js가 App.css를 불러와서 적용한다.

CSS Module

CSS를 불러와서 사용할 때 class의 이름을 고유한 값으로 만들어서 적용하는 방식이다. [파일명][클래스명][해쉬값]을 추가하여 클래스 이름이 지정된다. 사용법은 css 파일의 확장자를 test.module.css와 같이 만들어서 사용하면 된다.

classnames 라이브러리

CSS 클래스를 조건부로 설정할 때 유용한 라이브러리로 여러 클래스를 설정할 때 편리하게 사용할 수 있다.

SASS

CSS Preprocessor는 CSS가 동작하기 전에 사용하는 전처리기의 역할을 하며 CSS의 불편함을 해결하기 위한 확장 기능이다.

CSS와 문법은 유사하지만 선택자의 중첩(Nesting)이나 조건문, 반복문, 다양한 단위(Unit)의 연산 등 표준 CSS 보다 훨씬 많은 기능을 사용해서 편리하게 작성할 수 있다. 종류로는 Sass, Less, Stylus 등 이 있다.

Style-Components

자바스크립트 파일 안에 스타일을 선언하는 방식(CSS In JS)이다. 컴포넌트와 디자인을 분리하지 않고 컴포넌트와 디자인을 하나의 파일에서 작성하여 사용한다.

React에서 가장 많이 쓰이는 디자인 방식은?

주변 프론트엔드 개발자 분들은 주로 Style-Components를 많이 사용하시던데 react에서의 디자인 방식중 많이 쓰이는 게 무엇일지 궁금하였다.

npm trend에서 1년동안의 다운로드 수를 통계수치로 확인해보았고, Awesome React에서 styled-component와 classnames를, style-component와 sass에 대해서 확인하였다.

대부분 styled-component가 대중적으로 사용됨을 알 수 있었다.



CDN(Content Delivery-Deistribution Network)이란 무엇인가?

CDN이라는 용어가 굉장히 낯익고 종종 들어봤지만 사실 잘 몰랐다. 이번 기회에 CDN에 대해서 알아보려고 한다.

위 사진을 보고 어떤 느낌인지는 대충 이해할 수 있었다. 그럼 구체적으로 CDN이 무엇인지 살펴보자.

CDN이란 서버와 사용자 사이의 물리적인 거리를 줄이고 콘텐츠 로딩에 소요되는 시간을 최소화하기 위해서 동일한 콘텐츠를 여러 네트워크에 분산 저장한 후에 요청에 따라 가장 가까운 네트워크에서 다운로드하도록 지원하는 서비스이다.

CDN을 통해서 얻을 수 있는 이점은 데이터 사용량이 많은 애플리케이션의 웹 페이지 로드 속도를 높일 수 있다는 것이다.

사용자가 웹 사이트에 접속하면 웹 사이트의 서버에서 인터넷 네트워크를 타고 사용자의 컴퓨터까지 오게 되는데, 이 과정에서 서버와 사용자의 물리적 거리가 멀 수록 대용량 파일을 로드하는 시간은 상대적으로 길어질 수 밖에 없다.

바로 이 때, CDN을 통해서 이러한 문제를 개선할 수 있다. CDN 서버에 웹 사이트의 각종 콘텐츠가 저장되어 있다면 사용자는 가까운 CDN 서버에서 빠른 속도로 콘텐츠를 받아 로드할 수 있기 때문이다.

즉, CDN은 사용자 입장에선 콘텐츠 전송받는 시간을 줄여주게 된다.



Day - 27

react의 컴포넌트를 통해 각각의 상태를 변경함과 동시에 클라이언트 화면을 뒤바꿔주는 렌더링에 관해서 배우게 되었다. 또한 상태를 변경하는 데 있어 데이터의 원본에 영향을 주지않고 복제한 후 복제된 데이터에 작업을 진행해야 한다는 것을 알게 되었다.


렌더링과 리렌더링

React를 배우면서 렌더링과 리렌더링이라는 용어를 많이 접하게 되었다. 구체적으로 렌더링과 리렌더링이 무엇인지 살펴보자.

렌더링(Rendering)이란?
사용자 화면에 View(내용)를 보여 주는 것을 렌더링이라고 하는데, react에서는 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다.

리렌더링(Re-rendering)이란?
리렌더링은 사용자 화면에 뷰를 다시 새롭게 보여준다는 의미인데 이것을 업데이트 과정을 거친다.또는 조화 과정을 거친다라고 표현한다.

리액트에서 뷰(View)를 업데이트할 때는 업데이트 과정을 거친다 보다는 조화 과정을 거친다라고 표현하는 것이 더 정확하다. 이유는 컴포넌트에서 데이터에 변화가 있을 때 우리가 보기에는 변화에 따라 뷰가 변형되는 것처럼 보이지만, 사실 새로운 요소로 갈아 끼우기 때문이다.

컴포넌트가 리렌더링 되는 경우는?

  • 전달받은 props가 변경되는 경우
  • 자신의 state가 변경되는 경우
  • 상위 컴포넌트가 리렌더링 되는 경우
  • forceUpdate 함수를 호출하는 경우

현재 컴포넌트가 1000개 이상이라고 했을 때 현재 컴포넌트에서 변경점이 발생할 경우 App 컴포넌트의 state가 변경되었다고 감지하여 App이 리렌더링되어 화면 전체가 리렌더링 된다.

결국 하나의 데이터가 수정되었을 때 모든 데이터가 리렌더링 되는 문제가 발생하는 것이다.

이 때, 자신의 props가 변경되는 경우에 한해서만 리렌더링하도록 설정할 수 있다.
클래스형 컴포넌트에서는 shouldComponentUpdate라는 수명주기 메서드를, 함수형 컴포넌트에서는 React.memo를 이용하면 된다.

화면에 보여질 내용만 렌더링

스마트폰 애플리케이션에서 CollectionView들은 메모리 효율을 높이기 위해 Deque라는 자료구조를 이용하는데 화면에 보여지는 만큼만 메모리 할당을 해서 출력하고 스크롤하면 Deque으의 메모리를 재사용하는 메커니즘으로 메모리 효율을 높인다.

react에서도 외부 라이브러리를 이용하면 위와 유사하게 화면에 보여지는 만큼만 렌더링을 하고 나머지 데이터를 스크롤 할 때 렌더링하도록 할 수 있다. 이는 SPA에서는 굉장히 중요하다.

지연로딩
react-virtualized 라는 라이브러리를 이용해 지연로딩을 구현할 수 있다. 하나의 항목의 너비와 높이를 알아야 하고 목록의 높이를 알아야 한다. react에서 목록을 출력할 때는 스타일의 가로와 세로를 설정하는 것이 좋다.

지연로딩(Lazy Loading)이란?
말 그대로 로딩을 바로 하지 않고 지연시켰다가 나중에 로딩을 하는 것이다. 즉, 필요한 시점에 연관된 객체의 데이터를 불러오는 것이라고 할 수 있다.



불변성(Immutability)

불변성이란 데이터의 원본이 훼손되는 것을 막는 것을 의미한다.

react에서는 props와 useState로 만든 데이터의 원본을 수정할 수 없다.

react는 Virtual DOM의 개념을 사용하여 렌더링을 구현하는데, 현재 화면의 DOM과 메모리상의 Virtual DOM을 비교하여 수정된 부분만 다시 렌더링 하는 구조로 렌더링 속도를 향상시킨다.

즉, 원본을 변경하면 안된다는 것이다.

자바스크립트는 1개의 데이터를 가진 것과 여러 개의 데이터를 가진 것이 있을 때 다른 원리로 동작한다.

let a = 10;
let b = a;
// let b = 10;가 유사하게 동작

let obj1 = {name:"lango"};
let obj2 = obj1;
// obj1이 참조하고 있는 곳의 HashCode를 obj2에 대입

let arr1 = [1,2,3];
let arr2 = arr1;
// arr1이 참조하고 있는 곳의 HashCode를 arr2에 대입

객체나 배열에서는 주소나 위치를 담고 있다고 헷갈리면 안된다. 참조나 HashCode를 담고있다고 이해하고 있는 것이 맞다.

데이터가 1개인지, 0개 이상인지를 먼저 생각해보자.

원본이 수정되면 안되기에 위와 같은 방식으로 수정하는 작업을 하면 안된다.

let obj3 = {...obj1};
// obj1를 복제하여 obj3에 대입

let arr3 = [...arr1];
// arr1을 복제하여 arr3에 대입

컴포넌트 안에서의 함수는 반환값이 없으면 안된다. 반환값이 없는 함수는 대부분 원본을 건드리기 때문에 react에서는 사용할 수 없다.

예를 들어 sort() 메서드같은 경우는 매개변수로 전달한 원본 배열을 정렬하게 된다.

또한, 로그인을 구현한다고 하면 아이디와 비밀번호를 가져와서 별도로 원본 정보를 수정하면 안되기에 반환을 꼭 해주어야 한다고 이해하였다.



얕은 복사와 깊은 복사

객체나 배열같은 데이터를 이용핧 때 특정한 작업을 할 때 원본에 영향을 끼치면 안된다고 종종 들어왔다. 이 경우 원본을 별도로 복제하여 복제한 데이터에 작업을 진행해야 하는데, 복제하는 방식들에 대해서 궁금증이 생겼다.

얕은 복사(Weak Copy)

얕은 복사는 주소 값을 복사한다는 의미인데, 얕은 복사는 아주 최소한만 복사를 한다. 가장 바깥쪽 데이터를 복제하기에 값을 복사한다 하더라도, 인스턴스가 메모리에 새로 생성되지 않는다. 값 자체를 복사하는 것이 아니라 주소값을 복사하여 같은 메모리를 가리키기 때문이다.

새로운 인스턴스를 생성하지 않기 때문에 깊은 복사보다 상대적으로 빠르다. reference type을 복사하는 경우 얕은 복사가 일어난다.

하지만 얕은 복사라도 객체 안에 존재하는 객체의 속성이나 배열 내부의 데이터를 변경할 경우 변경되어 버린다는 단점이 있다.

let org = {
    name:10,
    arr:["A", "B"]
}
// 스프레드 연산자는 얕은 복사이다. 가장 바깥쪽만 복제한다.
let weakcp = Object.assign({}, org);
weakcp.arr[0] = "lango";
console.log(org, weakcp);

// org = {name:10, arr:["lango", "B"]}
// weakcp = {name:10, arr:["lango", "B"]}

깊은 복사(Deep Copy)

깊은 복사는 쉽게 말하면 실제 값을 새로운 메모리 공간에 복사하는 것을 의미하는데, 데이터 자체를 통째로 복사하는 방식으로 복사된 두 객체는 완전히 독립적인 메모리를 차지한다. 일반적으로 value type의 객체들은 깊은 복사를 하게 된다.

만약 데이터만 복제한다고 하면 편법을 사용할 수 있는데 JSON 문자열로 변환한 후 JSON 파싱하여 깊은 복사를 수행할 수 있다. JSON에는 함수가 없기 때문에 가능하다.

만약 react에서 함수도 깊은 복사를 하고자 한다면 직접 구현하거나 외부 라이브러리 immer나 lodash 등을 사용한다면 가능하다.

let org = {
    name:10,
    arr:["A", "B"]
}
// 깊은 복사
let deepcp = JSON.parse(JSON.stringify(org))
deepcp.arr[0] = "lango";
console.log(org, deepcp);

// org = {name:10, arr:["A", "B"]}
// weakcp = {name:10, arr:["lango", "B"]}



Day - 28

react에서 routing을 지원해주는 react-router에 대해서 배웠는데, 마치 서버단 컨트롤러에서 작성하는 메서드와 유사한 느낌이 들었다. 그만큼 스크립트 파일에서의 가독성이 좋다고 느꼈고, 추후 프론트엔드 구축시 꼭 사용해 봐야겠다고 다짐하였다.


React Router

react-router라는 라이브러리를 이용해 라우팅 기능을 사용할 수 있다.

Routing이란?
사용자가 요청한 경로(URL 주소)에 따라 해당 URL과 일치하는 화면(View)를 보여주는 것이다.

서버 렌더링과 클라이언트 렌더링

Server Rendering이란 웹 브라우저가 서버에게 요청하면 서버가 HTML을 전송하여 전체를 다시 출력하는 것이다. 이 방식은 사용자와 상호작용이 많은 웹 서비스에서는 속도 측면에서 문제가 발생할 수 있다.

SPA는 첫번째 요청에 대한 HTML만 응답받으며 이후에는 서버가 JSON 형식의 데이터를 전송하고 브라우저에서 렌더링하는 구조로 화면을 출력한다.

애플리케이션의 규모가 커질수록 JavaScript가 차지하는 비중이 많아져 트래픽과 로딩속도에 문제 발생 가능성이 있기에 Code Splitting을 통해 해결할 수 있다.

첫번째로 보여줄 페이지는 서버 렌더링을 통하여 보여주고 다음 페이지부터는 클라이언트 렌더링을 이용해야 사용성을 향상시킬 수 있다.

react-reouter 사용하기

그러면 react-router를 설치하여 사용해보자.

npm install react-router-dom
yarn add react-router-dom

위의 명령어로 설치할 수 있다.

import React from "react";
import { Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profile from "./Profile";
import Article from "./Article";
import Articles from "./Articles";
import Layout from "./Layout";
import Login from "./Login";
import MyPage from "./MyPage";
import NotFound from "./NotFound";

const App = () => {
  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      <Route path="/mypage" element={<MyPage />} />

      <Route element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/profiles/:username" element={<Profile />} />
      </Route>
    <Route path="/articles" element={<Articles />}>
      <Route path=":id" element={<Article />} />
    </Route>
    <Route path="*" element={<NotFound />} />
    </Routes>
  );
};
export default App;

entry point가 App.js라면 App.js에서의 react-router는 위와 같이 작성된다.

router로 작성된 컴포넌트들을 일일이 살펴보긴 길어져서 App.js에서의 구조만 살펴보았는데, SPA 구조에 더욱 부합되는 코드를 작성할 수 있다.

그리고 SPA에서는 다른 html을 보여주는것이 아니기 때문에 다른 주소에 다른 화면을 보여주는 routing 방식으로 페이지 이동을 해야하는데 이 떄 a 태그를 사용하는 것이 아니라 Router의 기능을 통해 페이지 이동을 해야 한다.



Forwarding과 Redirect

Forwarding
요청(Request) 객체를 유지하면서 이동, 새로고침을 하면 요청이 다시 이루어지는데 이경우는 서버에서 처리하는 로직이 있으면 다시 로직을 수행한다. 새로고침을 했을 떄도 작업을 다시 수행하고자 할 때 사용한다. (
조회)

Redirect
요청 객체를 소멸시키면서 이동, 새로고침시 요청이 다시 이루어지는 것이 아니고 현재 보여지는 결과를 다시 출력한다. 작업을 다시 수행하지 않아야 하는 경우 사용한다. (삽입, 삭제, 수정 등)



상태관리 라이브러리 Redux

Redux는 JavaScript의 상태관리 라이브러리인데, react에서 상태를 관리할 때 주로 사용한다고 한다.

react뿐만 아니라 node에서도 사용이 가능하며, 컴포넌트들의 상태 관련 로직들을 별도의 파일로 분리시켜 효율적으로 관리 할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있다. 프로젝트의 규모가 크거나 비동기 작업을 주로 하는 경우에 사용한다.

react를 사용하는 프로젝트 중 대부분이 redux를 사용하고 있다고 한다.

redux의 기본 원리 3가지

  • Single source of truth
    동일한 데이터는 항상 같은 곳에서 가지고 온다. 즉, 스토어라는 하나뿐인 데이터 공간이 있다는 의미이다.

  • State is read-only
    리액트에서는 setState 메소드를 활용해야만 상태 변경이 가능하다. 리덕스에서도 액션이라는 객체를 통해서만 상태를 변경할 수 있다.

  • Changes are made with pure functions
    변경은 순수함수로만 가능하다. 리듀서와 연관되는 개념이다.
    Store(스토어) – Action(액션) – Reducer(리듀서)

Store, Action, Reducer란?

Store(스토어)
스토어는 상태가 관리되는 오직 하나의 공간이라고 보면 된다. 컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담아두고 컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.

Action(액션)
액션은 앱에서 스토어에 운반할 데이터를 말한다 상태에 어떠한 변화가 필요할 때 발생시키는 객체이며 type을 필수로 넘겨주어야 한다.

Reducer(리듀서)
리덕스에서 데이터를 실제로 바꾸는 데 사용, 즉 저장된 상태(=데이터)를 변경하는 함수이다. 액션을 스토어로 바로 전달하지 않고 리듀서에 전달해야 하는데 dispatch() 메서드를 이용하여 액션 객체가 리듀서에 넘겨줄 수 있다. 그러면 리듀서는 새로운 스토어를 생성하게 된다.

Action 객체가 dispatch() 메소드에 전달 -> dispatch를 통해 Reducer를 호출 -> Reducer는 새로운 Store 생성






Final..

React에 대해서 배우며 프론트엔드와 백엔드와의 협업 포인트를 어떻게 구성해야할지 더욱 고민이 많아지게 되었다.

단순히 서버에 요청하여 요청을 처리하고 클라이언트로 응답을 전송하는 과정을 어떻게 하면 더 손쉽게 할 수 있을까를 고민했던 선배 개발자 분들의 노력의 흔적을 여실히 느낄 수 있었다.

다음 주부터 Java를 배우게 되는데, Java를 배우면서도 기본적인 동작 원리나 흐름, 내부 구조에 대해서 공부하려고 한다. 이 때, 단순히 문법적인 내용을 익히는 것도 중요하지만, Java 언어를 왜 사용하는지, Java를 통해 어떤 이점을 챙길 수 있는지 등의 요소들도 전반적으로 숙지하기 위해 노력할 것이다.



혹여 잘못된 내용이 있다면 지적해주시면 정정하도록 하겠습니다.

게시물과 관련된 소스코드는 Github Repository에서 확인할 수 있습니다.

참고자료 출처

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

0개의 댓글