ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프로그래머스 데브코스 - 8월 3주차 WIL
    React 2023. 8. 21. 00:00

     

    🔔 서론

    학습에 지장이 생길 정도로 터무니없이 더운 날씨에게 괴롭힘들 당하는 8월 중순, 드디어 데브코스에서 react과정을 시작하게 됐다. 아마 대부분의 수강생들이 vue보다는 react를 더 많이 사용해보았으리라 생각하고 이번 주차부터 반가움을 금치 못 할거라 생각한다. 내가 react를 공부했던 이유는 단순했다. 주변사람들이 많이 썼기 때문이다. angular나 vue는 주변에서 사용하는 사람도 거의 없었고(실제로 0명..) 현업에서도 react를 많이 사용한다는 이야기를 들었기 때문에 자연스럽게 react를 접하게 됐다. 하지만 깊게 공부할 기회는 없었고 얕은 지식으로 이것저것 부딪히며 공부를 했었기에 부족한 점이 많은 상태여서 이번 react 과정은 부족한 점을 채울 수 있는 정말 좋은 기회라 생각하기에 이번주에 공부한 react 내용 중 인상 깊었거나 몰랐던 부분에 대해 정리하는 시간을 가져보고자 한다.


    🔔 useRef

    useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다. - react 공식문서

    useRef는 Dom에 직접 접근할 때나 지역 변수로 사용할 때 사용된다. 일반적으로 Dom에 직접 접근할 때 사용하는 경우가 친숙할 것이다. Dom에 접근하는 방식의 코드를 보면서 사용법을 살펴보자.다음 예제는 버튼을 누르면 input 태그에 포커싱을 하는 간단한 예시 코드이다.

    import { useRef } from 'react'
    
    function App() {
      const inputRef = useRef()
      
      return (
        <div>
          <input ref={inputRef} />
          <button onClick={() => inputRef.current.focus()}>Focus</button>
        </div>
      )
    }
    
    export default App

     

    이번에는 컴포넌트를 통해 ref정보를 넘겨주는 방식도 한번 알아보자

    ref 정보를 넘겨주고자 하는 컴포넌트를 React.forwardRef를 감싸주기만 하면 된다. 이 때, 함수 매개변수의 첫 번째 인자는 props이므로 받는 props가 없다면 생략 가능하고, 두 번째 인자로 ref를 받는다.

    import React from 'react'
    
    const Input = React.forwardRef((_, ref) => {
      return (
        <>
          Input: <input ref={ref} />
        </>
      )
    }
    
    export default Input

    그리고 ref 정보를 전달 할 컴포넌트에 Input 컴포넌트를 넣어주면 된다.

    import { useRef } from 'react'
    import Input from './components/Input'
    
    function App() {
      const inputRef = useRef()
      
      return (
        <div>
          <Input ref={inputRef} />
          <button onClick={() => inputRef.current.focus()}>Focus</button>
        </div>
      )
    }
    
    export default App

    🔔 emotion

    Emotion은 JavaScript로 css 스타일을 작성하도록 설계된 라이브러리입니다. 소스 맵, 레이블 및 테스트 유틸리티와 같은 기능을 통해 훌륭한 개발자 경험 외에도 강력하고 예측 가능한 스타일 구성을 제공합니다. 문자열 및 개체 스타일이 모두 지원됩니다. - emotion 문서

    emotion은 컴포넌트를 스타일링 하기 위한 CSS in JS 기법중 하나다.

    사용해보기 위해 설치를 해보자

    npm install @emotion/react @emotion/styled

     

    @emotion/react는 리액트 패키지인건 알겠는데 styled는 뭔가? 라는 생각이 든다면 @emotion/styled는 styled-component도 사용할 수 있게해주는 패키지이다. 나는 styled-component를 react를 하면서 많이 애용해왔기 때문에 같이 설치하기로 했다.

    다음은 emotion을 사용하기 위해 plugin도 설치해주어야 한다.

    npm install --save-dev @emotion/babel-plugin

     

    emotion을 사용하기 위해 .babelrc 파일에서 emotion plugin을 등록해야하는데 cra에서는 .babelrc파일을 생성한다고 해서 적용이 되지않기 때문에 다른 방법을 통해 적용할 건데, craco (create-react-app-config-override) 라이브러리를 사용해서 설정을 override해줄 수 있다.

    npm install -D @craco/craco

    그리고 craco.config.js 파일을 생성해 설정해주면 된다.

    module.exports = {
      babel: {
        presets: ['@emotion/babel-preset-css-prop'],
      }
    }

    물론 presets에 들어가는 babel도 설치를 해주어야한다.

    마지막으로 package.json에서 스크립트 실행을 react-scripts가 아닌 craco로 실행해주게 변경하면 적용이 된다.

    'scripts': {
      'start': 'craco start',
    }

    이제 적용시켜보자

    import { css } from '@emotion/react'
    
    const style = css`
      color: hotpink;
    `
    
    const PinkComponent = () => {
      <div css={style}>hotpink text</div>
    }
    
    function App() {
      return (
        <div>fdsfdsa
          <PinkComponent />
        </div>
      )
    }
    
    export default App

    🔔 useMemo

    “생성(create)” 함수와 그것의 의존성 값의 배열을 전달하세요. useMemo는 의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산 할 것입니다. 이 최적화는 모든 렌더링 시의 고비용 계산을 방지하게 해 줍니다. - react 공식 문서

    useMemo는 컴포넌트의 불필요한 리렌더링을 줄이는 최적화에 도움이 되는 hook으로 두개의 매개변수를 받고, 첫 번째 매개변수로 실행할 함수, 두번째 매개변수로 의존성을 넣는다.

    리렌더링은 보통 다음 세 가지 경우에 의해 발생한다.

    1. 컴포넌트 자신의 state가 변경될 때
    2. 부모 컴포넌트로부터 받는 prop이 변경될 때
    3. 부모 컴포넌트의 state가 변경될 때

    이럴 때 useMemo를 사용하면 이 불합리한 리렌더링을 막아줄 수 있다. 예시를 통해서 살펴보자

    다음 코드는 1부터 n까지의 합을 리턴해주는 컴포넌트이다.

    function sum(n) {
      let result = 0
      for (let i = 1; i <= n; i += 1) {
        result += i
      }
      return result
    }
    
    const ShowSum = ({ label, n }) => {
      const result = sum(n)
      return (
        <span>
          {label}: {result}
        </span>
      )
    }
    
    export default ShowSum

    그리고 위의 컴포넌트를 보여주는 부모컴포넌트도 만들어준다.

    import { useState } from 'react'
    import ShowSum from './components/ShowSum'
    
    function App() {
      const [label, setSLabel] = useState('Result')
      
      return (
        <div>
          <button onClick={() => setLabel(label + ':')}>Change Label</button>
          <ShowSum label={label} n = 100 />
        </div>
      )
    }
    
    export default App

    이제 버튼을 누를 때마다 label에 변화가 생길 때마다 ShowSum 컴포넌트가 리렌더링 된다.
    당장은 아무 문제 없어보이지만 n의 값이 100이 아니라 상당히 큰 수라면 리렌더링되는데 시간이 오래 걸릴 수 있다.

    따라서, n에 변화가 생겼을 때만 리렌더링 될 수 있게 useMemo에 n을 의존하게 하면 된다.

    import { useMemo } from 'react'
    
    function sum(n) {
      let result = 0
      for (let i = 1; i <= n; i += 1) {
        result += i
      }
      return result
    }
    
    const ShowSum = ({ label, n }) => {
      const result = useMemo(() => sum(n), [n])
      return (
        <span>
          {label}: {result}
        </span>
      )
    }
    
    export default ShowSum

    🔔 React.memo

    useMemo를 공부할 때 리렌더링이 발생하는 세 가지 경우를 봤었다.

    1. 컴포넌트 자신의 state가 변경될 때
    2. 부모 컴포넌트로부터 받는 prop이 변경될 때
    3. 부모 컴포넌트의 state가 변경될 때

    1번과 2번은 그럴만하다 생각하지만 3번의 경우는 조금 불합리하다고 느껴진다. 이 때 React.memo를 사용하면 자식컴포넌트가 변경되지 않았을 경우에는 리렌더링을 막을 수 있게 해준다.

    다음 코드는 누를때마다 1씩 증가하는 버튼을 눌렀을 때 Box 컴포넌트가 리렌더링이 되고있다.

    const Box = () => {
      console.log('render')
      return <div>Box render</div>
    }
    
    export default Box
    import { useState } from 'reat'
    import Box from './components/Box'
    
    function App() {
      const [count, setCount] = useState(0)
      
      return (
        <div>
          {count}
          <button onClick={() => setCount(count + 1)}>+</button>
          <Box />
        </div>
      )
    }
    
    export default App

    이 때 Box 컴포넌트를 React.memo로 감싸주면 counter버튼을 누르더라도 더 이상 Box 컴포넌트가 리렌더링 되지 않는다.

    import React from 'react'
    
    const Box = () => React.memo({
      console.log('render')
      return <div>Box render</div>
    })
    
    export default Box

    🔔 useCallback

    인라인 콜백과 그것의 의존성 값의 배열을 전달하세요. useCallback은 콜백의 메모이제이션된 버전을 반환할 것입니다. 그 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때에만 변경됩니다. 이것은, 불필요한 렌더링을 방지하기 위해 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용합니다. - react 공식 문서

    react의 컴포넌트는 함수이다. 컴포넌트가 리렌더링된다는것은 함수가 다시 정의, 실행된다는것이고 이 때 재실행 되는 것을 막기위해 useMemo를 사용했다. useCallback은 함수가 재정의 되는것을 막기 위해 사용할 수 있다.
    사용법은 재정의를 막기위한 함수를 useCallback으로 감싸고 의존성배열을 useCallback의 두번째 인자로 넣어주면 된다.


    🔔 Custom Hook

    custom hook은 자주 사용되는 상태 로직을 별도 custom hook으로 빼두면 중복코드를 제거할 수 있고 코드가 깔끔해져 가독성이 좋아진다. 

    다음 코드는 이벤트가 발생할 때 true, false로 상태가 toggle되는 custom hook이다.

    import { useCallback, useState } from 'react'
    
    const useToggle = (initialState = false) => {
      const [state, setState] = useState(initialState)
      const toggle = useCallback(() => setState((state) => !state), [])
      
      return [state, toggle]
    }
    
    export default useToggle

     

    import useToggle form './hooks/useToggle'
    
    function App() {
      const [on, toggle] = useToggle()
      
      return (
       <div>
         <button onClick={toggle}>{on}</buton>
       </div>
      )
    }

     


    🔔 Storybook

    storybook은 ui 컴포넌트를 모아서 문서화하고 보여주는 오픈소스 툴이다. 컴포넌트를 stroybook에 등록시켜 놓으면 어떤 컴포넌트가 있는지 쉽게 확인할 수 있다. 

    https://storybook.js.org/

     

    Storybook: Frontend workshop for UI development

    Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It’s open source and free.

    storybook.js.org

    사용법과 활용법에 대해서는 내용이 방대하기 때문에 따로 포스트를 작성해서 알아보도록 하겠다.

    'React' 카테고리의 다른 글

    프로그래머스 데브코스 - 8월 4주차 WIL  (0) 2023.08.27
Designed by Tistory.