https://react.dev/learn/state-as-a-snapshot 를 보고 정리한다.

 

State 변수는 스냅샷처럼 작동한다. state 값을 바꾸면 이미 가지고 있는 state를 변경하는 것이 아니라 리렌더링이 트리거된다.

state 설정은 렌더링을 일으킨다.

user interface는 사용자 이벤트에 대한 응답하기 위해 직접 변경된다고 생각할 수 있다.

하지만 React에서 멘탈 모델은 이와 다르다.

React에서는 state를 설정(변경)하면 리렌더링이 요청된다.(Rendering and Commit 참고)
즉, 인터페이스가 이벤트에 반응하려면 state를 업데이트 해야한다는 것을 의미한다.

아래 예제에서 "send" 버튼을 누르면 setIsSent(true)가 React에 UI를 리렌더링하도록 지시한다.

자세한 과정은 다음과 같다.

  1. onSubmit 이벤트 핸들러가 실행
  2. setInSent(true)isSenttrue로 설정하고 새로운 렌더를 큐에 넣음
  3. React는 새로운 isSent 값에 따라 컴포넌트를 리렌더링

렌더링은 그때그때의 스냅샷을 생성한다.

"렌더링" 은 React가 함수인 컴포넌트를 호출한다는 의미이다. 함수에서 반환하는 JSX는 해당 시점의 UI 스냅샷과 같다.

  • props, 이벤트 핸들러 및 지역 변수는 모두 렌더링 시점의 state를 사용하여 계산된다.
  • UI "스냅샷"은 상호작용을 한다.
    • 입력에 대한 응답을 지정하는 이벤트 핸들러와 같은 로직이 포함된다.
    • 그런 다음 React는 이 스냅샷과 일치하도록 화면을 업데이트하고 이벤트 핸들러를 연결한다.
    • 결과적으로 버튼을 누르면 JSX에서 클릭 핸들러가 발생된다.

 

React가 컴포넌트를 다시 렌더링할 때:

  1. React는 함수를 다시 호출
  2. 함수가 새 JSX 스냅샷을 반환
  3. React는 반환된 스냅샷과 일치하도록 화면을 업데이트

 

 

컴포넌트의 메모리에서 state는 함수가 반환된 후 사라지는 일반 변수와 다르다.

State는 함수 외부에 있는 것처럼 React 자체에 실제로 "살아 있다".

 

React가 컴포넌트를 호출하면 state 스냅샷을 제공한다.
컴포넌트는 해당 렌더링의 state 값을 사용하여
모두 계산된 JSX의 새로운 props 및 이벤트 핸들러 세트와 함께 UI의 스냅샷을 반환한다!

아래 예제는 "+3"버튼을 클릭하면 setNumber(number+1)을 세 번 호출하기 때문에 counter가 3번 증가할 것으로 예상할 수 있다.

하지만 결과는 클릭당 1번만 증가하게 된다.

그 이유는 state 설정시 다음 렌더링에 대해서만 변경되기 때문이다.

  • 초기 렌더링 : number = 0
  • 다음 렌더링 : number = 0 + 1
    • 3번 호출했지만 현재 렌더의 이벤트 핸들러가 가진 number는 항상 0이다.
    • 따라서 모든 setNumber(number+1) 는 현재(초기 렌더링)number가 0이기 때문에 다음 렌더링에서 number는 0 + 1 = 1 이 된다.

시간 경과에 따른 State

버튼 클릭시 alert될 내용을 추측해보자.

결과는 "0"가 나온다.

 

setTimeout을 주어도 동일하게 "0"이 나올 것이다.

setNumber(0 + 5);
setTimeout(() => {
  alert(0);
}, 3000);
state 변수의 값은 이벤트 핸들러가 비동기식인 경우에도 렌더 내에서 절대 변경되지 않는다.
즉, React가 컴포넌트를 호출하여 UI의 "스냅샷을 찍었을 때" 값이 "고정"된다.

 

다음은 이벤트 핸들러가 타이밍 실수를 덜하게 만드는 방법의 예다.

5초 지연 메시지를 보내는 form이며 아래의 시나리오를 상상해보자

  1. "Send" 버튼을 누르면 Alice에게 "Hello"가 전송됩니다.
  2. 5초 지연이 끝나기 전에 "To" 필드의 값을 "Bob"으로 변경합니다.

결과는 “You said Hello to Alice”가 나타난다.

이유는 위에서 말했다시피 React는 한 렌더의 이벤트 핸들러 내에서 상태 값을 "고정"으로 유지하기 때문이다.

 

https://react.dev/learn/state-as-a-snapshot 를 보고 정리한다.

 

컴포넌트가 화면에 표시되기 전에 React에서 렌더링해야한다.

이 프로세스를 이해하면 코드가 실행되는 방식에 대해 생각하고 동작을 설명하는 데 도움이 된다.

 

