728x90
반응형
리액트의 3가지의 대표적인 특징
- JSX 문법
- Component 기반
- Virtual DOM
1. JSX 문법
소개
- HTML을 마치 JS처럼 편리하게 사용하기 위한 리액트 JS 확장 문법
- HTML 문법을 JavaScript 코드 내부에 쓴 것이다.
- 빌드 시 Babel에 의해 자바스크립트로 변환된다.
- React 17릴리즈(대략 2020년 9월 22일) 이전버전은 React를 import 하지 않으면 JSX를 이해하지 못함
import React from 'react';
규칙
- 속성은 따옴표로 감싼다. <속성="">
- 반드시 태그를 닫는다. <></>
- 모든 태그는 Self-closing 이 가능하다. </>
- 변수값은 중괄호로 감싸서 표현 <변수={123}>
- 반드시 div/Fragment태그로 감싸진 형태로 써야한다. return <>...<>
// did work const 컴포넌트 = () => ( <div></div> ) // did work const 컴포넌트 = () => ( <> <div></div> <div></div> </> ) // did not work const 컴포넌트 = () => ( <div></div> <div></div> )
사용법
const 컴포넌트 = () => {
const handleOnClickded = () => {...};
return (
<div>안녕하세요</div>
<span>안녕하세요</span>
<h1>안녕하세요</h1>
<button onClick={handleOnClickded}/>
<button onClick={handleOnClickded}>안녕하세요</button>
<img src="이미지주소" alt="대체텍스트"/>
)
}
2. Component 기반
소개
- 단일 기능을 하는 여러 컴포넌트 모듈을 쌓아서 하나의 어플리케이션을 만든다.
- 재활용성을 극대화 시킬 수 있다.
Element
- 화면에 렌더링 할 DOM 노드들의 정보를 React에게 알려주기 위한 수단
- type, props 필드를 가진다.
- React.createElement() 혹은 JSX 로 작성한다.
Element 트리 종류
- DOM Element : HTML tag를 가진 DOM 노드 <div></div>
- Component Element : 사용자 정의 컴포넌트 <MyComponent />
- 클래스형 컴포넌트
- 함수형 컴포넌트
// SubComponent.js
export const SubComponent = () => {
return (
// DOM Element들을 작성
<div>...</div>
<button>...</button>
)
}
// RootComponent.js
import SubComponent1 from "SubComponent1";
import SubComponent2 from "SubComponent2";
import SubComponent3 from "SubComponent3";
export const MainComponent = () => {
return (
// Component Element를 결합
<SubComponent1/>
<SubComponent2/>
<SubComponent3/>
)
}
클래스형 컴포넌트
- 로컬 state, 생명주기 함수 호출 가능
- 컴포넌트 인스턴스를 내부에 가진다. this 키워드를 통해 참조되는 대상
// SubComponent.js
import React, { Component } from 'react';
export const SubComponent extends React.Components {
render() {
return (
// DOM Element들을 작성
<div>...</div>
<button>...</button>
);
}
}
함수형 컴포넌트
- 로컬 state, 생명주기 함수 호출 불가함. just 함수임
→ Hook 으로 보완가능
// SubComponent.js
export const SubComponent = () => {
return (
// DOM Element들을 작성
<div>...</div>
<button>...</button>
)
}
Array 형태의 컴포넌트 생성
- map 함수 사용하여 각 컴포넌트에 차례로 값을 넣어서 만든다.
※ map : array의 각 item에 대해 function을 수행한 array를 반환) - 각 컴포넌트 요소들은 unique(유일성)을 가져야하므로 key속성에 값을 꼭 넣어야한다.
(똑같이 생긴 사람들에게 이름이 없다면 누군가를 지목할 수 없는 것임) - 이 key값은 실제 props로 전달되지 않고, react 내부에서 사용됨
const RootComponent = () => {
return (
[1,2,3,4].map(cur, idx => <Component key={idx} val={cur} /> )
)
}
props, state
- state : 컴포넌트 내부에서 가지는 값 (내꺼)
- props : 부모 컴포넌트가 자식 컴포넌트에게 주는 값 (부모→자식)
- 부모의 state를 자식에게 전달하면 자식 입장에서는 props인 것이다.
// RootComponent.js
import SubComponent from "SubComponent";
export const MainComponent = () => {
const [state, setState] = useState(1); // 나의 state 값
return (
<SubComponent state={state}/> // 자식에게 나의 state를 전달
)
}
// SubComponent.js
export const SubComponent = (props) => { // 자식 입장에서는 props가 됨
const [text, setText] = useState("안녕"); // 나의 state 값
return (
<div>{props.state}</div> // props.state명 으로 접근
)
}
Hook
- state와 생명주기 관리 로직만 따로 작성한 것으로 View와 로직을 분리시켜주는 핵심 역할
- Hook을 통해 함수형 컴포넌트에서도 state와 생명주기관리 로직을 사용할 수 있게 해준다.
(브라우저의 메모리 자원사용함) - 사용할땐 반드시 컴포넌트 내부에 선언되어야 함
- 기본 제공 Hook 외에 사용자가 만든 Custom Hook을 만들어 쓸 수 있다.
const [state, setState] = useState(initialState); // 기본 제공 Hook const [location, setLocation, error] = useGeolocation(); // 사용자 Custom Hook
- 사용법
// hooks/useGeolocation.js import { useState, useEffect } from "react"; export const useGeolocation = (onProvidedLocation) => { const defaultLocation = { lat: 37.554722, lng: 126.970833 }; const [location, setLocation] = useState(defaultLocation); const [error, setError] = useState(); const option = { enableHighAccuracy: true, timeout: 1000 * 60 * 1, // 1 min (1000 ms * 60 sec * 1 minute = 60 000ms) maximumAge: 1000 * 3600 * 24, // 24 hour }; const handleSuccess = (pos) => { const { latitude, longitude } = pos.coords; setLocation({ lat: latitude, lng: longitude }); onProvidedLocation && onProvidedLocation(); }; const handleError = (error) => setError(error.message); useEffect(() => { const { geolocation } = navigator; // 사용된 브라우저에서 지리적 위치(Geolocation)가 정의되지 않은 경우 오류로 처리 if (!geolocation) { setError("Geolocation is not supported."); return; } geolocation.getCurrentPosition(handleSuccess, handleError, option); }, []); return [location, setLocation, error]; };
// 컴포넌트.js import { useGeolocation } from "./hooks/useGeolocation"; // did work const 컴포넌트 = () => { const [location, setLocation, error] = useGeolocation(); return (...); } // did not work : Hook 안에서 또다른 Hook 호출못함! const 컴포넌트 = () => { useEffect(()=>{ const [location, setLocation, error] = useGeolocation(); },[]); return (...); } // did not work : 컴포넌트 밖에 선언못함! const [location, setLocation, error] = useGeolocation(); const 컴포넌트 = () => { return (...); }
useState()
- 비동기적으로 State를 업데이트한 뒤 리렌더링 요청을 한다.
- 동기가 아니라 비동기 호출 하는 이유
- 내부 일관성 유지
props와 state 사이의 일관성을 해칠 수 있으며 이것은 디버깅을 어렵게한다.
props는 부모 컴포넌트 리렌더링 해야 update 되는데 state 혼자만 동기적으로 업데이트 되면?
특히 props와 state를 혼합해서 쓰는 경우 뭐가 최신이고 뭐가 과거데이터인지 모르게 딤 - 동시 업데이트 활성화
setState() 를 연속적으로 호출하면 Batch(일괄)처리를 한다. 이것은 비동기이기 때문에 가능하다.
이벤트 헨들러, 네트워크 응답, 에니메이션 등에 따라 우선순위를 줄 수 있다.
백그라운드에서부터 렌더링을 시작하여 준비된 것 부터 빠르게 내보내면 사용자 경험 저하를 최소화 가능
- 내부 일관성 유지
- 사용방법
<주의> 비동기적으로 호출되기 때문에 setState() 후 바로state 값을 가져와도 업데이트 안됨
const [state, setState] = useState(1); setState(2); → 비동기적으로 state값을 1→2로 변경하고 다시 렌더링해주세요~ console.log(state); → 업데이트 전에 호출되어 1임 useEffect(()=>{ console.log(state); → 업데이트 끝나고 호출되므로 2임 },[state])
※ 참고 : ttps://github.com/facebook/react/issues/11527#issuecomment-360199710
3. Virtual DOM
소개
- React에서 제공하는 가상의 DOM
- 가상의 DOM에서 변경된 내용만 먼저 확인해서 실제 DOM에 업데이트 하여 불필요한 단계를 줄인다.
DOM vs Virtual DOM
- 기존 DOM : 조그만 변화가 일어나면 모든 DOM을 리렌더링하여 성능의 저하를 발생
- Virtual DOM : 변화가 일어난 부분만(+자식컴포넌트들 까지)을 대체함
DOM만을 사용할 때 보다 대부분의 렌더링 성능에서 우수함
※ DOM : 브라우저가 화면을 그리기위한 정보를 담고있는 문서 객체
동작 방식
- 처음부터 HTML에 소스코드를 넣지않고 먼저 빈 HTML을 빠르게 불러온다.
- 내가 작성한 component(App.js)를 HTML(document.getElementById('root'))에 밀어넣는다.
브라우저의 Workflow
- 브라우저가 HTML 파일 수신
- HTML Parser(구문 분석)하고 DOM Tree 생성
- CSS Parser(스타일 구문 분석)
- DOM Tree 의 노드, 스타일 정보 합쳐서 Render Tree 생성
- 모든 노드에 화면 좌표와 표시될 정확한 위치 제공
- Render Tree 가 탐색되고 각 노드의 paint() 메소드 호출하여 화면에 표시
리렌더링 과정
- 데이터가 업데이트 되면, 전체 UI를 Virtual DOM에 리렌더링
- 이전 Virtual DOM와 비교하여 바뀐 부분만 실제 DOM에 적용
- 클래스형 컴포넌트 : render() 함수 실행을 뜻한다.
- 함수형 컴포넌트 : 자기자신(함수 전체)을 실행하는 것을 뜻한다.
- ReactDOM.render(Root Element, DOM 노드) 호출
- Root Element부터 시작해서, 마주친 Element type을 검사
- 모든 컴포넌트들의 type을 검사 끝내면 최종적으로 한 Element Tree를 얻는다.
- 해당 Element Tree(= Virtual DOM)를 실제로 DOM에 일괄 반영
Element type 검사 시 type이 다른 경우 (이전 트리와 속해있던 Dom 노드를 전부 삭제)
- (class) Component Element
- componentWillUnmount 호출
- 새 DOM 노드가 DOM에 삽입, componentDidMount 호출
- 이전 트리의 소속된 모든 컴포넌트 인스턴스 Unmount, state 소멸
Element type 검사 시 type이 같은 경우
- (class) Component Element
- 컴포넌트 인스턴스는 동일하게 유지되어 렌더링 간 state 유지
- 새로운 엘리먼트 내용 반영하기 위해 props 갱신
- shouldComponentUpdate 호출하여 true/false 결과에 따라 render 호출 결정한다.
- 부모 Element에 대하여 동일한 과정을 재귀적으로 반복한다.
- DOM Element
- Element 속성 확인하여 동일 부분 유지, 변경 부분 갱신,
- 자식 Element에 대하여 동일한 과정을 재귀적으로 반복한다.
리 렌더링이 일어나는 경우
- props가 변경될 때(= 부모의 state가 변경될 때 / 리 렌더링 될때)
- state 변경이 있을 때 (= setState Hook이 호출되어 render()가 실행될 때)
setState() → render() → 새 Element Tree반환 → 재조정(Reconciliation) → Re-Rendering - forceUpdate() 가 실행될 때
render() 호출
- 새로운 Element 를 생성한다. 컴포넌트는 아래와 같이 객체로 변환됨
{
type: 'div',
props: {
className: 'Class',
children: [
'Child1',
'Child2'
]
}
}
재조정(Reconciliation) 과정
- 기존/새 Virtual Dom을 (얕은)비교하는 과정
- 리렌더링이 불필요한 대상은 제외시킨다.
shouldComponentUpdate()를 적절히 오버라이딩하여 불필요한 render() 메소드 호출을 막는 것이다. - 두 가지 가정으로 휴리스틱하게 동작하여 트리비교 시간복잡도 O(n^3)이 아닌 O(n)의 시간소요
- Type이 다른 Element는 서로 다른 Tree 생성 → type이 바뀌면 무조건 새로 그리도록
- key prop 값을 통해 변경 대상인지 아닌지 표시 → 변경 대상만 변경
변경 사항이 없는 하위 컴포넌트는 왜 리렌더링이 되는가?
- React는 값이 동일하더라도 항상 props 객체를 새로 만든다.
얕은 비교를 하기 때문에
즉, 메모리 주소가 바뀌니 변경된 것으로 인식해 리렌더링 해버린다.
- 참조 타입의 얕은 비교시 의도치 않은 불필요한 리렌더링이 방지를 하려면 아래 게시물 참고
진짜로 props가 매번 다시 만들어 질까?
아래 예시를 보자
button 클릭시 setCount()가 실행되어 리렌더링이 일어나며 Logger 내용이 콘솔에 출력된다.
그 이유는 Logger의 props객체가 매번 새로 생성되기 때문이다.
import * as React from 'react'
import ReactDOM from 'react-dom'
function Logger(props) {
console.log(`${props.label} rendered`)
return null // what is returned here is irrelevant...
}
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return (
<div>
<button onClick={increment}>The count is {count}</button>
<Logger label="counter" />
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'))
아래와 같이 다른 속성이 변경되지 않아도 항상 props 객체는 새로 생성되고,
새로 생성되면 메모리 주소값이 바뀔 테니 달라진 것으로 인식되어 리렌더링 하게 된다.
{
type: Logger, 변경X
props: { 항상 변경o → 리렌더링
label: 'counter', 변경X
},
}
그러나 Logger를 props로 넘겨 받으면?
최초 1회만 Logger 내용이 콘솔에 출력되며 버튼을 눌러도 다시 출력되지 않는다.
왜냐면 내 state가 아니라 부모의 state라서 그렇다.
부모가 리렌더링 되면 Logger도 다시 찍히겠지만
자식만 리렌더링 된 경우 Logger가 찍히지 않는 것이다.
import * as React from 'react'
import ReactDOM from 'react-dom'
function Counter(props) {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return (
<div>
<button onClick={increment}>The count is {count}</button>
{props.logger}
</div>
)
}
ReactDOM.render(
<Counter logger={<Logger label="counter" />} />,
document.getElementById('root'),
)
{
type: Logger, 변경X
props: { 변경X
label: 'counter', 변경X
},
},
라이프 사이클 (생명주기)
- Mounting
constructor() : JS class를 생성할때 호출되는 생성자 → 컴포넌트가 screen에 표시될때 호출
render() : 컴토넌트가 랜더링 될때 호출
componentDidMount() : 컴포넌트가 처음 랜더링 완료된 직후 호출 - Updating
componentDidUpdate() : 값이 (render된 다음에) 업데이트 완료되면 호출됨 - Unmounting
componentWillUnmount() : 컴포넌트가 DOM 상에서 제거될 때 호출
아래 링크에서 자세히 소개해준다.
반응형
'프론트엔드 개발 > React.js' 카테고리의 다른 글
React Query onError 가 동작하지 않는 이슈 (0) | 2022.12.21 |
---|---|
리액트 URL Hash기반의 서브페이지 관리 체계 도입 (0) | 2022.09.11 |
리액트 props.children 이란 무엇인가? (0) | 2021.12.17 |
React - 프로젝트에서 이미지 경로 찾기 (0) | 2021.11.30 |
React - useEffect 훅 사용하기 (훅에서 async await 함수 사용 포함) (0) | 2021.11.06 |