LIGHTLOG
article thumbnail

🎤 간단한 소감 

다시, 점심 뭐먹지 figma 시안

레벨2의 첫번째 온보딩 '다시, 점심 뭐먹지'미션에서는 ReactTypescript 에 처음으로 들어갔다!!! 😆
사실 리액트를 어느정도 알고 있다고 생각했지만, 그것은 나의 큰 오산이었다. 🗻

 

자신감이 많이 하락했긴 했지만, 이동욱님이 특강에서 말씀해주셨던
내 삶의 자신감을 잃게 해주지 않는 6가지의 기둥들을 나도 잘 마련해보고, 그곳에 집중하려 한다.

또한, 중요한 것은 '지금의 나?'가 아닌, '성장하고 있는 중이냐?'라는 점.

 

아마도 나는 우매함의 봉우리와 절망의 게곡 사이를 건너고 있는 중...

 

이번 회고글에서는

페어프로그래밍으로 미션을 진행하며,,

리뷰리뷰스터디💕에서 다른 크루들의 리뷰를 살펴보며,,

새롭게 알게된 점이나 사람들이 공통적으로 받았던 피드백에 대해 정리해보려 한다!

 

순서가 뒤죽박죽이라 옆에 중요도(3점 만점)를 표시해보았다 ㅎㅎ 

 


💡 알게된 점

1. Record Type vs Index Signature ⭐️

 

export const CATEGORY_IMG: Record<string, string> = {
한식: categoryKorean,
중식: categoryChinese,
일식: categoryJapanese,
아시안: categoryAsian,
양식: categoryWestern,
기타: categoryEtc,
} as const;

 

export const CATEGORY_IMG: { [key: string]: string } = {
한식: categoryKorean,
중식: categoryChinese,
일식: categoryJapanese,
아시안: categoryAsian,
양식: categoryWestern,
기타: categoryEtc,
} as const;

 

Record Type은 key로 문자열 리터럴을 허용한다!

 

또한 as const의 장점은 다음과 같다. 

- object나 array는 참조형이기 때문에 const로 선언하더라도, 내부 프로퍼티의 추론 범위가 한정되지 않고, 값을 변경할 수도 있다. 

- 근데 'as const'를 붙여주면, 객체의 모든 프로퍼티들이 readonly로 변경되고 각 프로퍼티의 타입이 할당된 리터럴 값으로 추론된다.

 


2. dom API를 통한 dom 조작보다는 ref 사용하자! ⭐️⭐️⭐️

 

// 클래스형 컴포넌트
class BottomSheet extends React.Component<BottomSheetType> {
  bottomSheetRef: React.RefObject<HTMLDivElement> =
    React.createRef<HTMLDivElement>();
  backdropRef: React.RefObject<HTMLDivElement> =
    React.createRef<HTMLDivElement>();

  componentDidMount() {
    setTimeout(() => {
      if (this.bottomSheetRef.current)
        this.bottomSheetRef.current.classList.remove("close_bottom_sheet");
      if (this.backdropRef.current)
        this.backdropRef.current?.classList.remove("close_background");
    });
  }

  render() {
    return (
      <ModalPortal>
        <BackDrop
          ref={this.backdropRef}
          id="backdrop"
          className="close_background"
          onClick={this.props.onClose}
        />
        <BottomSheetWrapper
          ref={this.bottomSheetRef}
          id="bottom_sheet"
          className="close_bottom_sheet"
        >
          {this.props.children}
        </BottomSheetWrapper>
      </ModalPortal>
    );
  }
}

 

// 함수형 컴포넌트
const BottomSheet = (props: BottomSheetType) => {
  const bottomSheetRef = useRef<HTMLDivElement>(null);
  const backdropRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    setTimeout(() => {
      if (bottomSheetRef.current)
        bottomSheetRef.current.classList.remove("close_bottom_sheet");
      if (backdropRef.current)
        backdropRef.current.classList.remove("close_background");
    });
  }, []);

  return (
    <ModalPortal>
      <BackDrop
        ref={backdropRef}
        id="backdrop"
        className="close_background"
        onClick={props.onClose}
      />
      <BottomSheetWrapper
        ref={bottomSheetRef}
        id="bottom_sheet"
        className="close_bottom_sheet"
      >
        {props.children}
      </BottomSheetWrapper>
    </ModalPortal>
  );
};

 