컴포넌트가 주방의 요리사라고 생각하면, React는 고객의 요청을 받고 주문을 가져오는 웨이터이다.

UI를 요청하고 제공하는 프로세스에는 세 단계가 있다.

  1. 렌더링 트리거(손님의 주문을 주방으로 전달)
  2. 컴포넌트 렌더링(주방에서 주문 준비)
  3. DOM에 커밋(테이블에 가져다 놓기)

1단계 : 렌더 트리거

컴포넌트는 2가지의 이유로 렌더링이 트리거된다.

  1. 컴포넌트의 초기 렌더링일 때
  2. 컴포넌트(또는 상위 컴포넌트 중 하나)의 state가 업데이트 될때

초기 렌더링

앱이 시작될 때 초기 렌더링이 트리거 된다.

DOM 노드의 타겟에 createRoot를 호출한 다음, 반환되는 것의 render메서드를 통해 컴포넌트를 호출한다.

state 업데이트시 리렌더링

컴포넌트의 첫 렌더링을 마친 후, set함수를 통해 state를 업데이트하면 리렌더링이 트리거된다.

컴포넌트의 state를 업데이트하면 자동으로 렌더가 큐에 등록된다.

2단계 : React가 컴포넌트를 렌더링한다.

렌더링을 트리거한 후 React는 컴포넌트를 호출하여 화면에 표시될 내용을 파악한다. "Rendering"은 React가 컴포넌트를 호출하는 것을 의미한다.

  • 초기 렌더링에서 React는 루트 컴포넌트를 호출한다.
  • 그 후 렌더링에서는 state 업데이트로 렌더링을 일으킨 컴포넌트를 리액트가 호출한다.

이 프로세스는 재귀적이다. 따라서 중첩된 컴포넌트가 없을 때까지 계속된다.

  • 업데이트된 컴포넌트가 다른 컴포넌트를 리턴한다면, 이 컴포넌트도 렌더링된다.

렌더링은 반드시 순수한 계산으로 이루어져야한다.(Keeping Components Pure 참고)
그렇지 않으면 예측할 수 없는 동작과 버그가 발생한다.
strict mode로 개발할 때 React는 각 컴포넌트의 함수를 두 번 호출하여 순수하지 않은 함수로 인한 실수를 걸러내는데 도움을 준다.

성능 최적화

더보기

업데이트된 컴포넌트 내에 중첩된 모든 컴포넌트를 렌더링하는 기본 동작은 업데이트된 컴포넌트가 루트와 가까워지는 경우라도 성능 최적화를 하지않는다.

성능 문제가 발생하는 경우 성능 섹션에서 문제를 해결할 수 있는 몇가지 옵션이 있다.

성급하게 최적화하지 마십시오!

3단계 : React는 DOM에 대한 변경 사항을 커밋한다.

컴포넌트를 렌더링 한 후, React는 DOM을 수정한다.

  • 초기 렌더링의 경우, React는 `appendChild()` DOM API를 사용하여 생성한 모든 DOM 노드를 화면에 표시한다.
  • 리렌더링의 경우, React는 필요한 최소한의 (렌더링에서 계산된)작업을 적용한다.

React는 렌더링 간에 차이가 있는 경우에만 DOM 노드를 변경합니다.

아래의 예제의 경우, 매 초마다 time이 변경되어 재렌더링을 하지만 <input>에 추가한 텍스트는 변경되지않는다.

  • 마지막 단계에서 React는 time이 적용되는 <h1>만 업데이트하기 때문이다.
  • <input>은 지난번화 동일한 위치에 나타나기 때문에 건드리지 않는다.

 

 

에필로그 : 브라우저 페인트

렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 다시 그린다. 이 프로세스를 "브라우저 렌더링"이라고 하지만 이 문서의 나머지 부분에서 혼동을 피하기 위해 "페인팅"이라고 부른다.

https://react.dev/learn/keeping-components-pure 를 보고 정리한다.

 

컴포넌트를 순수 함수로만 작성하면 code base가 커짐에 따라 전체 클래스의 당황스러운 버그와 예측할 수 없는 동작을 피할 수 있다.

순수성

컴퓨터 과학에서 순수 함수는 아래와 같은 특징을 가진다.

  • 자신이 할일만 신경쓴다. 함수가 호출되기 전의 다른 객체나 값을 변경하지 않는다.
  • 동일한 입력을 받으면 동일한 결과를 출력한다.

React는 순수함수를 중심으로 설계되었다. React는 작성하는 모든 컴포넌트가 순수 함수라고 가정한다.

즉, 작성하는 React 컴포넌트는 동일한 입력이 주어지면 항상 동일한 JSX를 반환해야한다.

Side Effects(부수 효과): 의도하지 않은 결과

React의 렌더링 프로세스는 항상 순수해야 한다. 컴포넌트는 JSX만 반환해야하며 렌더링 전에 존재했던 객체나 변수를 변경해서는 안 된다.

  • 모든 부수 효과는 렌더링 이후에 생성된다.

