Skip to content

Latest commit

 

History

History
490 lines (427 loc) · 24 KB

20210724.md

File metadata and controls

490 lines (427 loc) · 24 KB

React

인상 깊은 조언

  • 책은 참고용으로 두고, 공식문서 영어로 읽는게 가장 최신의 정확한 정보를 얻는 길이니 공식문서로 공부하자.

Function Component & Hooks

  • class component와 대조되며, 이제는 대부분 function component 많이 쓴다.
  • 기본적인 형태는 함수와 같으며 렌더링할 JSX 객체를 반환한다.

class component에서 function component으로 가면서 바뀐 것

  • 상태관리: this.statethis.setState로 관리하던 것을 이제는 useState로!
  • Life Cycle Method: constructor, componentDidMount, componentWillUnmount, componentDidUpdateuseEffect로!
  • props: 함수의 파라미터가 prop로, 그 props 그대로 useState에 사용
  • shouldComponentUpdate는 memo로 구현

Function Component에서의 상태관리

useState

  • 상태를 정의하는 함수
    • 첫 번째 파라미터에 값을 넘기면 그 값이 상태의 초기값이 된다.
  • 두 요소를 가진 배열을 반환
    • 첫 번째 요소는 관리할 상태
    • 두 번째 요소는 그 상태를 업데이트하는 setter 함수이다.
    • 배열 디스트럭쳐링 할당으로 받는다.
  • class component의 this.setState는 merge 방식, function component의 useState는 override 방식으로 상태를 업데이트
    • 객체를 병합하는 것이 아니라 setter함수가 받은 값으로 바꿔치기
  • setter 함수는 값을 받을 수도, 함수를 받을 수도 있다.
    • 값을 받으면 비동기적으로 그 값으로 상태를 업데이트
    • 함수를 받으면 함수의 첫 번째 파라미터에 이전 상태를 전달
import { useState } from 'react'

function App() {
  return (
    <Counter />
  )
}

function Counter() {
  const [ count, setCount ] = useState(0);
  const handleClick = () => {
    setCount((prevCount) => prevCount +1 );
  }
  return (
    <section>
      <span>{count}</span>
      <button onClick={handleClick}>Plus 1</button>
    </section>
  )
}

Lazy Initial State

  • useState에 전달된 초기상태는 첫 렌더링 이후에는 무시된다.
    • 초기 상태를 계산하는 과정이 비싼 연산이라면?
import { useState } from 'react'

function getCount() {
  return Array(100000).fill(1).reduce((acc, cur)=> acc + cur, 0)
}

function App() {
  return (
    <Counter />
  )
}

function Counter() {
  const expensivelyCalculatedCount = getCount()
  const [ count, setCount ] = useState(expensivelyCalculatedCount)
 
  const handleClick = () => {
    setCount( prevCount => prevCount + 1 )
  }
  
  return (
    <section>
      <span>{count}</span>
      <button onClick={handleClick}>Plus 1</button>
    </section>
  )
}
  • 위와 같은 작업은 handleClick이 호출되어 리렌더링 될때마다 비싼 연산을 계속 한다.
  • 이를 방지하기 위한 것이 Lazy Initial State
  • 비싼 연산을 하는 함수를 호출한 결과를 초기값으로 넣는 것이 아니라, 비싼 연산을 하는 함수를 호출하는 함수를 useState의 파라미터에 콜백으로 넣는다.
// Counter 함수 컴포넌트의 함수 몸체 내부
// 비싼 연산을 하는 함수를 매번 호출하는 초기값
const expensivelyCalculatedCount = getCount()
const [ count, setCount ] = useState(expensivelyCalculatedCount)

// 비싼 연산을 하는 함수를 콜백으로 넣은 초기값
const [ count, setCount ] =  useState( () => getCount() )
  • 이렇게 함수를 넘기면 React는 함수가 반환한 값을 한 번만 계산하여 사용, 그 뒤에 일어나는 렌더링 때는 계산하지 않는다.