기존 DOM API의 querySelector를 사용해

바텀시트가 열리고 닫히는 애니메이션 css를 추가 및 삭제해주었는데

Ref를 사용하는 방법으로 변경하였다!

 

여기서 잠깐, 

React에서는 DOM API 사용이 지양될까?

React에서 document.querySelector를 사용하게되면, 실제 DOM의 요소를 가져오게 된다. 하지만 React는 Virtual DOM을 통해 Real DOM을 그리기 때문에, React가 제어하고있는 Virtual DOM 안에 있는 요소가 더 신뢰할만하다. DOM API로 Real DOM에 있는 친구를 집었는데, 이게 현재 Virtual DOM을 통해 Real DOM에 존재하는 친구인지 아닌지 확신할 수 없다는 것이다!
React를 사용하게되면, 가장 중요한 개념 중 하나가 바로바로 State이다. React 내부에서 데이터는 컴포넌트 내의 State으로 조작된다.  즉, React가 State를 컨트롤(제어)하고 있다. 만약 이러한 React 시스템을 벗어나 DOM을 직접적으로 건드리게되면 이 내용들은 React가 제어하는 영역에서 벗어나게 되고, 이렇게 React의 제어를 벗어나게 되면, React에서 제공하는 이점들을 사용할 수 없게 된다.
또 이렇게 React가 제어하는 State와 제어하지 않는 State을 혼용해서 사용해 데이터를 조작할 경우, 위에 언급했듯이 React의 Lifecycle에 맞추어 DOM Element를 가져오지 못해 가져온 DOM Element를 신뢰할 수 없어지는 문제가 발생한다. 이렇게 데이터를 어디에서 어떻게 조작하고 있는지 예측하기 어렵기 때문에 디버깅 또한 어려워진다.

 

Ref가 무엇인지에 대해서도 자세히 알아보았다. 
react 공식문서에 따르면, 

Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다.

 

하지만, ref는 남용해서는 안되며 특정 바람직한 사용 사례가 정해져있다고 한다.

Ref의 바람직한 사용 사례는 다음과 같습니다. 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때, 애니메이션을 직접적으로 실행시킬 때, 서드 파티DOM 라이브러리를 React와 같이 사용할 때입니다. Ref를 남용하지 마세요. ref는 애플리케이션에 “어떤 일이 일어나게” 할 때 사용될 수도 있습니다. 그럴 때는 잠시 멈추고 어느 컴포넌트 계층에서 상태를 소유해야 하는지 신중하게 생각해보세요. 대부분의 경우, 상태를 소유해야 하는 적절한 장소가 더 높은 계층이라는 결론이 날 겁니다.

 

 

3. 'noscript'의 역할 ⭐️

cra후, index.html에서 아무생각 없이 지우던 부분이

script가 안 읽혀서 로드가 안될 때 뜨게 되는 내용을 담고 있는 태그라 한다...

 

<noscript>
  <h2>이 페이지를 사용할 수 없습니다.</h2>
  <p>스크립트 사용이 비활성화 되어 있습니다.</p>
  <p>스크립트 사용을 활성화 시켜 주세요.</p>
</noscript>

 

 

4. PropsWithChildren ⭐️⭐️

컴포넌트를 구현할때, props로 children을 선언해줄 때가 종종 있었는데, 

그것에 대한 번거로움을 해결해주는 PropsWithChildren이라는 친구가 있다는 것을 알게되었다!

 

interface Props {
  isOpened: boolean;
  handleCloseModal: () => void;
}

function Modal({
  isOpened,
  handleCloseModal,
  children,
}: PropsWithChildren<Props>) {
  return (
    <Portal isOpened={isOpened}>
      <>
        <Dimmer hasBackgroundColor={true} onClick={handleCloseModal} />
        {children}
      </>
    </Portal>
  );
}

 