순수 함수가 아닌 경우의 예 중 하나는 외부에서 선언된 변수를 컴포넌트에 사용하는 경우이다. 이는 동일한 입력을 여러 번 받을 때 동일한 결과를 출력한다는 보장이 없다. 따라서 예측 불가능해진다.

이를 해결하기 위해 prop을 선언해서 사용하는 방법으로 수정할 수 있다.

일반적으로 컴포넌트가 특정 순서로 렌더링될 것을 기대하면 안되며, 모든 컴포넌트가 서로 독립적으로 해결되어야한다.

  • 렌더링은 언제든지 발생할 수 있으므로 구성 요소는 서로의 렌더링 순서에 의존하지 않아야 합니다.

StrictMode로 순수하지 않은 계산 감지하기

더보기

React는 개발 중에 각 구성 요소의 기능을 두 번 호출하는 "Strict Mode"를 제공합니다. 구성 요소 함수를 두 번 호출함으로써 Strict Mode는 이러한 규칙을 위반하는 구성 요소를 찾는 데 도움이 됩니다.

순수 함수는 계산만 하므로 두 번 호출해도 아무 것도 변경 되지 않습니다.

StrictMode는 production에 영향을 미치지 않으므로 사용자의 앱 속도를 늦추지 않습니다. StrictMode를 선택하려면 루트 구성 요소를 <React.StrictMode>로 감싸면 된다.. 일부 프레임워크는 기본적으로 이 작업을 수행합니다.

Local mutation: 컴포넌트의 작은 비밀

순수 함수는 함수 범위 밖의 변수나 호출 전에 생성된 객체를 변경하면 안됩니다.

그러나 렌더링하는 동안 로컬 변수와 개체를 만들고 이를 변경하는 것은 전혀 문제가 없습니다. 이를 'local mutation'이라 부른다.

Side Effects를 일으킬 수 있는 곳

함수형 프로그래밍이 함수의 순수성에 크게 의존하지만, 우리는 결국 동적인 UI를 위해 Side Effects를 일으켜야한다.

  • 화면 업데이트
  • 애니메이션 시작
  • 데이터 변경...

이는 렌더링 중이 아닌 "on the side(측면)"에서 발생한다.

React에서 side effects는 보통 event handlers 통해 발생한다. Event handlers는 컴포넌트 내부에 정의되어 있지만 렌더링 중에는 실행되지 않는다. 따라서 event handler는 순수할 필요가 없다.

event handler만으로 side effect를 붙이기 어려운 경우, useEffect 호출을 이용할 수 있다. 이는 렌더링 후에 실행해야할 side effect를 지정할 수 있다. 그러나 이 방식은 최후의 수단이여야 한다.

 

React가 순수성에 대해 관심을 가지는 이유?

더보기

순수 함수는 놀라운 기회을 제공합니다.

  • 컴포넌트는 다른 환경(서버...)에서 실행될 수 있다.
    • 동일한 입력에 대해 동일한 결과를 반환하기 때문에 하나의 컴포넌트가 많은 사용자 요청을 처리할 수 있다.
  • 같은 입력을 가진 컴포넌트에 대한 렌더링을 skip함으로써 성능을 향상시킬 수 있다.
    • 순수 함수는 항상 동일한 결과를 반환하므로 캐시하는 것도 안전하다.
  • 깊은 컴포넌트 트리를 렌더링하는 도중 일부 데이터가 변경되면, React는 낭비 되는 시간(지난 렌더링을 끝내는 시간)없이 렌더링을 다시 시작할 수 있다.
    • 순수 함수는 언제나 안전하게 계산을 중단할 수 있다.

 

모든 React 기능은 순수성을 활용한다.

데이터 가져오기에서 애니메이션, 성능에 이르기까지 컴포넌트를 순수하게 유지하면 React 패러다임의 힘이 발휘될 것이다.

 

Challeges

해결책과 다른 방법으로 해결하였을 경우, 기록합니다.

 

3번 : Fix a broken story tray

문제 원인 : prop으로 받은 stories를 건드렸다.

문제 해결 : prop을 건드리지 않기 위해 해당 내용을 복사한 변수를 생성하고, 여기에 추가할 내용을 push한다.

export default function StoryTray({ stories }) {
  const curStories = [...stories];
  curStories.push({
    id: 'create',
    label: 'Create Story'
  });

  return (
    <ul>
      {curStories.map(story => (
        <StoryItem story={story}></StoryItem>
      ))}
    </ul>
  );
}

function StoryItem({ story }) {
  return (
    <li key={story.id}>
      {story.label}
    </li>
  )
}

https://react.dev/learn 를 보고 정리한다.

Creating and nesting components

React는 Component로 이루어진다.

Component는 고유한 logic, apperance를 가진 UI의 일부이다.

