Logo
Search|
Published on

Reading React Official Documents (2)

Authors
  • avatar
    Name
    Easyoon
    Twitter

[React] Official Docs memo 2

무엇을 state로 관리할 것인지 등

컴포넌트 개발 전 고민

1. Mock-Up (JSON 등)으로 시작

2. 화면을 UI 컴포넌트로 구성할 때

  • 간단한 경우 : 하층부에 있는 컴포넌트에서 상층부에 있 는 컴포넌트의 순서로 개발

  • 프로젝트가 큰 경우 : 상향식으로 만들고 테스트를 작성하는 쪽이 빠름

3. Props & State 판단

  1. 부모로부터 props를 통해 전달하는 값 => props

  2. 시간이 지나도 변하지 않는 값=> props

  3. state나 props를 가지고 계산 가능한 값 => props

  • 제품의 원본 목록은 props를 통해 전달되므로 state가 아님

  • 검색어와 체크박스는 state, 시간이 지남에 따라 변하기도 하면서 다른 것들로부터 계산될 수 없다.

  • 필터링 된 목록 => state가 아님. 제품의 원본 목록과 검색어, 체크박스의 값을 조합해서 계산해낼 수 있기 때문

4. 애플리케이션이 가지는 각각의 state에 대해

  • state를 기반으로 렌더링하는 모든 컴포넌트 찾기

  • 공통 소유 컴포넌트 (common owner component)를 찾으세요. (계층 구조 내에서 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트)

  • **공통 혹은 더 상위에 있는 컴포넌트가 state를 가진다 **(만약, state를 소유 할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 소유 컴포넌트의 상위 계층에 추가)

5. Result

state로 관리할 부분: 검색어와 체크박스 상태 표시

ProductTable : state에 의존한 상품 리스트의 필터링 SearchBar : 검색어와 체크박스의 상태를 표시 FilterableProductTable : 위 두 컴포넌트를 소유한 공통 소유 컴포넌트

  • 의미상으로도 FilterableProductTable이 검색어와 체크박스의 체크 여부를 가지는 것이 타당
this.state = {filterText: , inStockOnly: false} 

를 FilterableProductTable의 constructor에 추가하여 애플리케이션의 초기 상태를 반영 후, filterText와 inStockOnly를 ProductTable와 SearchBar에 prop으로 전달하는 식.

setState()

  • props를통해 콜백 함수를 전달하는 것이 일반적
    updateCount() {
      this.setState((prevState, props) => {
        return { count: prevState.count + 1 }
      });
    }
    
    // DO NOT USE
    this.setState({
      count: this.state.count + 1
    });

setState상태를 직접 업데이트하는 대신 콜백 함수를 가져와야 함. 콜백 내에서 prevState액세스 가능

initialState

initialState 인자는 초기 렌더링 시에 사용하는 state로, 그 이후의 렌더링 시에는 이 값은 사용 되지 않음

초기 state가 고비용 계산의 결과라면, 초기 렌더링 시에만 실행될 함수를 대신 제공할 수 있습니다.

    const [state, setState] = useState(() => {
      const initialState = someExpensiveComputation(props);
      return initialState;
    });

Hook

왜 Vue나 React가 재사용 가능한 동작을 구성 요소에 “부착”하는 방법(예: 저장소에 연결)에서 hooks로 넘어가는가

: 구성 요소 간에 상태 저장 논리를 재사용하기 어려워서

  • Hooks를 사용하면 컴포넌트에서 상태를 추출하여 독립적으로 테스트하고 재사용 가능

  • Hooks를 사용하면 컴포넌트 계층 구조를 변경하지 않고도 컴포넌트간에 쉽게 공유 가능

useEffect Hook

컴포넌트의 렌더링 이후 다양한 사이드 이펙트를 제어

정리(clean-up)를 이용하는 Effects hook

Effect에 정리(clean-up)가 필요한 경우에는 함수를 return

    useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });

정리(clean-up)가 필요없는 경우

    useEffect(() => {
        document.title = `You clicked ${count} times`;
      });

Tip) Effect 내 로직이 많이 필요한 경우, 구분을 위해서 useEffect를 여러번 구분해서 호출

Tip) Effect를 건너뛰어 성능 최적화