useState의 특성

  • setter함수 실행한다고 바로 렌더링에 반영되지 않는다.
    • 새로운 값을 받으면 실행 시 곧바로 렌더링을 다시 하는 것이 아니라, component rerendering queue에 등록해두고 React가 나중에 처리한다.
    • 새 값들을 모아서 리렌더링에 사용하는 batch 처리방식
  • function component에서 리렌더링 시 모든 함수가 재실행되지만 useState는 한 번만 실행된다.
    • setter함수는 동일성이 보장된다. 즉 첫 번째로 반환했던 setter함수의 값을 리액트가 기억하고 있으므로, 재실행하더라도 새 함수를 만들어 반환하지 않고 기존함수를 사용한다.
    • setter함수는 dependency array에 넣지 않아도 된다.
  • useState에 콜백함수를 전달하면 이 함수는 초기 렌더링에만 실행된다.
    • 콜백함수가 반환하는 값을 초기 렌더링에 사용하고 그 뒤로는 값이 무시된다.

같은 값으로 setter함수를 호출하여 갱신하는 경우, 리렌더링은 무시된다.

  • React는 이전 상태와 새 상태가 같다면 함수 컴포넌트를 실행(리렌더링)하지 않는다.
  • setter 함수에 현재 state와 같은 값을 인자로 전달하면, React는 자식을 렌더링하거나 다른 함수를 실행하는 등의 행동을 하지 않는다.
  • state의 변경 감지는 Object.is 알고리즘을 사용하며, 이 알고리즘은 === 연산보다 엄격
  • 결론: 리액트는 같은 값의 state로 setter함수를 실행하는 경우 ObjectIs 알고리즘을 사용하여 무시하는 방식으로 자체최적화

useMemo

  • useState로 비싼 연산을 한 번만 실행하기 위해 초기값으로 함수를 넣는 식으로 처리하는 방식을 배웠다.
    • 콜백으로 넘긴 그 비싼 연산 함수를 초기값 설정 뿐 아니라 특정 상황 업데이트될 때 실행시켜야 한다면?
    • 예컨대 장바구니에서 이것저것 넣을 때마다 주문서라는 component에서는 총 주문금액 등을 새로 계산하여야 한다. 이럴 때 useState보다는 useMemo로 실행하면 좋다.
  • useMemo는 Memoization 기법을 활용하며, parameter가 똑같다면 계산하지 않는다.
  • parameter 두개를 받는다
    • 함수: 비싼 연산을 실행하고 그 결과값을 반환하는 함수
    • 배열: dependency array, 이 안의 요소가 하나라도 변하면 첫 번째로 전달된 함수를 실행하여 반환한다. 이후 설명할 useEffect 등에서도 동일
function getCount(fillNumber) {
  console.log('An expensive work is been executing...')
  return Array(100000).fill(fillNumber).reduce((acc, cur) => acc + cur, 0)
}


function App() {
  return (
    <Counter />
  )
}

function Counter() {
  const [fillNumber, setFillNumber] = useState(1)
  const [text, setText] = useState('')
  const sum = getCount(fillNumber)
  const handleClick = () => {
    setFillNumber((previousNumber) => previousNumber + 1)
  }

  return (
    <section>
      <span>Sum: {sum}</span>
      <input value={text} onChange={(event) => setText(event.target.value)} />
      <button onClick={handleClick}>
        Plus FillNumber 1
      </button>
    </section>
  )
}
  • 위 코드에서, input 태그에 Change event가 발생할 경우마다 sum이라는 변수에 할당된 getCount 함수가 연산식을 실행한다.
    • 우리가 의도한 것은 fillNumber가 바뀔 때만 sum을 계산하는 것이므로, sum에 할당하는 getCountuseMemo로 넣어준다.
// Counter 함수 콤포넌트의 함수 몸체 내부
// 기존 비싼 연산을 바로 할당하는 경우
const sum = getCount(fillNumber)

// useMemo를 사용하여 비싼 연산을 필요할 때만 실행하게 하는 경우
const sum = useMemo(() => getCount(fillNumber), [ fillNumber ])
  • 비싼 연산을 하는 getCount 함수가 우리가 의도한대로 fillNumber값이 바뀔 때에만 수행된다.
    • dependency array에 넘긴 값에 변경이 있을 때 useMemo에 첫 번째로 넘긴 함수가 실행
    • dep array에 넘긴 값이 동일하다면 그 값에 대해 연산한 결과를 메모로 기억하고 있다가 해당 연산결과를 반환한다.