하지만, PropsWithChildren 타입을 살펴보면

children이 옵셔널로 선언되어있기 때문에

항상 children을 넘겨줘야하는 경우에는

PropsWithChildren을 사용하지 않는게 나을 수도 있다!

 

 

5. 디버깅할 때, 콘솔로그가 매번 2번씩 찍히는 이유? -> React.StrictMode ⭐️⭐️

항상, useEffect안의  콘솔로그를 확인할 때 두 번씩 찍혀서

내 컴포넌트 상의 로직이 문제가 있는줄 알았다...

 

범인은 요놈,

 

root.render(
  <React.StrictMode>
    <GlobalStyle />
    <App />
  </React.StrictMode>
);

 

다음과 같은 도움이 된다고 한다. 

- 안전하지 않는 생명주기를 사용하는 컴포넌트 발견

- 레거시 문자열 ref 사용에 대한 경고

- 권장되지 않는 findDOMNODE 사용에 대한 경고

- 예상치 못한 부작용 검사

- 레거시 context API 검사

- Ensuring reusable state

 

그래서, React.StrictMode를 적용한 상태로 개발하는게 좋다.

useEffect 두번 실행되는 그런 것들은 개발할때만 그런 것이고, 실제 배포해서는 사라진다고 함!!

 

 

6. import/order -> (import 정렬 eslint) ⭐️

import 정렬 매번 수작업으로 해주고는 햇는데

eslint설정으로 자동 정렬되게 해줄 수 있다고 한다!

 

"import/order": [
      "error",
      {
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent", 
          "sibling",
          "object",
          "type",
          "index"
        ],
        "alphabetize": {
          "order": "asc"
        }
      }
    ]

 

 

7. 컴포넌트 라이프 사이클 ⭐️⭐️⭐️

 

추가적으로 componentDidMountcomponenetWillMount의 차이에 대해 알아보자면

-  componentDidmount() : 컴포넌트가 마운트된 직 후에 호출됩니다. 외부에서 데이터를 불러와야 한다면 네트워크 요청을 보내기 적절한 위치라고 할 수 있습니다.
- componentWillmount(): 마운트가 발생하기 전에 호출됩니다. render가 실행되기 전에 실행되기 때문에 이 메서드 안에서는 setState를 사용하더라도 추가적인 랜더링이 발생하지 않으니 주의해주세요.

 

 

8. a태그의 noreferrer, noopener는 어떤 설정인가? ⭐️

 

<a href={link} target="_blank" rel="noreferrer" className={styles.link}>

 

둘 다, window.opener 객체와 관련된 악의적인 동작을 조작하지 못하게 해준다고 한다.👍🏻

다음미션부터 적용해야지!

 

 

9. type import 활용하기 ⭐️

 

import { Restaurant } from '../../domain/type'; // 보다는
import type { Restaurant } from '../../domain/type'; // 이렇게
export type { ~~ }; // export에서도!

 

 

10. Basic한 컴포넌트에서 속성 정의할때... ⭐️⭐️

Button이나 Input과 같은 basic한 컴포넌트를 구현할 때

당연한 속성들을 주르륵 정의해줘야할 때가 있다.

 

그럴땐 extends를 통해 태그가 가지는 속성을 그대로 가져와주자!

 

//보다는
interface ButtonProps {
text: string;
onClick: () => void;
}

//이렇게
interface ButtonProps extends HTMLAttributes<HTMLButtonElement>{
  text: string;
}

class Button extends React.Component<ButtonProps> {
  render() {
    const { text, ...rest } = this.props;

    return <ButtonWrapper {...rest}>{text}</ButtonWrapper>;
  }
}

 

 

11. 커링 함수 ⭐️⭐️⭐️

핸들러 함수에서 함수의 파라미터에 event객체 말고, 다른 인자를 넘겨줘야할 때 유용한 커링 함수에 대해 알게되었다!

 

// 커링함수 예시
const makeHandler = (id) => () => {
 // do something
} 

<Item onClick={makeHandler(1)} />
<Item onClick={makeHandler(2)} />

 

 