React Component는 Markup을 반환하는 JS function이다.

function MyButton() {
    return (
        <button>I'm a button</button>
    );
}

생성된 Component를 다른 Component에 중첩(nest)시킬 수 있다.

React Component 이름은 항상 대문자로 시작해야 하며 HTML 태그는 소문자여야 합니다.

Writing markup with JSX

선택 사항이지만, 편의를 위해 대부분의 React project는 JSX를 사용한다.

Component는 여러 개의 JSX태그를 반환할 수 없다.

- 여러 개의 태그를 반환하려면 이를 wrapping하는 공통된 부모를 사용하면 된다.

- wrapper : <div>...</div>, <>...</>

- html-to-jsx : 링크

Adding styles

className을 사용해 CSS class를 사용할 수 있다.

<img className="avatar" />

React는 CSS 파일을 추가하는 법을 규정하지 않는다.

React와 관계없이 아래처럼 CSS 파일을 추가할 수 있다.

  • HTML에 태그를 추가
  • 빌드 도구/프레임워크 사용

Displaying data

중괄호를 사용하면 JavaScript로 "escape back"하여 코드에서 변수를 사용자에게 표시할 수 있다.

  • 단순한 변수뿐만 아니라 복잡한 표현식도 넣을 수 있다.
return (
  <h1>
    {user.name}
  </h1>
);

Conditional rendering

React에서 조건을 작성하기 위한 특별한 구문은 없다. 일반적으로 JS처럼 사용하면 된다.

if문 사용

자세한 내용 : 링크

let content;
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}
return (
  <div>
    {content}
  </div>
);

? 사용

자세한 내용 : 링크

<div>
  {isLoggedIn ? (
    <AdminPanel />
  ) : (
    <LoginForm />
  )}
</div>

&& 사용

자세한 내용 : 링크

<div>
  {isLoggedIn && <AdminPanel />}
</div>

Rendering lists

component의 list를 렌더링하기 위해 for loop와 array의 map()함수를 사용한다.

map()함수를 이용해 배열의 내용을 <li>항목으로 변환한다.
이때, 목록의 각 항목에 대해 고유한 식별자(문자열/숫자)를 key로 지정해야한다.

  • React는 추후 항목을 삽입, 삭제 또는 재정렬하는 경우 어떤 일이 발생했는지 이해하기 위해 키에 의존합니다.

Responding to events

event handler 함수를 사용해 Component 내부에서 발생하는 이벤트에 응답할 수 있다.

function MyButton() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

이때, 끝에 이벤트 호출을 뜻하는 괄호를 붙이지 않아야 한다.onClick={handleClick}

  • 이벤트 핸들러 함수를 호출하는 것이 아니라 전달하기만 해야한다.

Updating the screen

component에 state를 사용해 기억하고자 하는 정보를 저장할 수 있다.

  1. import useState
  2. useState로 부터 현재 state와 state를 업데이트할 수 있는 함수를 받는다.
    • 이름을 맘대로 지정할 수 있지만 보통 [something, setSomething]로 짓는다.

state를 사용해 카운터 버튼을 만들면 아래와 같다.

  1. 초기 버튼은 useState()을 사용해 0으로 설정했기 때문에 count가 0으로 표시된다.
  2. 버튼을 클릭하면 setCount()로 인해 새로운 값이 생기며, 여기서는 count를 증가시킨다.

Using Hooks

use로 시작하는 하는 함수를 Hooks라고 한다.
useState는 React에서 제공하는 built-in Hook이다.

  • 그 외 built-in Hooks : React API reference
  • 기존의 Hook을 결합해 자신만의 Hook을 작성 가능

Hooks는 일반 함수보다 더 제한적이다.

  • 컴포넌트의 최상위 레벨에서만 Hooks을 호출 할 수 있다.
  • 조건이나 루프에서 useState를 사용하려면 새로운 Component를 만들어서 넣어야한다.

Sharing data between components

위의 예제에서 각 MyButton는 독립적인 count를 가진다.

그러나 데이터를 공유하고 함께 업데이트할 수 있는 Component들이 필요한 경우가 많다.
이를 위해서는 공유하려는 Component들을 포함하는 가장 가까운 Component로 state를 이동해야한다.

위 예제에서 count를 공유하도록 코드를 구성해보자.

  1. MyButton에서 MyApp으로 state를 이동한다.
  2. MyApp의 state를 MyButton에게 내려준다.(click handler도 포함)
    • 중괄호를 사용해 정보를 전달할 수 있다. 이렇게 전달하는 정보를 props라고 한다.
  3. MyButton에서 부모 Component에서 내려준 정보를 읽도록 변경한다.

이때, state를 위로 이동하여 Component간의 공유하는 것을 “lifting state up”라고 부른다.

'React' 카테고리의 다른 글