useMemo의 특성

  • 최적화를 위해 사용될 수 있지만 항상 최적화를 보장하지는 않는다.
    • Memoization을 한다는 것은 곧 memory에 값을 저장한다는 것이므로 불필요하게 많은 값을 메모하면 오히려 성능이 떨어진다.
    • 정말 이 값을 메모해야하는지 고민한 후 사용할 것
  • 단순한 값 자체를 메모이제이션 할 때 사용
  • useMemo로 전달된 함수는 렌더링 중에 실행된다.
    • 렌더링 후 호출하는 함수는 useEffect에서 해야 한다.

Component Memoization

  • 컴포넌트 내부에서 상태를 memo할 수 있다면 컴포넌트 자체도 memo가 가능할까? React.memo로 가능!
  • function component는 props라는 하나의 파라미터를 받아서 UI 결과를 반환하는 함수일 뿐이다.
    • 즉, 컴포넌트에 전달되는 props만 이전과 같다면 반환값도 동일하므로 memo를 통해 리렌더링을 방지, CPU연산을 아낄 수 있다.
  • import를 해준 후 React.memo(function MyComponent(props) { 함수 콤포넌트 몸체 })의 형태로 메모가 가능하다.
  • 내부적으로 useState, useReducer. useContext 등의 상태변화 훅을 가지고 있어서 렌더링을 유발하는 경우라면 컴포넌트 메모의 의미가 없어진다.
    • 위의 예제에서 input이 change event를 발생시킬 때마다 rendering 되는 경우 등
  • 기본적으로 memo함수는 함수컴포넌트가 받는 props의 기존 객체와 새 객체를 shallow compare을 통해 비교, 객체의 레퍼런스만 비교하여 업데이트 여부를 판단한다.
  • 리터럴 객체를 넘기는 경우 reference 주소가 매번 달라지므로 리렌더링이 된다. 이를 방지하거나 deep compare로 컴포넌트 렌더링을 통제하고자 한다면 memo의 두 번째 파라미터로 비교 함수를 만들어 전달하면 된다.
    • 이전 props와 현재 props를 비교하는 비교함수로 areEqual을 만들어 내용물을 비교해주는 함수를 넣어주거나 또는 Json.stringify()를 사용하여 비교하면 된다.
    • 해당 콤포넌트를 export할 때 export default memo(Counter, areEqual)로 메모함수로 감싸 export한다.
function MyComponent(props) {
  /* props를 사용한 렌더링 로직이 있는 함수콤포넌트 몸체 */
}

function areEqual(prevProps, nextProps){
  // prevProps와 nextProps 비교 로직 (예시)
  function isSameObject(obj1, obj2){
    return Object.entries(obj1).every( ([key, value] => {
      if (obj2[key] === value) return true;
    }));
  }
  return isSameObject(prevProps, nextProps);
}

export default memo(MyComponent, areEqual);

useEffect

  • Function Component의 Life cycle method를 담당하는 훅 중 하나
    • class component에서는 componentDidMount, componentDidUpdate, componentWillUnMount 등의 메서드를 사용했다.
    • 이중 componentDidMount, componentDidUpdate, componentWillUnmount의 역할을 한다.
  • 렌더링이 모두 완료된 후 호출
  • 함수와 배열을 파라미터로 받는다.
  • 리렌더링 될때마다 계속 실행되는데, 이를 방지하는 dependency array가 두 번째 파라미터로 오는 배열이다.
  • 첫번째로 넘기는 함수가 다른 함수를 반환하면, 반환된 함수는 unmount시점에 실행

mount / unmount 시점에 주로 일어나는 일들

  • mount: API 요청, web socket 연결, 3rd party lib 활용, timeout이나 interval set
  • unmount: web socket 자원 정리, lib 자원 정리, timeout이나 interval clear

