📌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 |