React  (0) 2022.07.22
React - Redux : initialState: undefined 에러 해결  (0) 2022.03.28
React Router V5  (0) 2022.02.27

장점

 사용자 경험이 좋아진다.

  • 웹에서 앱과 같은 사용자 경험을 만들어 줌.
    • single page application여서 화면 이동 간 깜박임이 없음.

컴포넌트 단위

  • 중복되는 요소들을 하나로 묶어줄 수 있다. 따라서 유지보수가 쉬워진다.

데이터-화면 동기화를 알아서 해준다.

  • 기존에는 데이터를 화면과 동기화시키는 것이 어려움.

개발자 도구에서 data 상태를 알 수 있음

문법적 특징

  • JSX : JS + XML(태그)
    • 반복문은 map을 사용
    • 삼항연산자 사용 가능
{
	this.state.result.length === 0
		? null
		: <div>평균 시간 : {this.state.result.reduce((a, c) => a+c) / this.state.result.length}ms</div>
}
  •  
    • 부호 연산자 사용 가능
{
	this.state.result.length !== 0
		&& <div>평균 시간 : {this.state.result.reduce((a, c) => a+c) / this.state.result.length}ms</div>
}
  • babel : 실험적인 문법(js안에서 html문법을 쓸수 있는)을 쓸 수 있게 해줌.
    • babel이 JSX문법을 JS가 이해할 수 있게 변경해준다.

 

렌더링

  • state, prop의 값이 변경될 때 렌더링이 발생

불변성 체크

예전 state와 지금 state의 참조가 변경되어야 render가 실행된다.

// 렌더링 안됨
const arr = [];
arr.push(1);
// 이렇게 해야 렌더링함
const arr1 = [];
const arr2 = [...arr1, 1];
arr1 === arr2; // false

렌더링 최적화

성능 문제 발생 원인

  • state나 prop가 자주 변경되면 또는 쓸데없이 변경되면 성능에 영향을 끼칠 수 있다.
  • 컴포넌트는 자신의 state가 변경되거나, 부모에게서 받는 props가 변경될 때마다 리렌더링된다.
    • 하위 컴포넌트에 최적화 설정을 하지 않으면 부모에게서 받는 props가 변경되지 않았더라도 리렌더링되는 것이 기본이다.

렌더링이 자주 일어날때 문제를 찾는법

  • 개발자 도구 > React > 설정(톱니바퀴) > "Highlight updates when components render."에 체크.
    • 렌더링될때마다 화면이 반짝거린다.
      • 색상 : 렌더링이 많이 일어날 수 록 점점 색깔이 빨개짐. (파 > 초 > 노 > 빨)

최적화 방법

  1. state 내 변수 변경 여부를 직접 구현 가능 : PureComponent
  2. 복잡한 함수를 캐싱(useMemo, useCallback)


그 외 알게된것들

리로딩과 핫 리로딩

  • 리로딩 : 새로고침. 웹팩 데브 서버가 변경을 감지하면 새로고침을 시킴.
    • 단점 : 기존 데이터가 날라감. 다시해야함
  • 핫 리로딩 : 기존 데이터를 유지하면서 화면을 변경함.
    • 에러찾기 좋음

리덕스

  • 여러 컴포넌트에서 쓰이는 공통적인 데이터 관리
  • dev tool
    • 이력 관리

'React' 카테고리의 다른 글

Quick Start  (0) 2022.09.24
React - Redux : initialState: undefined 에러 해결  (0) 2022.03.28
React Router V5  (0) 2022.02.27
4. WrappedApp created new store with withRedux(NodeBird) { initialState: undefined, initialStateFromGSPorGSSR: undefined }
TypeError: Cannot read property 'user' of undefined
    at D:\github\NodeBirdSNS\front\.next\server\pages\index.js:146:101
    at useSelectorWithStoreAndSubscription (D:\github\NodeBirdSNS\front\node_modules\react-redux\lib\hooks\useSelector.js:39:30)
    at useSelector (D:\github\NodeBirdSNS\front\node_modules\react-redux\lib\hooks\useSelector.js:139:25)
    at AppLayout (D:\github\NodeBirdSNS\front\.next\server\pages\index.js:146:85)
    at processChild (D:\github\NodeBirdSNS\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3353:14)
    at resolve (D:\github\NodeBirdSNS\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3270:5)
    at ReactDOMServerRenderer.render (D:\github\NodeBirdSNS\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3753:22)
    at ReactDOMServerRenderer.read (D:\github\NodeBirdSNS\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3690:29)
    at renderToString (D:\github\NodeBirdSNS\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:4298:27)
    at Object.renderPage (D:\github\NodeBirdSNS\front\node_modules\next\dist\next-server\server\render.js:50:851)

Redux 사용한 프로젝트에서 위와 같이 initialState: undefined 에러가 발생한다면, 아래 방법을 사용해 해결할 수 있다.