useEffect의 특성

  • 렌더링 완료 이후 비동기적으로 실행된다.
    • useEffect의 콜백함수는 리액트가 전달된 콜백함수를 기억하고 있다가 실행하기 때문에 렌더링 이후 실행이 보장된다. 또한 그 다음 렌더링 이전의 실행을 보장한다.
    • 렌더링 결과가 화면에 모두 그려진 뒤 비동기적으로 실행하므로 일부 상태를 즉시 변경할 필요가 없는 경우나, 이벤트 핸들러를 관리하는 경우 사용
    • 동기적으로 실행하여 레이아웃 측정 등의 작업을 해야 하는 경우에는 useLayoutEffect를 사용
  • 매 렌더링 이후 실행되지만, dep array에 주어진 값이 변경되지 않았다면 실행하지 않는다.
    • 매번 clean up도 실행: 업데이트 된 새로운 값으로 실행되면서, 업데이트 된 props 값을 반영하여 버그를 줄인다.
  • 콜백으로 익명함수를 넘기는 이유는 클로저로 컴포넌트 내의 참조값을 제대로 업데이트 하기 위함이다.

useLayoutEffect

  • 모든 DOM 변경 이후 동기적으로 실행하며, 모든 DOM이 업데이트되고 나서 DOM을 변경하려는 경우나, 상태 업데이트로 UI가 깜박거리는 경우를 방지하기 위해 사용
  • useEffect와 함께 Function Component의 Life cycle method를 담당하는 훅 중 하나
  • useEffect는 렌더링 완료 이후 실행되는데, useEffect에 전달되는 함수에서 DOM 에 직접 접근하여 변경해야 하는 함수를 넣으면, UI가 변경되고 난 후 렌더링을 또 하면서 깜박거림이 생긴다.
    • 그러므로 LifeCycle 내에서 직접 UI를 변경해야 하는 경우에는 useEffect가 아니라 useLayoutEffect로 한다.

useCallback

  • useMemo로 함수를 메모한 것과 동일하다. 반환값으로 값을 넘기냐 함수를 넘기냐의 차이일 뿐
    • useCallback(func, dep array) == useMemo(() => func, dep array)
  • 첫 번째 인자로 memo할 함수를 넘기며, 두 번째 인자로 언제 함수가 재할당되어야하는지 결정하는 dependency array를 넘기면 dep array가 바뀔 때 메모된 함수가 콜백된다.
  • 함수가 만들어지고 할당되는 과정에서 반복되는 계산을 방지한다.

useRef

  • component의 생명주기나 rendering과 관계 없이 어떤 값을 꾸준히 유지시키거나 DOM에 접근할 때 사용
  • 초기값을 인자로 넘기면 current라는 프로퍼티 키에 초기값을 값으로 가진 ref 객체를 반환한다.
    • 초기값에는 문자열, 숫자 등 아무거나 넣을 수 있다.
    • 리렌더링이 되더라도 매번 동일한 ref 객체를 제공한다.
const refContainer = useRef(initialValue)
// refContainer = { current: initialValue }
  • useState, useCallback, useMemo 등으로 만든 값이나 함수는 리렌더링이 될 때마다 함수 컴포넌트가 재실행되면서 값을 재할당하는데, useRef는 그렇지 않기 때문에 DOM에 접근할�때 활용한다.

useRef가 DOM에서 참조를 얻어내는 방법

function TextInputWithFocusBtn() {
  const inputEl = useRef(null);
  const onBtnClick = () => {
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onBtnClick}>Focus!</button>
    </>
  );
}
  • useRef의 초기값에 null을 주고, input 태그에 ref라는 props에 ref 객체를 넣어준다.
  • 그다음 버튼클릭하면 해당 ref객체에 focus를 주는 함수를 이벤트핸들러로 걸어준다.
  • 이처럼 JSX가 실제 DOM에 반영되고 나서 input태그의 참조를 inputEl이라는 key에 넣어주면 current로 들어가게 된다.
  • React에서는 document.~으로 가져오는 건 최대한 지양, useRef를 사용할 것
  • 렌더링 후 바로 focus 주고 싶은 경우는 렌더링 이후 실행되는 useEffect를 사용하자.