: props.friend.id 값이 변경 될 경우에만 재렌더링

    useEffect(() => {
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    }, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.

  • 주의

배열이 컴포넌트 범위 내에서 바뀌는 값들과 effect에 의해 사용되는 값들을 모두 포함해야 함

그렇지 않으면 현재 값이 아닌 이전의 렌더링 때의 값을 참고

effect를 실행하고 이를 정리(clean-up)하는 과정을 (마운트와 마운트 해제 시에)딱 한 번씩만 실행하고 싶다면, 빈 배열([])을 두 번째 인수로 넘김

*아래 참고

    function Example({ someProp }) {
      function doSomething() {
        console.log(someProp);  }
    
      useEffect(() => {
        doSomething();
      }, []); 
    }
      // 🔴 이것은 안전하지 않습니다 (`someProp`을 사용하는`doSomething`을 호출합니다)}

    function Example({ someProp }) {
      useEffect(() => {
        function doSomething() {
          console.log(someProp);    }
    
        doSomething();
      }, [someProp]); // ✅ OK (우리 effect는`someProp` 만 사용합니다)}

    useEffect(() => {
      function doSomething() {
        console.log('hello');
      }
    
      doSomething();
    }, []); // ✅ 이 예에서는 컴포넌트 범위의 *어떤* 값도 사용하지 않기 때문에 좋습니다
    }
function ProductPage({ productId }) {
      const [product, setProduct] = useState(null);
    
      async function fetchProduct() {
        const response = await fetch('http://myapi/product/' + productId); 
        // productId props 사용
        const json = await response.json();
        setProduct(json);
      }
    
      useEffect(() => {
        fetchProduct();
      }, []); // 🔴 `fetchProduct`가`productId`를 사용하므로 잘못되었습니다 
    }

    function ProductPage({ productId }) {
      const [product, setProduct] = useState(null);
    
      useEffect(() => {
        // 이 함수 컴포넌트를 effect 내부로 이동하면 사용하는 값을 명확하게 볼 수 있습니다.
        async function fetchProduct() {
          const response = await fetch('http://myapi/product/' + productId);
          const json = await response.json();
          setProduct(json);
        }
        fetchProduct();
      }, [productId]); // ✅ 효과는 productId 만 사용하므로 유효합니다  
    }

Tip) Hook Linter Plugin

Hook을 사용할 때는 두 가지 규칙을 준수해야 하기 때문에, 이 규칙들을 자동으로 강제하기 위한 linter 플러그인을 제공

반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하지 말 것

(렌더링이 될 때마다동일한 순서로 Hook이 호출되는 것을 보장)

  // 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다
    if (name !== '') {
      useEffect(function persistForm() {
        localStorage.setItem('formData', name);
      });
    } 

조건부로 effect를 실행하기를 원한다면, 조건문을 Hook 내부에 넣으면 됨

  useEffect(function persistForm() {
      // 👍 더 이상 첫 번째 규칙을 어기지 않습니다
      if (name !== '') {
        localStorage.setItem('formData', name);
      }
    });

useReducer

useState의 대체 함수로 다음 2가지 경우에 사용

  • 다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우

  • 다음 state가 이전 state에 의존적인 경우

또한 useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 하는데, 이것은** 콜백 대신 dispatch를 전달 할 수 있기 때문**

(** 자세한 셈플 필요, reduce()를 dispatch와 함께 사용하는 예시)

아래는 useState 를 reducer를 사용해서 다시 작성한 예시 (모르겠음)

    const initialState = {count: 0};
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return {count: state.count + 1};
        case 'decrement':
          return {count: state.count - 1};
        default:
          throw new Error();
      }
    }
    function Counter() {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({type: 'decrement'})}>-</button>
          <button onClick={() => dispatch({type: 'increment'})}>+</button>
        </>
      );
    }

useMemo

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

메모이제이션된 값을 반환 (Vue로 따지면 Computed 개념)

useMemo로 전달된 함수는 렌더링 중에 실행 = 통상적으로 렌더링 중에는 하면 안되는 로직들을 넣으면 안됨 (렌더링 관련 로직은 UseEffect)

useImperativeHandle

사용을 지양 대부분의 경우 좀 더 나은 컴포넌트 설계로 필요한 이슈들을 해결할 수 있다.

useLayoutEffect

개념은 useEffect와 동일하긴 한데, 모든 DOM 변경 후에 동기적으로 발생, 따라서 DOM에서 레이아웃을 읽고 동기적으로 리렌더링하는 경우에 사용

먼저 useEffect를 사용해 보고 문제가 있다면 그다음으로 useLayoutEffect를 사용해 보기를 권장

+)서버 렌더링을 사용하는 경우라면 자바스크립트가 모두 다운로드될 때까지는 useLayoutEffect와 useEffect 어느 것도 실행되지 않는다