=> default 분기 처리 추가!!

  • 이유 : 초기화시 아래 함수를 사용하기 때문에, default 처리를 하지 않으면 초기값이 없다고 판단한다.
const reducer = ((state = initialState, action) => {
    switch(action.type) {
    	case 'A':
        	return { ... };
        default:
        	return state;
    }
}

'React' 카테고리의 다른 글

Quick Start  (0) 2022.09.24
React  (0) 2022.07.22
React Router V5  (0) 2022.02.27
브라우저에서 페이지 요청 시 서버에서 어떻게 데이터를 전달해 어느 쪽에서 렌더링 할 것인가?

 

SSR(Server Side Rendering)

서버에서 사용자에게 보여줄 페이지를 모두 구성 한 후 브라우저에 전달하는 방식이다.

렌더링이 서버쪽에서 이루진 다고 해서 서버 사이드 렌더링이라고 불린다.

 

브라우저를 통해 blog 페이지를 연다고 가정할 때, SSR 방식은 아래와 같이 동작한다.

SSR(Server Side Rendering) Flow

  1. 브라우저 => 프론트 서버로 블로그 페이지 요청
  2. 프론트 서버 => 백엔드 서버로 블로그 게시글 요청
  3. 백엔드 서버 => 데이터 베이스로 블로그 게시글 데이터 요청
  4. 데이터 베이스 => 백엔드 서버로 블로그 게시글 데이터 반환
  5. 백엔드 서버 => 프론트 서버로 블로그 게시글 응답 : 데이터 베이스로부터 받은 데이터를 가공해서 반환
  6. 프론트 서버 =>  블로그 페이지 응답 : 백엔드 서버로부터 받은 데이터와 화면 구성(html, js, css등)을 합쳐서 반환

장단점

장점

  • 브라우저에게 모든 데이터가 매핑된 서비스 페이지를 한 번에 넘겨줄 수 있다.
  • SEO(search engine optimization)에 대한 큰 문제가 없다.

단점

  • 브라우저 요청 - 응답 플로우가 길기 때문에 CSR보다는 각 페이지를 구성하는 속도가 늦어진다.
  • 재 렌더링이 필요하지 않은 부분까지 다시 렌더링 하게 된다.
    • 클라이언트가 페이지 이동할 때마다 이 과정을 반복하기 때문에 화면에서 바뀌지 않아도 되는 부분까지 계속해서 다시 렌더링 되는 단점이 있다.
    • 이는 서버 부하로 이어질 가능성이 존재한다.

CSR(Client Side Rendering)

React, Vue,... 과 같은 SPA(Single Page Application)에서 사용되는 방법이다.

브라우저가 프론트에서 화면 구성 코드를 받고, 그 후 백엔드 서버에서 필요한 데이터만 받는 방식이다.

렌더링이 클라이언트 쪽에서 이루어진다고 해서 클라이언트 사이드 렌더링이라고 불린다.

 

브라우저를 통해 blog 페이지를 연다고 가정할 때, SSR 방식은 아래와 같이 동작한다.

CSR(Client Side Rendering) Flow

1, 2 : 브라우저에서 프론트 서버로 요청 후 응답

  • 데이터를 제외한 모든 화면을 그리는 코드(html, js, css등) 다운
  • 이때, 요청 받은 화면 포함 모든 화면의 구성을 다 가지고 오기 때문에 시간이 많이 소요될 수 있다.
  • 서버에서 데이터 다 받기 전까지 임시로 구성된 로딩화면을 띄울 수 있다.

3, 4, 5, 6 : 브라우저에서 백엔드 서버로 블로그에 필요한 데이터 요청 및 응답

  • 서버에서 데이터를 다 받으면 브라우저에서 이를 구성해서 화면을 보여준다.

 

 

장단점

장점

  • 첫 방문 이후로는 필요한 데이터만 백엔드 서버를 통해 가져옴으로 서버 부하가 줄어든다.
  • SEO(search engine optimization)가 미완성 페이지로 인식할 수 있다.
    • 이는 code splitting 기능으로 첫 방문만 프론트 서버에서 방문한 페이지에 대한 코드만 보내주는 방법으로 해결 가능

단점

  • 처음에 프론트 서버로부터 모든 화면의 구성을 가져와야하기 때문에 더 느릴 수 있다.

 

어느게 나은가?

상황에 따라 다르다.

아래는 참고 페이지에서 가져온 예시이다.

SSR을 사용하자

  • 네트워크가 느릴 때
    (CSR은 한 번에 모든 것을 불러오지만 SSR은 각 페이지마다 나눠 불러오기 때문)
  • SEO(serach engine optimization : 검색 엔진 최적화)가 필요할 때.
  • 최초 로딩이 빨라야하는 사이트를 개발할 때
  • 메인 스크립트가 크고 로딩이 매우 느릴 때CSR은 메인스크립트가 로딩이 끝나면 API로 데이터 요청을 보낸다. 하지만 SSR은 한 번의 요청에 아예 렌더가 가능한 페이지가 돌아온다.
  • 웹 사이트가 상호작용이 별로 없을 때.

CSR을 사용하자

  • 네트워크가 빠를 때
  • 서버의 성능이 좋지 않을 때
  • 사용자에게 보여줘야 하는 데이터의 양이 많을 때.
    (로딩창을 띄울 수 있는 장점이 있다.)
  • 메인 스크립트가 가벼울 때
  • SEO 따윈 관심 없을 때
  • 웹 어플리케이션에 사용자와 상호작용할 것들이 많을 때. (아예 렌더링 되지 않아서 사용자의 행동을 막는 것이 경험에 오히려 유리함.)

 

 

참고 사이트

출처

해당 내용은 ZeroCho님의 리액트 무료 강좌 중 ReactRouter를 정리한 내용입니다.

코드 : https://github.com/ZeroCho/react-webgame/tree/master/react-router

포인트

  • 웹사이트 개발시 역할
  • React에서의 역할
  • React 연동방법
  • 그 외 정보는 공식 문서 참고

React Router 도입하기

설치

npm i react-router​
  • react-router 뼈대. 웹, 앱 둘다 사용 가능
npm i react-router-dom
  • 웹에서 사용할 때 설치.
  • 실제로는 react-router-dom만 사용
  • react-router는 react-router-dom이 내부적으로 사용

Router 컴포넌트

Router 컴포넌트를 사용하면 각각 따로만들었던 컴포넌트를 하나의 페이지에서 동시에 사용 가능하다.

컴포넌트의 최상위를 <..Router>로 감싸줘야한다.

  • Route : 페이지들 만듬
  • path : 페이지 주소
  • component : 보여줄 컴포넌트
const Games = () => {
    return (
        <BrowserRouter>
            <div>
                <Route path="/3_NumberBaseball" component={NumberBaseball}></Route>
                <Route path="/5_leture" component={RSP}></Route>
                <Route path="/6_lecture" component={Lotto}></Route>
            </div>
        </BrowserRouter>
    );
}

Link와 BrowserRouter

실제로 페이지가 여러 개가 있는게 아니라 react-router만 알고있는 가상으로 만들어낸 페이지이다.

페이지 전환처럼 보이기 위해서 Link 컴포넌트를 사용한다.

<Link to='/number-baseball'>숫자야구</Link>
  • Route로 정의한 path를 to에 넣어준다.
  • html의 <a></a>와 동일한 기능이다.
링크 클릭시 'http://localhost:8080/lotto-generator'로 주소가 바뀐다.
 

배치

공통적인 layout은 `<Route>` 바깥으로 빼야한다.

<BrowserRouter>
  <div>
    <Link to='/number-baseball'>숫자야구</Link>
    &nbsp;
    <Link to='/rock-scissors-paper'>가위바위보</Link>
    &nbsp;
    <Link to='lotto-generator'>로또생성기</Link>
  </div>
  <div>
    <Route path="/number-baseball" component={NumberBaseball} />
    <Route path="/rock-scissors-paper" component={RSP} />
    <Route path="/lotto-generator" component={Lotto} />
  </div>
</BrowserRouter>
이렇게 배치할 경우
  • 첫번째 div에 있는 부분은 페이지가 변경되도 바뀌지 않는다.
  • 두번째 div에 있는 부분은 페이지가 변경되면 바뀐다.

해시 라우터, params, with Router

해시 라우터

해시 라우터 사용시 링크를 클릭하면 주소 중간에 #가 들어간다.

  • ex) http://localhost:8080/#/number-baseball