useEffect( () => {
  inputEl.current.focus();
}

useRef 사용 시 주의사항

  • useRef 속의 current는 아무리 여러번 렌더링 되어도 값을 유지하며, current값을 수정한다고 해도 리렌더링이 되지 않는다.
    • React가 DOM노드에 ref를 붙이거나 뗄 때마다 어떤 동작을 수행하기 위해 리렌더링을 해야하면 useCallback으로 Callback Ref를 사용해야.
  • useCallback으로 만들어진 함수를 넣으면 React가 알아서 할당한다. useCallback에 넘겨지는 콜백함수의 첫 번째 인자는 DOM의 참조객체이다.
  • React가 렌더링이 끝나고 DOM의 ref를 current에 저장하고, useEffect를 실행한다.
  • 값을 넣는 시점에 무언가 실행시키고 싶은데 useEffect를 쓰고 싶지 않은 경우 useCallback을 쓴다고 생각하자.
function MeasureExample() {
  const [ height, setHeight ] = useState(0);
  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>
    </>
  );
}
  • 렌더링 후 h1 태그의 참조를 받아 해당 태그의 height값을 setHeight로 할당한다.
  • 만약 이것을 useRef로 만들었는데 리렌더링 이후 CSS가 적용되어 height값이 바뀌면 setHeight로 설정해둔 height값은 이전 높이의 값을 여전히 가지고 있다.

forwardRef

  • 여러군데에서 활용되고 있는 컴포넌트에서, 자식 컴포넌트에 대한 참조를 얻어야 하는 경우?
    • 자신의 DOM이 아니라, 부모 component가 자식 component의 DOM객체 참조를 얻어서 무언가 작업해야 하는 경우 등
  • 이런 경우를 위해 React는 함수 컴포넌트를 인자로 받는 forwardRef라는 함수를 제공한다.
    • 함수 컴포넌트는 참조해야하는 component를 첫 번째 인자로, ref를 두 번째 인자로 받는다.
    • 위 ref를 JSX의 ref 어트리뷰트에 전달하면서 함수 컴포넌트를 호출하면 해당하는 참조를 갖는 React 노드, 즉 함수 component가 반환된다.
const FancyButton = React.forwardRef(({ children }, ref) => (
  <button ref={ref} className="FancyButton">
    {children}
  </button>
));

const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>
  • createRef를 쓰지 않고 컴포넌트에서 다른 컴포넌트를 렌더링하는 경우가 더 많다.
    • 자식 컴포넌트에서 forwardRef로 보내면 부모 컴포넌트에서는 useRef로 받아서 사용
import { forwardRef, useRef } from 'react'

// 부모컴포넌트
function Form() {
  const inputRef = useRef(null)
  function handleFocus() {
    inputRef.current.focus()
  }
  return (
    <form>
      <MyInput ref={inputRef} />
      <button type="submit" onClick={handleFocus}>Focus!</button>
    </form>
  )
}

//자식 컴포넌트
const MyInput = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />
})

forwardRef 특성

  • forwardRef를 하면 새로운 컴포넌트가 만들어지는데, 개발자도구에 이름이 Anonymous로 뜬다.
    • 이름을 뜨게 하려면 Counter.displayName='Counter'
    • memo나 forwardRef를 하면 기본적으로 익명으로 뜨니까 displayName으로 이름 세팅해주자

다시한번 useRef에 대해 기억해야 할 것

  • 값을 전달하면 이를 초기값으로 하는, 변경가능한(mutable) ref 객체를 반환한다.
    • 매 렌더링마다 동일한 값, current라는 하나의 키를 갖는 ref 객체 제공
  • DOM 노드에 딱 한 번 실행해야 하는 경우 사용한다.
  • useRef로 current값을 바꾸더라도 렌더링하지는 않는다. 그러므로 렌더링 되기 전 값을 기억해야 하는 경우 사용.
  • useEffect는 rendering 이후 실행하므로 current에 새 값을 렌더링 이후에 설정한다.
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>;
}
  • 처음 실행할 때 count는 0으로 초기화되고, useRef가 만들어져 prevCountRef에 할당되고, 렌더링 이후 useEffect가 실행된다.
  • prevCount에는 현재 가진 값이 나타나고, 재실행되더라도 useRef는 실행되지 않으므로 count가 업데이트 되면 전에 있던 값을 물고 있다.

