React.js

21.04.28 useSelector, useEffect, useDispatch

슈팅스타제제 2021. 4. 30. 04:27

📌useSelector() : 리덕스 스토어의 데이터를 추출

React-Redux Hook, 참조하는 상태와 일치 여부를 판단하고 상태를 가져온다. 

react-redux 공식 소스코드는 다음과 같다.

>>

더보기
import { useReducer, useRef, useMemo, useContext, useDebugValue } from 'react'
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
import Subscription from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
import { ReactReduxContext } from '../components/Context'

const refEquality = (a, b) => a === b

function useSelectorWithStoreAndSubscription(
  selector,
  equalityFn,
  store,
  contextSub
) {
  const [, forceRender] = useReducer((s) => s + 1, 0)

  const subscription = useMemo(() => new Subscription(store, contextSub), [
    store,
    contextSub,
  ])

  const latestSubscriptionCallbackError = useRef()
  const latestSelector = useRef()
  const latestStoreState = useRef()
  const latestSelectedState = useRef()

  const storeState = store.getState()
  let selectedState

  try {
    if (
      selector !== latestSelector.current ||
      storeState !== latestStoreState.current ||
      latestSubscriptionCallbackError.current
    ) {
      const newSelectedState = selector(storeState)
      // ensure latest selected state is reused so that a custom equality function can result in identical references
      if (
        latestSelectedState.current === undefined ||
        !equalityFn(newSelectedState, latestSelectedState.current)
      ) {
        selectedState = newSelectedState
      } else {
        selectedState = latestSelectedState.current
      }
    } else {
      selectedState = latestSelectedState.current
    }
  } catch (err) {
    if (latestSubscriptionCallbackError.current) {
      err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
    }

    throw err
  }

  useIsomorphicLayoutEffect(() => {
    latestSelector.current = selector
    latestStoreState.current = storeState
    latestSelectedState.current = selectedState
    latestSubscriptionCallbackError.current = undefined
  })

  useIsomorphicLayoutEffect(() => {
    function checkForUpdates() {
      try {
        const newStoreState = store.getState()
        const newSelectedState = latestSelector.current(newStoreState)

        if (equalityFn(newSelectedState, latestSelectedState.current)) {
          return
        }

        latestSelectedState.current = newSelectedState
        latestStoreState.current = newStoreState
      } catch (err) {
        // we ignore all errors here, since when the component
        // is re-rendered, the selectors are called again, and
        // will throw again, if neither props nor store state
        // changed
        latestSubscriptionCallbackError.current = err
      }

      forceRender()
    }

    subscription.onStateChange = checkForUpdates
    subscription.trySubscribe()

    checkForUpdates()

    return () => subscription.tryUnsubscribe()
  }, [store, subscription])

  return selectedState
}

/**
 * Hook factory, which creates a `useSelector` hook bound to a given context.
 *
 * @param {React.Context} [context=ReactReduxContext] Context passed to your `<Provider>`.
 * @returns {Function} A `useSelector` hook bound to the specified context.
 */
export function createSelectorHook(context = ReactReduxContext) {
  const useReduxContext =
    context === ReactReduxContext
      ? useDefaultReduxContext
      : () => useContext(context)
  return function useSelector(selector, equalityFn = refEquality) {
    if (process.env.NODE_ENV !== 'production') {
      if (!selector) {
        throw new Error(`You must pass a selector to useSelector`)
      }
      if (typeof selector !== 'function') {
        throw new Error(`You must pass a function as a selector to useSelector`)
      }
      if (typeof equalityFn !== 'function') {
        throw new Error(
          `You must pass a function as an equality function to useSelector`
        )
      }
    }
    const { store, subscription: contextSub } = useReduxContext()

    const selectedState = useSelectorWithStoreAndSubscription(
      selector,
      equalityFn,
      store,
      contextSub
    )

    useDebugValue(selectedState)

    return selectedState
  }
}

/**
 * A hook to access the redux store's state. This hook takes a selector function
 * as an argument. The selector is called with the store state.
 *
 * This hook takes an optional equality comparison function as the second parameter
 * that allows you to customize the way the selected state is compared to determine
 * whether the component needs to be re-rendered.
 *
 * @param {Function} selector the selector function
 * @param {Function=} equalityFn the function that will be used to determine equality
 *
 * @returns {any} the selected state
 *
 * @example
 *
 * import React from 'react'
 * import { useSelector } from 'react-redux'
 *
 * export const CounterComponent = () => {
 *   const counter = useSelector(state => state.counter)
 *   return <div>{counter}</div>
 * }
 */
