포도가게의 개발일지
React 본문
반응형
리액트 앱은 컴포넌트로 구성됩니다. 컴포넌트는 고유한 로직과 모양을 가진 UI의 일부, 컴포넌트는 버튼만큼 작을 수도 있고, 전체 페이지만큼 클 수 있습니다.
리액트 컴포넌트는 마크업을 반환하는 자바스크립트 함수입니다. 리액트 컴포넌트의 이름은 항상 대문자로 시작해야하고, html tag는 소문자로 시작해야합니다.
export default 키워드는 파일의 기본 컴포넌트를 지정합니다. 위에서 본 마크업 분법을 jsx라고 합니다.. 대부분의 리액트 프로젝트는 편의성을 위해 jsx를 사용합니다. jsx에서는
과 같이 태그를 닫아야합니다. 또한 컴포넌트를 여러 개의 jsx tag를 반환할 수 없습니다. 반환하기 위해서는 랩핑한
, <>...</> 부모로 감싸야 합니다.
react에서는 className으로 CSS class를 지정합니다. 이것은 html의 class 어트리뷰트와 동일한 방식으로 동작합니다.
jsx를 사용하면 js에 마크업을 넣을 수 있습니다. 중괄호를 사용하여
컴포넌트 내부에 이벤트 핸들러 함수를 선언하여 이벤트에 응답할수있습니다. 소괄호 없이 전달되면 함수를 호출하지않고 전달만 하면 됩니다.
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}컴포넌트가 특정 정보를 기억하여 표시하기를 원할때 useState를 사용, 이들을 어떤 이름으로도 지정할 수 있지만 [something, setSomething]으로 작성하는 것이 일반적입니다. 각 컴포넌트마다 고유한 state를 얻어 따로 기억합니다.
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}Hook
- use로 시작하는 함수를 hook이라고 한다. useState는 react에서 제공하는 내장 hook, custom hook도가능
- 컴포넌트의 상단, 또는 다른 hook에 상단에서만 hook을 호출 할 수있습니다.
- 위에 예제에서 두 컴포넌트간 동일한 count를 표시하고 업데이트하려면 state를 두 컴포넌트를 둘다 포함하는 가장 가까운 컴포넌트 위쪽으로 이동해야합니다.
- 그후 전달을 하게 됩니다. 이렇게 전달한 정보를 props라고 부릅니다.
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>Counters that update together</h1>
<MyButton count={count} onClick={handleClick} />
<MyButton count={count} onClick={handleClick} />
</div>
);
}
function MyButton({ count, onClick }) {
return (
<button onClick={onClick}>
Clicked {count} times
</button>
);
}App.js
- App.js의 코드는 컴포넌트를 생성합니다. react의 컴포넌트는 사용자의 인터페이스 일부를 표시하는 재사용 가능한 코드 조각입니다.
- 컴포넌트는 애플리케이션의 UI 엘리먼트 렌더링, 관리, 업데이트할 때 사용됩니다.
JSX 엘리먼트
- javascript 코드와 html tag의 조합으로 표시할 내용을 설명합니다.
- className = 'sqare' 는 버튼 prop으로 CSS에 버튼의 스타일을 지정하는 방법을 알려줍니다.
export default function Square() {
return <button className="square">X</button>;
}styles.css
- react 앱의 스타일을 정의합니다. *, body는 앱의 대부분의 스타일을 정의하고
- .sqare 선택자는 className prop이 sqare로 설정된 모든 컴포넌트의 스타일을 정의합니다.
index.js
- App.js파일에서 만든 컴포넌트와 웹 브라우저 사이의 다리 역할을 합니다.
- JSX에서 javascript로 동작하게 할려면 {} 중괄호로 감싸야 합니다.
state 끌어올리기
- 여러 자식 컴포넌트에서 데이터를 수집하고너 두 자식 컴포넌트간에 서로 통신하도록 하려면, 부모 컴포넌트에서 공유 state를 선언하세요
- 부모 컴포넌트는 props를 통해 핻아 state를 자식 컴포넌트에 전달할 수 있습니다.
화살표함수로 lazy하게 실행되도록 함수 전달
불변성의 중요성
- 직접적인 데이터 변경을 피하면 이전 버전의 데이터를 그대로 유지하여 나중에 재사용 또는 초기화하기 편해짐
- 기본적으로 부모 컴포넌트 state가 변경되면 모든 자식 컴포넌트가 자동으로 다시 렌더링됩, 변경사항이 없는 애들도
- 그로인해 성능상 이유로 트리의 영향을 받지 않는 부분의 리렌더링을 피하는것이 좋음.
key 선택하기
- list를 렌더링할때 react는 리스트 각 항목에 대한 몇가지 정보를 저장함.
- 리스트를 업데이트 할때 react는 무엇이 변경되었는지 확인해야함.
- 리스트의 항목에 key property를 지정하여 각 리스트의 항목이 다른 항목과 다른다는것을 구별해주어야함.
- react는 key를 사용해 update할 컴포넌트를 결정
- 동적인 리스트륾 만들때마다 적절한 key를 할당하는것을 추천, key가 없는경우 배열의 인덱스를 기본 key로 사용합니다.
- 배열의 인덱스를 사용하면 리스트의 항목의 순서를 바꾸거나 , 추가제거할때 문제가 발생합니다.
리액트 사고방식
- 먼저 사용자 인터페이스를 컴포넌트 조각으로 나눕니다.
- 각 컴포넌트의 다양한 시각적 상태들을 정의합니다.
- 마지막으로 컴포넌트들을 연결하여 데이터가 그 사이를 흘러가게 합니다.
디자인을 컴포넌트로 나누는 방법
- programming 단일책임 원칙으로 나누기
- css, 클래스 선택자를 무엇으로 만들지 고민
- 디자인 계층을 어떤식으로 구성할지 고민
State의 역할
- state는 앱이 기억해야 하는 변경할수있는 데이터의 최소 집합
- state를 구조화하는 가장 중요한 원칙은 중복배제원칙
- 애플리케이션이 필요로하는 가장 최소한의 state를 파악하고 나머지 모든것들은 필요에 따라 실시간으로 계산해라
- 상품 아이템의 개수를 노출하고 싶다고 하면 상품 아이템 개수를 따로 state 값으로 가지는 게 아니라 단순하게 배열의 길이만 쓰면 됩니다.
state의 기준
- 시간이 지나도 변하지않는지?
- 부모로부터 props로 전달되는지?
- 컴포넌트 내부의 다른 state나 props를 가지고 계산 가능한지,
state가 어디 있어야할까?
- React는 항상 컴포넌트 계층구조를 따라 부모에서 자식으로 데이터를 전달하는 단방향 데이터 흐름을 사용하는 것을 기억하세요!
- 해당 state를 기반으로 렌더링하는 모든 컴포넌트를 찾으세요.
- 그들의 가장 가까운 공통되는 부모 컴포넌트를 찾으세요. - 계층에서 모두를 포괄하는 상위 컴포넌트
- state가 어디에 위치 돼야 하는지 결정합시다
- 대개, 공통 부모에 state를 그냥 두면 됩니다.
- 혹은, 공통 부모 상위의 컴포넌트에 둬도 됩니다.
- state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 상위 계층에 추가하세요.
역 데이터 흐름
- 이제 사용자 입력에 따라 state를 변경하려면 반대 방향의 데이터 흐름을 만들어야 합니다.
- 계층 구조의 하단에 있는 컴포넌트에서 부모 컴포넌트의 state를 업데이트할 수 있어야 합니다.
첫 컴포넌트
- 마크업을 얹을 수 있는 javascript 함수
- 컴포넌트의 이름은 항상 대문자로 시작합니다
- JSX 마크업을 반환합니다.
JSX 규칙
- 한 컴포넌트에서 여러 엘리먼트를 반환하려면, 하나의 부모 태그로 감싸주세요.
- 모든 태그는 닫아주기
- JSX에서 중괄호를 이용하여 JavaScript 사용하기
``` - 마크업에
- 를 추가하고 싶지 않다면, <>와 </>로 대체할 수 있습니다.
- 이 빈 태그를 Fragment라고 합니다. Fragments는 브라우저상의 HTML 트리 구조에서 흔적을 남기지 않고 그룹화해 줍니다.
리스트 렌더링
- 각 배열 항목마다 key를 지정해야 합니다. 일반적으로 데이터베이스에서 가져온 ID를 key로 사용하게 될 것입니다. Key를 사용하면 리스트가 변경되더라도 React가 각 항목의 위치를 추적할 수 있습니다.
- Key는 각 컴포넌트가 어떤 배열 항목에 해당하는지 React에 알려주어 나중에 일치시킬 수 있도록 합니다. 이는 배열 항목이 정렬 등으로 인해 이동하거나 삽입되거나 삭제될 수 있는 경우 중요해집니다.
- key는 형제 간에 고유해야 합니다.
- key는 변경되어서는 안 되며
- 재정렬로 인해 위치가 변경되더라도 key는 React가 생명주기 내내 해당 항목을 식별할 수 있게 해줍니다.
State 관리
- 불필요한 중복된 state는 버그의 흔한 원인
State를 통해 input 관리
- 개별적인 UI를 직접 조작하는 것 대신에 컴포넌트 내부에 여러 state를 묘사하고 사용자의 입력에 따라 state를 변경합니다.
- 명령형 대신에 무엇을 보여주고 싶은지 선언하기만 하면 됩니다.
UI를 선언적인 방식으로 생각
- 시작적 state를 확인
- 무엇이 state 변화를 트리거하는지 알아내라
- useState를 사용해 메모리의 state를 표현 (일단 필요한거 다 적고 리팩토링하자)
- 불필요한 state 변수를 제거해라
- state가 역설을 일으키나요?
- 다른 state 변수에 이미 같은 정보가 담겨있나요?
- 다른 변수를 뒤집었을때 같은 정보를 얻을 수 있나요?
- state 설정을 위해 이벤트 핸들러를 연결해
Empty: 폼은 비활성화된 “제출” 버튼을 가지고 있다.
Typing: 폼은 활성화된 “제출” 버튼을 가지고 있다.
Submitting: 폼은 완전히 비활성화되고 스피너가 보인다.
Success: 폼 대신에 “감사합니다” 메시지가 보인다.
Error: “Typing” state와 동일하지만 오류 메시지가 보인다.useState 구조화 원칙
- 연관된 state 그룹화하기. 두 개 이상의 state 변수를 항상 동시에 업데이트한다면, 단일 state 변수로 병합하는 것을 고려하세요.
- State의 모순 피하기. 여러 state 조각이 서로 모순되고 “불일치”할 수 있는 방식으로 state를 구성하는 것은 실수가 발생할 여지를 만듭니다.
- 렌더링 중에 컴포넌트의 props나 기존 state 변수에서 일부 정보를 계산할 수 있다면, 컴포넌트의 state에 해당 정보를 넣지 않아야 합
- 데이터가 중복될 경우 동기화를 유지하기가 어렵습니다. 가능하다면 중복을 줄이세요.
- 깊게 계층화된 state는 업데이트하기 쉽지 않습니다. 가능하면 state를 평탄한 방식으로 구성
- 데이터베이스 구조를 “정규화”하여 버그 발생 가능성을 줄이는 것과 유사
- 예를 들어 두개의 state 변수가 항상 함께 변경된다면, 단일 state 변수로 통합하는것이 좋음.
- 계산할수있다면 state에서 빼라
컴포넌트간 State 공유
- state 끌어올리기를 하는 가장 흔한 일
- 제어 컴포넌트 비제어 컴포넌트
- 지역 state 대신 props에 의해 만들어지는 경우 컴포넌트가 제어된다고 한다.
- 비제어 컴포넌트는 설정할 것이 적어 부모 컴포넌트에서 사용하기 더 쉽습니다. 하지만 여러 컴포넌트를 함께 조정하려고 할 때 비제어 컴포넌트는 덜 유연합니다.
state 동작 원리
- state가 변경되면 react는 컴포넌트를 다시 렌더링하여 ui를 업데이트합니다
- state는 직접 수정하지않고, setState를 통해 새로운 값으로 업데이트
- setState는 비동기적으로 작동하므로, 즉시 변경안됨,
- 일반변수는 렌더링 간에 유지되지 않으며, 변경시 ui반영안됨, state는 렌더링간에 유지되고 변경시 ui자동업데이트
react 렌더링 커밋과정
- 트리거, 렌더링, 커밋 세가지 단계로 , 렌더링은 순수 계산과정으로 멱등성을 보장함
- 렌더링 트리거
- 초기렌더링: 앱이 처음 로드될 때, createRoot와 root.render를 호출하여 루트 컴포넌트를 렌더링합니다. (예: root.render();)
- 컴포넌트의 상태가 변경될 때 (예: setState 함수 호출로 색상 변경), React가 리렌더링을 트리거합니다.
- 컴포넌트 렌더링 단계
- React가 컴포넌트 함수를 호출하여 화면에 표시할 내용을 계산합니다.
- 초기 렌더링: 루트 컴포넌트부터 시작.
- 리렌더링: 상태가 업데이트된 컴포넌트와 그 자식 컴포넌트만 호출.
- 렌더링 커밋
- 렌더링 후 React가 실제 DOM을 수정합니다
- 초기 렌더링: 생성된 DOM 노드를 화면에 추가 (예: appendChild() 호출).
- 리렌더링: 최소한의 변경만 적용하여 효율적입니다. (예: 의 값처럼 이전 상태를 유지.)
- 커밋 후 브라우저가 화면을 페인팅(painting)하여 사용자에게 보이게 됩니다.
- 최적화
- 순수성 유지: 렌더링 함수는 부수 효과(side effects)가 없어야 하며, 이전 상태를 변경하지 않습니다. Strict Mode에서 함수를 두 번 호출하여 순수성을 검사합니다.
- 성능: React는 기본적으로 빠르지만, 필요 시 옵트인 최적화(예: memoization)를 사용하세요. 성급한 최적화는 피하는 것이 좋습니다.
state snapshot
- State는 렌더링 시점의 스냅샷처럼 동작하며, 업데이트 시 즉시 변경되지 않고 다음 렌더링에서 반영됩니다.
- 렌더링은 컴포넌트 함수를 호출하여 JSX를 반환하며, 이는 해당 시점의 UI 스냅샷
- State 업데이트는 비동기적: 같은 이벤트 핸들러 내에서 여러 번 setState를 호출해도, 현재 렌더링의 State 값은 변하지 않습니다.
- React는 스냅샷과 일치하도록 DOM을 업데이트합니다.
react state batches updated
- React state 업데이트를 하기전에 이벤트 핸들러의 모든 코드가 실행될때까지 기다리고 리렌더링이 일어남
- 업데이트 함수를 사용하여 이전 업데이트 함수의 반환값을 가져와 다음 업데이트 함수에 전달가능.
이 이벤트 핸들러가 React에 지시하는 작업은 다음과 같습니다.
setNumber(number + 5) : number는 0이므로 setNumber(0 + 5)입니다. React는 큐에 “5로 바꾸기” 를 추가합니다.
setNumber(n => n + 1) : n => n + 1는 업데이터 함수입니다. React는 해당 함수를 큐에 추가합니다.
다음 렌더링하는 동안 React는 state 큐를 순회합니다.
queued update n returns
”replace with 5” 0 (unused) 5
n => n + 1 5 5 + 1 = 6
React는 6을 최종 결과로 저장하고 useState에서 반환합니다.
** setState(5)가 실제로는 setState(n => 5) 처럼 동작하지만 n이 사용되지 않는다는 것을 눈치채셨을 것입니다!- 이벤트 핸들러가 완료되면 react는 리렌더링을 실행합니다. 리렌더링 동안 react는 queue를 처리 합니다.
- update function은 렌더링중에 실행되므로, 업데이트함수는 pure해야합니다. 업데이트 함수 내부에서 state를 변경하거나 다른 사이드 이펙트를 실행하려고 하면 안됩니다.
객체 state update하기
- State는 객체를 포함한 모든 종류의 자바스크립트 값을 가질수있습니다.
- 업데이트를 할대는 새로운 객체를 생성하여 state가 복사본을 사용하도록 하세요
- 객체를 변경하는 대신 교체해라
- react는 state 설정함수가 없으면 객체가 변경되었는지 알 수 없습
리듀서와 컨텍스트 복잡한 state관리
- Context는
- 앱 전체에서 공유되는 box, props를 layer별로 드릴링하지 않고, 바로 꺼낼수있게 도와줌.
- Provider는 값을 공유하는 공급자, Provider component를 사용해 일부 또는 전체 제공, 아래에 있는 모든 자식 컴포넌트에서 접근할수있게 됨
- Reducer는
- State를 업데이트 하는 레시피북, 복잡한 state 변화를 한곳에 모아서 처리
- Redux lib에서 유래했지만, React 기본 useReducer로 쓸 수 있음.
명령형 ui vs 선언형 ui
- 명령형 UI(직접 DOM 조작)는 복잡하고 버그가 발생하기 쉽습니다.
- 선언형은 다양한 시각적 상태(Empty, Typing, Submitting, Success, Error)를 식별하고, 입력에 따라 상태를 전환합니다. React가 DOM을 업데이트합니다.
state 로직 reducer로 작성
- Reducer는 State 업데이트 로직을 하나의 함수로 모아 관리하는 방법
- 이벤트 핸들러에서 "무슨 일이 일어났는지"를 action으로 dispatch하면, reducer가 현재 State와 action을 기반으로 새로운 State를 반환
- 순수성 유지: 사이드 이펙트(네트워크 요청, setTimeout 등) 포함 금지. State 불변성 지키기
Context를 사용해 데이터를 깊게 전달
- prop drilling?
- Props를 통해 데이터를 전달하는 것은 명시적이지만, 깊은 컴포넌트 트리에서 중간 컴포넌트들이 불필요한 props를 계속 전달해야 하는 "Prop drilling" 문제가 발생
- 코드가 장황해지고 유지보수가 어려워짐, context를 사용해 중간 컴포넌트를 건너띄고 직접 데이터를 전달
- 주의할점은 context 값이 자주 변경되면 Provider 아래 모든 컴포넌트가 전부 리렌더링됨
- 대안을 먼저 생각해보고 사용하는게 좋습니다.
Ref 참조
- Ref는 렌더링을 유발하지않고 값을 기억하는 도구, useRef훅으로 생성
- state와 달리 ref 변경시 컴포넌트가 리렌더링 되지 않음.
- 렌더링 중에는 current 값을 읽거나 쓰면 안되요~
- 컴포넌트의 형태에 영향을 미치지 않는 브라우저 api와 통신해야 할때 ref를 사용
- 예를 들어 사용자가 버튼을 눌러 시작하거나 중지할 수 있는 스톱워치를 만들어봅시다. 사용자가 “시작”을 누른 후 시간이 얼마나 지났는지 표시하려면 시작 버튼을 누른 시기와 현재 시각을 추적해야 합니다. 이 정보는 렌더링에 사용되므로 state를 유지합니다.
Ref로 dom 조작
- 가끔 특정 노드에 포커스를 옮기거나, 스크롤 위치를 옮기거나, 위치와 크기를 측정하기 위해서 React가 관리하는 DOM 요소에 접근해야 할 때가 있습
Effect로 값 동기화
- useEffect훅으로 렌더링 후 코드 실행, 클린업 함수로 자원 해제
- 불필요한 Effect는 코드 복잡성 및 성능저하 유발.
- Effect는 컴포넌트와 별개의 생명주기, 마운트 시 실행, 의존성 변화시 재실행
- 대표적으로 채팅룸 id 변경시 서버 재연결
- useEffect는 화면에 렌더링이 반영될 때까지 코드 실행을 “지연”시킵니다.
- React는 지정한 모든 종속성이 이전 렌더링의 그것과 정확히 동일한 값을 가진 경우에만 Effect를 다시 실행하지 않습니다.
- ChatRoom 컴포넌트가 마운트 해제됩니다. 마지막으로 사용자가 뒤로 가기 버튼을 클릭하고 ChatRoom이 다시 마운트됩니다. 이렇게 되면 두 번째 연결이 설정되지만 첫 번째 연결은 종료되지 않았습니다! 사용자가 앱을 탐색하는 동안 연결은 종료되지 않고 계속 쌓일 것입니다. -> 이 문제를 해결하려면 Effect에서 클린업 함수를 반환하면 됩니다.
- React는 Effect가 다시 실행되기 전마다 클린업 함수를 호출하고, 컴포넌트가 마운트 해제(제거)될 때에도 마지막으로 호출합니다.
- https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/synchronizing-with-effects !!
무한루프 useEffect
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});
Comments