[ 🌱 함께자라기 🌱 ]
React 공식문서를 정독하고 필요한 부분을 메모
🪂 공식문서
JSX는 HTML보다는 JavaScript에 가깝기 때문에,
camelCase 프로퍼티 명명 규칙을 사용
모든 항목은 렌더링 되기 전에 문자열로 변환
이런 특성으로 인해 XSS (cross-site-scripting) 공격을 방지
Babel은 JSX를 React.createElement() 호출로 컴파일
React 엘리먼트 렌더링 과정
ReactDOM.createRoot(...) & root.render(...)
UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이를 root.render()로 전달하는 것 => React 엘리먼트는 불변객체이기 때문
React 컴포넌트는 props를 다룰 때 반드시 순수 함수처럼 동작해야함
State 업데이트는 비동기적일 수도 있음
=> 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리
❗️다음 state를 계산할 때 해당 값에 의존하면 안됨
=> 💡**인자를 함수로!!**
// 비동기로 처리되어 원하는 결과값이 나오지 않음 => +1
const plus1 = () => {
setCount(count + 1);
setCount(count + 1);
};
// 인자를 함수로 전달 => +2
const plus2 = () => {
setCount((count) => count + 1); // 현재 state를 참조하지 않고 state를 변경해야 하는 방법
setCount((count) => count + 1); // setCount에 대한 내부 호출이 count에 최신 값을 사용
};
참고 - 코드펜에 작성해봄
=> 이전 state를 사용해서 새로운 state를 계산하는 경우 함수를 setState 로 전달 가능
단방향식 데이터 흐름
=> 모든 state는 항상 특정한 컴포넌트가 소유,
그 state로부터 파생된 UI 또는 데이터는 자신의 “아래”에 있는 컴포넌트에만 영
향
조건이 false라면 React는 무시하고 건너뜁니다.
falsy 표현식을 반환하면 여전히 && 뒤에 있는 표현식은 건너뛰지만
❗️falsy 표현식이 반환
된다는 것에 주의
return (
<div>
{unreadMessages.length > 0 &&
<h2>You have unreadMessages</h2> // 무시됨
}
</div>
);
// <div>0</div>
render() {
const count = 0;
return (
<div>
{count && <h1>Messages: {count}</h1>}
</div>
);
}
가끔 다른 컴포넌트에 의해 렌더링될 때 컴포넌트 자체를 숨기고 싶을 때
=> 렌더링 결과를 출력하는 대신 null을 반환하면 해결
function WarningBanner(props) {
if (!props.warn) {
return null;
}
...
}
Index as a key is an anti-pattern
1️⃣ the list and items are static–they are not computed and do not change;
2️⃣ the items in the list have no ids;
3️⃣ the list is never reordered or filtered.
👉 세가지 다 만족하는 경우 안전하게 index를 key로 사용
Key는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없음
=> 두 개의 다른 배열을 만들 때 동일한 key를 사용 가능
제어 컴포넌트 (Controlled Component)
React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()
에 의해 업데이트 -> input의 값은 항상 React state에 의해 결정
=> 신뢰 가능한 단일 출처 (single source of truth)
+❓🤔 vue의 v-model와 차이점은 뭘까
React에서는 selected 어트리뷰트를 사용하는 대신 최상단 select태그에 value 어트리뷰트를 사용
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
</select>
select 태그에 multiple 옵션을 허용한다면 value 어트리뷰트에 배열을 전달
<select multiple={true} value={['B', 'C']}>
file input 태그는 값이 읽기 전용이기 때문에 비제어 컴포넌트
state를 끌어올리는 작업(Lifting state up)
은 양방향 바인딩 접근 방식보다 더 많은 *보일러 플레이트 코드를 유발하지만, 버그를 찾고 격리하기 더 쉽게 만듬
React는 강력한 합성 모델을 가지고 있으며,
상속 대신 합성을 사용하여 재사용하는 것을 권장
합성의 방법은 담기(Containment)와 특수화(Specialization)
Containment는 props.children
을 사용하여 상위 컴포넌트에서 정의한 자식 요소들을 출력
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
</FancyBorder>
);
}
function FancyBorder(props) {
return (
<div>
{props.children} // 여기에 h1태그 출력됨
</div>
);
}
+❓🤔 vue의 slot이랑 차이점은 뭘까
Specialization은 구체적인 컴포넌트가 일반적인 컴포넌트를 렌더링하고 props를 통해 내용을 구성
단일책임원칙 : 하나의 컴포넌트는 한 가지 일을 하는게 이상적이라는 원칙
리액트로 사고하기 5단계
1️⃣ UI를 컴포넌트 계층 구조로 나누기
2️⃣ React로 정적인 버전 만들기
3️⃣ UI state에 대한 최소한의 표현 찾아내기
=> 중복배제원칙(DRY) : 가장 최소한의 state
4️⃣ State가 어디에 있어야 할 지 찾기
5️⃣ 역방향 데이터 흐름 추가하기
state가 되기 위한 조건
1) 부모로부터 props를 통해 전달되지 않아야 함
2) 시간이 지나면 변해야 함
3) 컴포넌트 안의 다른 state나 props를 가지고 계산 가능하지 않아야 함
state 소유하는 컴포넌트 찾기
1) 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 함
2) 적절한 컴포넌트를 찾지 못하였다면, 공통 소유 컴포넌트의 상위 계층에 추가
🪡 Hook
함수 컴포넌트에서 React state와 생명주기 기능을 연동(hook into)할 수 있게 해주는 함수
=> class 없이 React를 사용할 수 있게 해줌
Hook은 React 버전 16.8부터 React 요소로 새로 추가,
기존 Class 바탕의 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용
Hook의 등장배경 및 장점
1) Hook은 계층의 변화 없이 상태 관련 로직을 재사용할 수 있도록 도와줌
2) 생명주기 메서드를 기반으로 쪼개는 것 보다는, Hook을 통해 서로 비슷한 것을 하는 작은 함수의 묶음으로 컴포넌트를 나누는 방법을 사용가능 (useEffect)
=> Hook을 사용하면 구독을 추가하고 제거하는 로직과 같이 서로 관련 있는 코드들을 한군데에 모아서 작성가능, 반면 class 컴포넌트에서는 생명주기 메서드 각각에 쪼개서 넣어야만 함
결론적으로 코드가 무엇을 하는지에 따라 나눌 수가 있음
3) Class 컴포넌트가 최적화를 더 느린 경로로 되돌리는 의도하지 않은 패턴을 장려할 수 있다는 것이 발견됨
4) Class는 코드의 최소화를 힘들게 만들고, 핫 리로딩을 깨지기 쉽고 신뢰할 수 없게 함
useState
는 class의 this.setState
와 거의 유사하지만, 이전 state와 새로운 state를 합치지 않음
useState
초기값은 첫 번째 렌더링에만 딱 한번 사용
다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업 등을 side effects
라고 함, useEffect
는 함수 컴포넌트 내에서 이런 side effects를 수행
=> ex) 데이터 가져오기, 구독(subscription) 설정하기, 수동으로 React 컴포넌트의 DOM을 수정하는 것 등
useEffect
는 React class의 componentDidMount
나 componentDidUpdate
, componentWillUnmount
와 같은 목적으로 제공되지만, 하나의 API로 통합된 것
Effects는 컴포넌트 안에 선언되어있기 때문에 props와 state에 접근가능
❗️ Hook 사용 규칙
1) 최상위에서만 Hook을 호출.
반복문, 조건문, 중첩된 함수 내에서 Hook을 실행 x
=> React가 특정 state가 어떤 useState 호출에 해당하는지 알기위해 Hook이 호출되는 순서에 의존하기 떄문
2) React 함수 컴포넌트 내에서만 Hook을 호출.
일반 JavaScript 함수에서는 Hook을 호출 x
상태 관련 로직을 컴포넌트 간에 재사용하고 싶은 경우
전통적으로 고차 컴포넌트와 render props 사용,
Custom Hook
은 이들 둘과는 달리 컴포넌트 트리에 새 컴포넌트를 추가하지 않음
Custom Hook
은 기능이라기보다는 컨벤션
1) 이름이 use
로 시작하고
2) 안에서 다른 Hook을 호출
일반적으로 일반 변수는 함수가 끝날 때 사라지지만, state변수
는 React에 의해 사라지지 않음
useState
는 클래스 컴포넌트의 this.setState와 달리 state를 갱신하는 것은 병합하는 것이 아니라 대체하는 것
useEffect Hook을 이용하여 우리는 React에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는 지를 말함
=> React는 함수를 기억했다가 DOM 업데이트를 수행한 이후에 call
useEffect
를 컴포넌트 안에서 불러내는 이유: 클로저를 이용하여 state, prop에 접근 가능
리렌더링하는 때마다 모두 이전과 다른 effect로 교체하여 전달
=> 각각의 effect는 특정한 렌더링에 속하게 됨
componentDidMount
혹은 componentDidUpdate
와는 달리 useEffect
는 브라우저가 화면을 업데이트하는 것을 차단 x
즉, 레이아웃 배치와 그리기를 완료한 후 발생
=> 이를 통해 애플리케이션의 반응성을 향상
useEffect
는 비동기적 실행
💡 동기적 실행이 필요한 경우에는 useEffect와 동일한 API를 사용하는 useLayoutEffect
라는 별도의 Hook이 존재
useEffect
는 컴포넌트들이 render 와 paint 된 후 실행
useLayoutEffect
는 컴포넌트들이 render 된 후 실행되며 그 이후에 paint
참고) useEffect vs useLayoutEffect
effect는 한번이 아니라 렌더링이 실행되는 때마다 실행
=> React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect 또한 정리하는 이유
setState
는 안정적이고 리렌더링 시에도 변경되지 않을 것이라는 것을 보장
=> useEffect나 useCallback 의존성 목록에 이 함수를 포함하지 않아도 무방
initialState
인자는 초기 렌더링 시에 사용하는 state로 이후의 렌더링 시에는 이 값은 무시됨
초기 state가 고비용 계산의 결과라면, 초기 렌더링 시에만 실행될 함수를 대신 제공
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적인 경우에 보통 useState보다 useReducer를 선호
useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 하는데, 이것은 콜백 대신 dispatch를 전달 할 수 있기 때문
useReducer 초기화지연방법: init 함수를 세 번째 인자로 전달
useReducer(reducer, initialCount, init);
useCallback
은 메모이제이션된 콜백을 반환하며 콜백의 의존성이 변경되었을 때에만 변경
불필요한 렌더링을 방지하기 위해 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용
useMemo
는 메모이제이션된 값을 반환하며 의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산
=> 모든 렌더링 시의 고비용 계산을 방지
useMemo
로 전달된 함수는 렌더링 중에 실행
=> ❗️ effects같이 렌더링 중에는 하지 않는 것을 이 함수 내에서 하면 안됨
useRef
는 .current 프로퍼티
로 변경 가능한 ref 객체를 반환하며
매번 렌더링을 할 때 동일한 ref 객체를 제공
useRef는 매번 렌더링을 할 때 동일한 ref 객체를 제공
❗️ useRef
는 내용이 변경됨을 알려주지는 않아 .current 프로퍼티
를 변형하는 것이 리렌더링을 발생시키지는 않음
=> React가 DOM 노드에 ref를 attach하거나 detach할 때 어떤 코드를 실행하고 싶다면 대신 콜백 ref를 사용
부모 컴포넌트에서 자식컴포넌트의 ref를 사용하고 싶을 때 useImperativeHandle
사용
/*
<FancyInput ref={inputRef} />를 렌더링한 부모 컴포넌트는
inputRef.current.focus()를 호출 가능
*/
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput); // forwardRef와 더불어 사용
useDeferredValue
는 debounce, throttling과 같은 역할을 함
활용 사례
useTransition
은 일부 상태 업데이트를 급하지 않은 업데이트로 간주하여 급한 상태 업데이트가 급하지 않은 상태 업데이트를 중단할 수 있음
useId
는 client-server사이드에서 hydration(HTML 코드와 JS 코드를 서로 매칭시켜 동적인 웹사이트를 브라우저에 랜더링하는 기술)미스매치
를 피하기 위해서 unique ID를 만들어주는 훅
❗️ list의 key를 만들어주기 위한 훅이 아님
useInsertionEffect
는 useEffect
와 비슷하지만 모든 돔 변화 이전에 실행되며 레이아웃을 읽기전 스타일 주입을 위해 사용
shouldComponentUpdate
구현 => React.memo
로 래핑하여 props를 얕게 비교 가능
React.memo
≈ PureComponent
useRef
Hook은 DOM ref뿐 아니라 ref
객체는 현재 프로퍼티가 변경할 수 있고 어떤 값이든 보유할 수 있는 일반 컨테이너로 class의 인스턴스 프로퍼티와 유사
이전 props 또는 state를 얻는 방법
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
DOM 노드의 위치나 크기를 측정하는 기본적인 방법 - 콜백 ref
를 사용
unction MeasureExample() {
const [height, setHeight] = useState(0);
// 콜백 ref를 사용하면 나중에 측정된 노드를 표시하더라도 이에 대한 알림을 받고 측정을 업데이트 가능
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
컴포넌트의 크기가 조정될 때마다 알림을 받으려면 ResizeObserver 사용 가능
Effect 외부의 함수 컴포넌트에서 어떤 props 또는 state를 사용하는지 기억하기 어려움
=> 일반적으로 그 내부의 effect에 필요한 함수 컴포넌트를 선언
어떤 이유로 함수를 effect 내부로 이동할 수 없는 경우
function ProductPage({ productId }) {
// 모든 렌더링에서 변경되지 않도록 useCallback으로 래핑
const fetchProduct = useCallback(() => {
// ...
}, [productId]); // 모든 useCallback 종속성이 지정됩니다
return <ProductDetails fetchProduct={fetchProduct} />;
}
function ProductDetails({ fetchProduct }) {
useEffect(() => {
fetchProduct();
}, [fetchProduct]); // 모든 useEffect 종속성이 지정됩니다
}
useMemo
를 사용하면 자식의 값비싼 리렌더링 건너뛰기 가능
function Parent({ a, b }) {
// 'a'가 변경된 경우에만 다시 렌더링 됩니다:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// 'b'가 변경된 경우에만 다시 렌더링 됩니다:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
고비용의 객체를 지연해서 생성하는 법
function Image(props) {
const ref = useRef(null);
// IntersectionObserver는 한 번 느리게 생성됩니다
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// 필요할 때 getObserver()를 호출
}