useImperativeHandle

  • forwardRef랑 같이 사용, 자식 component에서 특정 method를 부모component에게 넘겨야 하는 상황에서 사용
  • React에서 부모에서 자식으로 props 전달하는 게 일반적이지만, 다음과 같은 경우에 사용할 수 있다.
    • 부모 컴포넌트가 자식 컴포넌트의 참조를 전적으로 갖는 것이 부담될 때
    • 자식 컴포넌트에서 메서드를 부모 컴포넌트에게 넘겨야 할 때
  • 첫 인자는 부모로부터 전달받은 ref를 받고 두번째 인자로 함수를 전달, 그 함수는 메서드를 담은 객체를 return 한다.
    • 함수가 리턴하는 객체는 { key: value } 형태로 부모에게 알리고 싶은 동작을 custom하여 전달하며, 부모는 getValue로 이를 참조할 수 있다.
// ref와 메서드를 보내는 자식 component
const Input = React.forwardRef((props, ref) => {
  const inputRef = useRef(null)

  useImperativeHandle(ref, () => ({ 
    getValue:()=> {
     inputRef.current.focus()
    }
  }))  
 
  return (
    <input type="text" ref={inputRef} />
  )
})

// ref와 메서드를 받는 부모 component
function App() {
  const inputRef = React.useRef()
  return (
    <div className="App">
      <Input ref={inputRef} />
      <button 
        onclick={ ()=> {
          console.log(inputRef.current.getValue())
        }}>
      focus
      </button>
    </div>
  )
}

useReducer

  • 한 컴포넌트에서 불러온 애를 다른 공간에서도 써야 할 때, 전역 상태 관리 도구인 redux를 주로 사용한다.
  • useReducer와 context API를 사용하면 비슷한 효과를 낼 수 있다.
  • 예컨대 회원가입 리프레시하더라도 사용자가 입력한 값을 기억하여 UX를 높이고자 하는 상황을 생각해보자
    • local storage에 하나하나 저장해두는 방법도 있지만 구조가 잡혀있는 형태로 관리를 해야 나중에 편해진다.
    • 전역상태로 들고 있는 정보를 통해 다른 component에서도 접근하여 활용하도록 해보자.

useReducer 활용 예제

  • 정의해두고 싶은 복잡한 상태를 정의하고, 처음에는 빈값으로 채워두고 reducer이라는 함수를 작성하여 첫 번째 인자로 넘긴다.
  • 두 번째 인자로 초기 상태를, 세 번째 인자로 초기화를 비동기적으로 실행하는 함수를 넘긴다.
const initialState = {
  product : {
    name: 'Chocolate',
    price: 800,
    madeBy: 'Lotte',
    quantity: 28,
  }
}

function init(initialProduct){
  // 초기화작업
  return {
    product: initialProduct,
  }
}

function reducer(state, action){
  switch (action.type) {
    case: 'sold':
      return {
        product: {
          ... product ,
          quantity: state.product.quantity - action.payload.soldQuantity,
        }
      }
    case 'income':
      return {
        product: {
          ... product ,
          quantity: state.product.quantity + action.payload.incomeQuantity,
      }
    }
    default:
      return initialState
  }
}

function Dashboard() {
  const [ state, dispatch ] = useReducer(reducer, initialState, init)
  return (
    <>
      Product: {JSON.stringify(state.product)}
      <button onClick={() => dispatch({type: 'sold'})}>Sell a product</button>
      <button onClick={() => dispatch({type: 'income'})}>Income a product</button>
    </>
  )
}
  • 우리가 만든 함수 reducer은 state와 action 두가지를 parameter로 받는다. useReducer는 이 두가지 인자를 통해 '정의된 상태를 어떻게 할 건지' 나타내는 함수이다.
    • state는 상태, 즉 초기상태를 말한다
    • action은 하나의 객체에 불과하다. { type: '', payload: {...} } 형태
  • 장바구니에 담은 여러가지 상품을 결제하는 상황
    • action type에 sold라는 값이 전달되면 이전상태의 state.product를 spread문법으로 풀어주고 quantity에 팔린 개수만큼 뺀 값을 덮어씌운다.
    • { product: { ... state, quantity: state.product.quantity - action.payload.soldQuantity }}

Function component로 Migrate

느낀 점

  • 원리만 잘 알면 정말 편하게 쓸 수 있는 훅들일텐데.. 아직 감이 안 오지만 일단 열심히 공부하자