장점 : 새로고침을 해도 화면이 보인다.

단점 : 서버가 해석하지 못해서 실무에서는 잘 사용되지않는다. 서버가 해석하지 못하면 검색 엔진에 뜨지 않는다.

  • url에 #가 있으면 서버가 해석하지 못하고, 브라우저(프론트엔드)에서 처리한다.
  • 검색엔진에 뜨게하려면 따로 서버에 세팅해야한다.

 

 

 

 

따라서 주소 이상해도 되고, 검색 엔진에 뜨지 않아도 되면 사용해도 된다.

dynamic Route matching1

Route가 계속 추가되면 추가해야할 만큼 일일히 쳐줘야한다. => 너무 방대해짐

route가 늘어나는 것을 하나로 합칠 수 있다.

효율적으로 Route들을 관리 가능

param

:는 param이라고 불린다.

/game/으로 시작하는 모든 Link는 <Route path="/game/:name >" 하나로 매칭할 수 있다.

ex)<Route path="/game/:name >"로 선언한 경우

  • name부분은 동적으로 변경된다.
    • <Link to="/game/...">
      • ...은 아무거나
      • /game/으로 시작하는 모든 Link가 이 Route에 매칭된다.

withRouter

<Route>내 정의된 component에서 this.props에는 자동으로 history, location, match가 생기며, 이는 <Route>가 자동으로 넣어주는 것이다.