export const useSelector = /*#__PURE__*/ createSelectorHook()

>>

흐름은 다음과 같다. 

1. selector를 통해 필요한 상태를 store에서 가져온다.

2. 이때, store데이터 저장소에서 subscription찜뽕해놓고 있던 데이터 상태가 변경되면

  2-1. checkForUpdates 호출하여 현재 상태와 이전 상태 비교

  2-2. 새로운 데이터 상태를 다시 찜뽕

3. 상태 변경 X 이라면 리렌더링하지 않고

4. 상태가 변경됐다면 새로운 상태를 정의하여 값을 담고

5. useReducer를 통해 새로운 상태를 component에 전달한다.

const result = useSelector(selector, equalityFn);
const 변수명(나타낼 데이터) = useSelector(필요한 상태를 가져오는 함수, 일치 여부를 판단하는 함수[참조 객체]);
const value = useSelector(state => state.value);

 

selector와 equalityFn 을 통해 store의 데이터에서 필요한 상태를 고르고 참조 객체와 같은지 비교하여 그 상태를 가져오는 함수이다. useSelector를 통해 상태의 값을 가져와서 value 에 정의한다. 

 

📌useDispatch() : 액션 발생 함수

React-Redux Hook, context에 포함된 dispatch를 가져오고 이 dispatch를 이용해 action을 발생시킨다. 

자식 컴포넌트의 불필요한 렌더링을 방지하기 위해 useCallback()을 사용하는 것이 낫다.

✔액션 타입으로 실행할 경우 : dispatch({type : ACTION_TYPES})

액션 크리에이터로 실행할 경우 : dispatch(actionCreater(arguments))

 

react-redux 공식 소스코드는 다음과 같다.

>>

더보기
import { ReactReduxContext } from '../components/Context'
import { useStore as useDefaultStore, createStoreHook } from './useStore'

/**
 * Hook factory, which creates a `useDispatch` hook bound to a given context.
 *
 * @param {React.Context} [context=ReactReduxContext] Context passed to your `<Provider>`.
 * @returns {Function} A `useDispatch` hook bound to the specified context.
 */
export function createDispatchHook(context = ReactReduxContext) {
  const useStore =
    context === ReactReduxContext ? useDefaultStore : createStoreHook(context)

  return function useDispatch() {
    const store = useStore()
    return store.dispatch
  }
}

/**
 * A hook to access the redux `dispatch` function.
 *
 * @returns {any|function} redux store's `dispatch` function
 *
 * @example
 *
 * import React, { useCallback } from 'react'
 * import { useDispatch } from 'react-redux'
 *
 * export const CounterComponent = ({ value }) => {
 *   const dispatch = useDispatch()
 *   const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), [])
 *   return (
 *     <div>
 *       <span>{value}</span>
 *       <button onClick={increaseCounter}>Increase counter</button>
 *     </div>
 *   )
 * }
 */
export const useDispatch = /*#__PURE__*/ createDispatchHook()

>>

흐름은 다음과 같다. 

1. React.useContext로 <Provider store={store}/>처럼 Provider에서 정의한 contextValue를 가져와서

2. store.dispatch를 반환한다. 

 

📌useEffect()

Effect Hook, 컴포넌트 첫 렌더링과 이후 모든 업데이트에서 개발자가 지정한 함수 즉, effect를 수행한다. 

 

📌Basic Example

위 Hooks를 모두 이용한 기본 틀은 다음과 같다!

function Component(){
	const dispatch = useDispatch();
    useEffect(()=>{
    	dispatch({type : Action});
    }, [dispatch]);
	const value = useSelector(state=>state.value);
    return <div>{value}</div>
}

 

✔dispatch 함수를 사용하겠다.

✔useEffect 함수로 액션 타입이 Action일 때 정의된 dispatch 함수를 가져와라.

단, dispatch 가 바뀔 때만 useEffect 실행!

👉만약 dispatch 가 리렌더링 시에 변경되지 않는다면 useEffect를 건너뛰게 하여 최적화한다. 

마운트와 마운트 해제를 딱 한 번씩만 실행하고 싶다면 빈 배열[ ]을 두번째 인자로 넘긴다. 

 

 

 

'React.js' 카테고리의 다른 글

21.09.04 컴포넌트 생성 단축키  (0) 2021.09.04
21.06.05 Virtual DOM  (0) 2021.06.06
21.04.27 redux, axios 로그인 상태 관리 프로세스  (0) 2021.04.27
21.04.10 redux 개념  (0) 2021.04.14
21.04.02 useReducer  (0) 2021.04.10