12. styled-componenets 의 css 함수 ⭐️⭐️

 

display: flex;
align-items: center;
justify-content: center;

 

와 같이 반복되는 style 정의에 유용한 것을 알게되었다. 

바로 css 함수!

 

const pattern = `
	fdfdaf
	fdsfdsfds
`; //그냥 문자열을 넣어줘도 되지만

const pattern2 = css`
	fdfdaf
	fdsfdsfds
	asdf: ${(props) => props.a}
`; // css 함수는 props를 사용할 수 있다.

const StyledComponent = styled`
	fdfd;
	fdsfdafd;
	fdfdfas;
	${css}
`;

 

 

13. useLayoutEffect ⭐️⭐️

useEffect 훅은 DOM의 레이아웃 배치와 페인트가 끝난 후 이펙트 함수를 호출하기 때문에

뷰에 나타난 상태값이 이펙트에 의존할 경우 불편한 사용자 경험으로 이어질 수 있다.

 

그럴땐 useEffect 훅과 거의 흡사한 useLayoutEffect 훅을 사용해보자!

 

import { useEffect, useState } from "react";

function App() {
  const [age, setAge] = useState(0);
  const [name, setName] = useState("");
  
  useEffect(() => {
    setAge(25);
    setName("찬민");
  }, []); 
  
  useLayoutEffect(() => {
    setAge(25);
    setName("찬민");
  }, []);
  
  return (
    <>
      <div className="App">{`그의 이름은 ${name} 이며, 나이는 ${age}살 입니다.`}</div>
    </>
  );
}

export default App;

 

useEffect
<div>그의 이름은 이며, 나이는 0살 입니다.</div> 를 페인트한 후에 
이펙트 내부의 setNumber, setName 호출하고
재렌더링 수행한다. -> <div>그의 이름은 찬민이며, 나이는 25살 입니다.</div>
  
useLayoutEffect
레이아웃 이펙트 내부의 setNumber, setName 호출
<div>그의 이름은 찬민이며, 나이는 25살 입니다.</div> 를 페인트한다.

 

 

14. 여러개의 if 문보다는 Lookup Table! ⭐️

 

const howIsBoo = (state) => {
  if (state === ‘HUNGRY’) return ‘WANTS FOOD’;
  if (state === ‘SAD’) return ‘CRYING’;
  if (state === ‘HAPPY’) return ‘LAUGHING’
  return ‘SLEEPING’
} // 보다는

const booFeelsTable = {
  ‘HUNGRY’: ‘WANTS FOOD’,
  ‘SAD’: ‘CRYING’,
  ‘HAPPY’: ‘LAUGHING’
} // 이렇게!

const howIsBoo = (state) => booFeelsTable[state] || ‘SLEEPING’;

 

 

15. setState가 비동기처럼 동작하는 이유 ⭐️⭐️⭐️

일단, setState는 비동기 함수가 아니다.

(함수 시그니처를 살펴보면, 리턴 타입이 Promise가 아니기 때문에)

 

가상돔과 관련되어서 리액트가 비동기적으로 작동하기 때문이다.

가상돔은 실제 돔을 업데이트시키기 전에 화면을 구성하기 위해 closure를 사용하여 각 참조값을 메모리에 저장한다.

 

const [count, setCount] = useState(0); 

const onClick = () => {
  setCount(count + 1);
  console.log(count) -> 0
}

// 콘솔로그가 0이 찍히는 이유는
// onClick 함수 내부에서 setCount가 1번 혹은 여러번 호출되든
// 다음 렌더링 시점, 즉 closure에서 참조되는 변수 값이 업데이트되기 전까지
// closure 내부에서 참조하는 count값은 항상 0이기 때문이다.

return (
  <div>
    <button onClick={onClick}>click me</button>
  </div>
 );

 

 

16. 확장자가 .tsx인 파일의 화살표함수에서 제네릭을 주는 방법 ⭐️

 

const f1 = <T extends {}>() => {};

 

profile

LIGHTLOG

@lightOnCoding

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!