일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- JavaScript
- Next 팀 프로젝트
- 리액트
- 리액트 훅
- typeScript
- 파이썬 enumerate
- 내배캠 프로젝트
- 파이썬 for in
- 파이썬 반복문
- useState
- 파이썬 for
- React Hooks
- 파이썬 딕셔너리
- 타입스크립트
- 타입스크립트 props
- 내일배움캠프 최종 프로젝트
- 내일배움캠프 프로젝트
- 코딩테스트
- tanstack query
- 파이썬 replace
- js
- 프로그래머스
- 파이썬 slice
- REACT
- 타입스크립트 리액트
- 자바스크립트
- 파이썬 list
- 리액트 프로젝트
- 한글 공부 사이트
- 내일배움캠프
- Today
- Total
sohyeon kim
[React] 리액트 공식문서 정리(1) : UI 표현하기 - 불변성, JSX, key, 컴포넌트, 순수 함수, 트리 본문
💡 리액트 공식문서 정리 : UI 표현하기
1. export default : decault 키워드는 코드를 사용하는 다른 파일에서 이 함수가 주요 함수임을 알려준다.
export default function Square() {
return <button className="square">X</button>;
}
Syntax | Export 구문 | Import 구문 | 다른 이름으로 가져오기 |
Default | export default function Button () {} | import Button from './button.js'; | O |
Named | export function Button () {} | import { Button } from './button.js'; | X (그래서 Named import) |
보편적으로, 한 파일에서 하나의 컴포넌트만 export 할 때 default export 방식을 사용하고
여러 컴포넌트를 export 할 경우엔 named export 방식을 사용
2. 불변성 유지 : nextSquares를 따로 만드는 이유는 상태를 불변성(immutability)을 유지하며 업데이트하기 위해서
왜 불변성을 유지해야 하는가?
- 불변성 유지 : squares 배열을 직접 수정하지 않고, nextSquares 라는 새로운 배열을 만들어 setSquares 로 상태를 업데이트하면 React가 변경된 상태를 감지하고 UI를 올바르게 업데이트한다. 만약 squares 를 직접 수정하면, React 는 상태가 변한 것을 인식하지 못해 UI 가 재렌더링되지 않을 수 있다.
- 성능 최적화 : 불변성을 유지하는 방식은 React 에서 상태가 변할 때 변경된 부분만 효율적으로 재렌더링할 수 있도록 도와준다.
- '돌아가기' 기능(틱택토 게임 중) : 특정 작업을 실행 취소하고 다시 실행하는 기능은 앱의 일반적인 요구사항이다. 직접적인 데이터 변경을 피하면 이전 버전의 데이터를 그대로 유지하여 나중에 재사용(또는 초기화)할 수 있다.
- 저렴한 비용 : 불변성을 사용하면 컴포넌트가 데이터의 변경 여부를 저렴한 비용으로 판단할 수 있다.
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
nextSquares[i] = 'X';
setSquares(nextSquares);
}
3. JSX : JavaScript 를 확장한 문법, JS 파일을 HTML 과 비슷하게 마크업 작성을 할 수 있도록 해준다.
- HTML 과 비슷해 보이지만, 조금 더 엄격하며 동적으로 정보 표시 가능
- <></> : Fragment, 빈 태그로 브라우저상의 HTML 트리 구조에 흔적을 남기지 않고 엘리먼트들을 그룹화 해준다.
- 왜 하나로 감싸줘야 할까? : JSX 는 HTML 처럼 보이지만 내부적으로는 일반 JS 객체로 변환된다.
함수에서 두 개의 객체를 하나의 배열로 래핑하지 않고선 반환할 수 없기 때문에 다른 태그나 Fragment 로 감싸줘야 한다.
- 왜 하나로 감싸줘야 할까? : JSX 는 HTML 처럼 보이지만 내부적으로는 일반 JS 객체로 변환된다.
spread 문법으로 props 전달하기
때때로 컴포넌트의 재사용으로, 전달되는 props 가 반복적일 수 있다. 이를 간결하게 만들기 위해 spread 문법을 활용하면 좋다.
(하지만 과도하게 사용하지 말 것.)
// 기존
function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}
// spread props
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
window.devicePixelRatio 를 고려해 높은 DPI 화면에서 더 선명한 이미지를 표시할 수 있다.
function Avatar({ person, size }) {
let thumbnailSize = 's';
if (size * ratio > 90) {
thumbnailSize = 'b';
}
return (
<img
...
/>
);
}
3. key : map() 호출 내부의 JSX 엘리먼트에 항상 필요. 각 컴포넌트가 어떤 배열 항목에 해당하는지 React 에 알려준다.
왜 key 가 필요한가?
- 배열 항목이 이동, 삽입, 삭제되는 경우 정확한 항목을 식별(선택)할 수 있도록 해준다.
- 파일에 이름을 부여해준다고 생각.
참고, 주의사항
- 렌더링 중 key 를 생성해선 안되며, 데이터에 key 를 포함해야 한다.
- 데이터베이스의 데이터 : DB 에서 데이터를 가져오는 경우 DB의 key, id 를 사용
- 로컬에서 생성된 데이터 : 데이터가 로컬에서 생성되는 경우, 항목을 만들 때 crypto.randomUUID() 또는 uuid 패키지 사용
- 빈 <></> fragment 구문을 사용할 땐 <div> 나 더 명시적인 <Fragment> 문법으로 그룹화할 수 있다.
import { Fragment } from 'react';
// Fragment는 DOM에서 사라지므로 <h1>, <p>, <h1>, <p> 등의 평평한 리스트가 생성된다.
const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
- key = {math.random()} 처럼 즉석에서 key 를 생성하면 안된다.
렌더링 간 key 가 일치하지 않아 모든 컴포넌트와 DOM 이 매번 다시 생성되고 속도가 느려질 수 있다. - 컴포넌트는 key 를 prop 으로 받지 않는다.
컴포넌트에 id 를 넘겨주어야 한다면 <Profile key={id} userId={id} /> 와 같이 별도의 prop 으로 전달한다.
import { recipes } from './data.js';
function Recipe({ id, name, ingredients }) {
return (
<div>
<h2>{name}</h2>
<ul>
{ingredients.map(ingredient =>
<li key={ingredient}>
{ingredient}
</li>
)}
</ul>
</div>
);
}
// ...recipe => // recipe 객체의 모든 속성을 props 로 전달
// ** 위 div 가 아니라 Recipe 자체에 key 지정
export default function RecipeList() {
return (
<div>
<h1>Recipes</h1>
{recipes.map(recipe =>
<Recipe {...recipe} key={recipe.id} />
)}
</div>
);
}
4 - 1. 컴포넌트 순수하게 유지하기 : 컴포넌트를 순수함수로 작성하면 예상 외 동작이나 버그를 피할 수 있다.
순수성, 순수 함수란?
- 자신의 일에 집중. 함수가 호출되기 전에 존재했던 어떤 객체나 변수는 변경하지 않는다.
- 같은 입력이 주어졌다면 같은 결과를 반환해야 한다.
y = 2x 일 때, x = 2 라면 y 는 항상 4다.
function double(number) { // 순수함수 double
return 2 * number;
}
React 는 이러한 컨셉을 기반으로 설계되어, 작성되는 모든 컴포넌트가 순수 함수일 것이라 가정한다.
➡️ 작성되는 컴포넌트에 같은 입력이 주어진다면 반드시 같은 JSX 를 반환한다는 것을 의미.
➡️ 마치 수학 공식이나 레시피처럼! 예측이 가능해야 한다!
4 - 2. 사이드 이펙트
의도하지 않은 결과 주의. 아래의 컴포넌트는 순수성 규칙을 위반하고 있다.
- 컴포넌트 바깥에 선언된 guest 변수를 수정하고 있다. 만약 컴포넌트가 여러 번 불리면 다른 JSX 를 생성한다.
- 현재 <React.StrictMode> 로 인해 함수가 두 번 호출되어 아래와 같은 결과가 나왔다.
순수 함수는 연산만 하므로 두 번 호출해도 아무것도 변경되지 않는다.
y = 2x 를 두 번 푸는게 y 의 답을 바꾸지 않는 것처럼, 항상 같은 입력이면 같은 출력이다.
- 현재 <React.StrictMode> 로 인해 함수가 두 번 호출되어 아래와 같은 결과가 나왔다.
- 다른 컴포넌트가 guest 를 읽었다면 언제 렌더링 되었는지에 따라 다른 JSX 를 생성할 것. 결과를 예측할 수 없어진다.
❗️ 렌더링은 언제든지 발생할 수 있으므로 컴포넌트는 서로의 렌더링 순서에 의존하지 않아야 한다.
💡 guset 를 변수가 아닌 prop 으로 전달해 순수 컴포넌트로 수정할 수 있다.
사이드 이펙트란?
함수형 프로그래밍은 순수성에 크게 의존하지만 언젠간, 어딘가에서, 무언가가 바뀌어야 한다.
이러한 변화들 - 화면을 업데이트하고, 애니메이션을 시작하고, 데이터를 변경하는 것이 사이드 이펙트다.
렌더링 중 발생하는 것이 아니라 '사이드에서' 발생하는 현상.
- 보통 이벤트 핸들러에 포함된다.
이벤트 핸들러가 컴포넌트 내부에 정의되었더라도 렌더링 중엔 실행되지 않는다. (e.g. 버튼 클릭 시 실행)
➡️ 따라서 이벤트 핸들러는 순수할 필요가 없다. - 다른 옵션을 모두 사용했지만 사이드 이펙트에 적합한 이벤트 핸들러를 찾을 수 없는 경우, useEffect 를 사용할 수 있다.
해당 이벤트는 렌더링 후 나중에 실행되며, 이 접근 방식은 마지막 수단이 되어야 한다.
React 는 왜 순수함을 신경쓰는가?
- 컴포넌트는 서로 다른 환경에서 실행될 수 있는데(e.g. 서버),
동일한 입력엔 동일한 결과를 반환하기 때문에 많은 사용자 요청을 처리할 수 있고, 캐시하기에 안전하다. - 입력이 변경되지 않은 컴포넌트는 렌더링을 건너뛰어 성능을 향상시킬 수 있다.
- 깊은 컴포넌트 트리를 렌더링하는 도중 일부 데이터가 변경되는 경우,
오래된 렌더링을 완료하는 데 시간을 낭비하지 않고 렌더링을 다시 시작할 수 있다.
5. 트리로서의 UI :
트리란? React 앱의 데이터 흐름과 렌더링 성능 및 앱 크기를 최적화하는 방법을 이해하는 데 유용한 도구
- 요소 간의 관계를 나타내는 일반적인 방법으로 UI를 모델링하는 데 자주 사용된다.
예를 들어, 브라우저는 HTML(DOM) 과 CSS(CSSOM) 를 모델링하기 위해 트리 구조를 사용하며, 모바일 플랫폼도 뷰 계층 구조를 나타내는 데 트리를 사용한다. - 애플리케이션을 트리로 생각하면 컴포넌트 간의 관계를 이해하는 데 도움이 된다.
React 의 렌더 트리 : 앱을 렌더링할 때 모델링하는 트리
- 렌더트리에서 트리는 노드로 구성되어 있으며, 각 노드는 컴포넌트를 나타낸다.
- 최상위 컴포넌트는 루트 컴포넌트에 가장 가까운 컴포넌트이며,
그 아래의 모든 컴포넌트의 렌더링 성능에 영향을 미치며, 가장 복잡성이 높다. - 리프 컴포넌트는 트리의 맨 아래에 있으며 자식 컴포넌트가 없으며 자주 다시 렌더링 된다.
React 의 모듈 의존성 트리 : 앱의 모듈 의존성을 모델링하는 트리
- 각 노드는 모듈이며, 각 가지는 해당 모듈의 import 문을 나타낸다.
- 트리의 루트 노드는 루트 모듈이며, 엔트리 포인트 파일이라고도 한다. 일반적으로 루트 컴포넌트를 포함하는 모듈.
- 의존성 트리는 React 앱을 실행하는 데 필요한 모듈을 결정하는 데 유용. 어떤 코드를 번들로 묶을지 최적화할 기회를 제공한다.
- React 앱을 프로덕션용으로 빌드할 때, 일반적으로 클라이언트에 제공할 모든 필요 JavaScript 를 번들로 묶는 빌드 단계가 있다. 이 작업을 담당하는 도구를 번들러라고 하며, 번들러는 의존성 트리를 사용하여 포함해야 할 모듈을 결정한다.
- 앱이 커짐에 따라 번들 크기도 커지고, 이에 따라 클라이언트가 다운로드하고 실행하는 데 드는 비용도 커진다.
또한 UI가 그려지는 데 시간이 지체될 수 있는데, 앱의 의존성 트리를 파악하면 이러한 문제를 디버깅하는 데 도움이 된다.
- ❗️ 동일한 앱의 렌더 트리와 비교했을 때 차이점
- inspirations.js 와 같은 컴포넌트가 아닌 모듈도 이 트리에 나타난다. 렌더 트리는 컴포넌트만 캡슐화.
- Copyright.js 가 App.js 아래에 나타나지만, 렌더 트리에서 Copyright 컴포넌트는 InspirationGenerator 의 자식으로 나타난다. 이는 InspirationGenerator 가 자식 props 로 JSX 를 허용하기 때문에, Copyright 를 자식 컴포넌트로 렌더링하지만 모듈을 가져오지는 않기 때문.
🔗 https://ko.react.dev/learn/describing-the-ui
'React' 카테고리의 다른 글
[React] 리액트 공식문서 정리(2) : 상호작용성 더하기 - 렌더링, 스냅샷, 배칭, 배치 업데이트, 객체 불변성 유지, Immer (0) | 2024.10.18 |
---|---|
[React] Context 외부에서 데이터를 사용하려 할 때의 에러 처리, useContext (0) | 2024.10.11 |
[React] Zustand : 너무나 가벼운 상태관리 라이브러리 (0) | 2024.08.07 |
[React] TanStack Query : Infinite Queries & Intersection Observer 무한스크롤 활용 (0) | 2024.08.07 |
[React] TanStack Query : Paginated & Lagged Queries 페이지네이션 & 지연 쿼리 (0) | 2024.08.07 |