<Route>와 연결이 안된 컴포넌트에서 history, location, match를 사용하고 싶으면 withRouter를 사용하면 된다.

import { withRouter } from 'react-router-dom';

class GameMatcher extends Component {}

export default withRouter(GameMatcher);

location, match, history

history

페이지 넘나든 내역을 가지고있다. 라우터 이동에 사용하며, goBack, goForward... 함수를 사용할 수 있다.

내부적으로 브라우저의 history를 사용한다.

기본 브라우저의 동작과 달리 실제로 페이지가 바뀌는게 아니기 때문에 히스토리를 관리할 수 있는게 존재한다.

match

동적 주소 라우팅시 params정보가 존재

match > params에 param 정보가 들어있다. 이걸로 분기처리를 할 수 있다.

location

주소(pathname), hash, search가 들어있다. 나중에 배움.

dynamic Route matching2

this.props.match.params.name를 이용해 분기 처리

하나의 라우터로 여러개의 페이지를 처리

class GameMatcher extends Component {
  render() {
    if(this.props.match.params.name === 'number-baseball') {
      return <NumberBaseball />
    } else if (this.props.match.params.name === 'rock-scissors-paper') {
      return <RSP />
    } else if (this.props.match.params.name === 'lottoe-generator') {
      return <Lotto />
    }
    return (
      <div>
        일치하는 게임이 없습니다.
      </div>
    );
  }
}

queryString과 URLSeatchParam

queryString

<Link to="">에 ?붙이고 key=value형식으로 정의하는 것

여러개일경우 &로 구분

주소로 데이터를 전달하는 가장 쉬운 방법

ex) <Link to='/game/number-baseball?query=10&hello=zerocho&bye=react'>

이 정보는 <Route>내 정의된 component에서 this.props.location.search에 쿼리스트링이 존재한다.

URLSeatchParam

queryString을 편하게 파싱하기 위해 사용한다.

브라우저에서 제공.

let urlSearchParams = new URLSearchParams(this.props.location.search.slice(1)); console.log(urlSearchParams.get('hello'));

render props, switch, exact

하위 컴포넌트로 props 넘기는 방법

첫 번째 방법 : 함수로 감싸기

<Route path="/game/:name" component={() => <GameMatcher props="12345" />} />

두 번째 방법 : render를 사용해 부모의 props 넘기기

<Route path="/game/:name" render={(props) => <GameMatcher props={props} />} />

=> props를 그대로 넘기는 게 목적이면 2번 방법을 추천.

동시에 라우트가 여러 개 뜨는 것을 막아보자.

switch

<Route>가 동적 라우팅과 정적 라우팅이 같이 있을 때 정적 라우팅으로 접근하면 화면이 어떻게 나올까?

ex) /game/exact로 접근시 두 라우터가 모두 렌더링된다.

<BrowserRouter>
    <div>
        <Route path="/game/:name" component={GameMatcher} />
        <Route path="/game/exact" component={GameMatcher} />
    </div>
</BrowserRouter>

이를 개선하기 위해 <Switch>를 사용하면 첫번째로 렌더링되는 것 하나만 렌더링한다.

<BrowserRouter>
    <div>
        <Switch>
            <Route path="/game/:name" component={GameMatcher} />
            <Route path="/game/exact" component={GameMatcher} />
        </Switch>
    </div>
</BrowserRouter>

exact

<Route>에 매칭되는 라우터가 여러 개일 때는 매칭되는 라우터가 모두 렌더링된다.

  • 기본적으로 상위주소도 항상 일치한다고 생각한다.

ex) /game/number-baseball로 접근시 두 라우터가 모두 나온다. => /도 일치한다고 생각한다.

<BrowserRouter>
    <div>
        <Route path="/" component={GameMatcher} />
        <Route path="/game/:name" component={GameMatcher} />
    </div>
</BrowserRouter>

이런 경우, <Switch>로도 해결이 되지 않는다.

  • 위의 경우 /가 더 위에 있기때문에 / 라우트만 렌더링된다.
  • 원래 원하는 건 아래 라우트로 렌더링되는 것.

exact를 사용해 적힌 주소와 정확히 일치하는 라우트만 렌더링한다.

그래서 아래의 경우 /에 붙여서, 진짜 주소가 /인 경우만 렌더링되게한다.

<BrowserRouter>
    <div>
        <Switch>
          <Route exact path="/" component={GameMatcher} />
          <Route path="/game/:name" component={GameMatcher} />
        </Switch>
    </div>
</BrowserRouter>

'React' 카테고리의 다른 글

Quick Start  (0) 2022.09.24
React  (0) 2022.07.22
React - Redux : initialState: undefined 에러 해결  (0) 2022.03.28

+ Recent posts