hon9g / TIL

Note taking on issue tab
https://github.com/hon9g/TIL/issues
1 stars 1 forks source link

Reduct: Reselect #41

Open hon9g opened 2 years ago

hon9g commented 2 years ago

Deriving Data with Selectors

리덕스 문서에 목차 Deriving Data with Selectors를 보면 Selector들의 목적과 동기, 왜 memoized Selector들이 유용한지, 일반적인 Reselect 사용 패턴, React-Redux에서의 Selector 사용법을 다룹니다.

Deriving Data

Redux 어플리케이션에서 state는 최소한으로 유지하고, 언제든지 가능할 때에 추가적인 값들을 state에서 파생시키는 것을 추천합니다. 여기에는 필터링된 목록 연산과 값들을 합산하는 연산 또한 포함됩니다.

이러한 방식의 장점은 다음과 같습니다.

Calculating Derived Data with Selectors

일반적인 Redux 애플리케이션에서 데이터를 파생시키는 로직은 일반적으로 Selector라고 하는 함수로 작성됩니다.

Selector들은 다음의 로직을 캡슐화하는데에 사용됩니다.

모든 state 조회에 Selector를 사용할 필요는 없지만, 이는 표준 패턴이며 널리 사용됩니다.

Selector의 기본 컨셉

"selector function"은the Redux store state (or part of the state)를 인자로 받고, state를 기반으로 한 데이터를 반환하는 (아무) 함수를 말합니다. Selector들은 특별한 라이브러리를 통해 작성될 필요가 없습니다. 또한, 화살표 함수로 작성하든, function 키워드를 통해 작성하든 전혀 상관 없습니다.

아래의 함수들은 모두 Selector 입니다.

// Arrow function, direct lookup
const selectEntities = state => state.entities

// Function declaration, mapping over an array to derive values
function selectItemIds(state) {
  return state.items.map(item => item.id)
}

// Function declaration, encapsulating a deep lookup
function selectSomeSpecificField(state) {
  return state.some.deeply.nested.field
}

// Arrow function, deriving values from an array
const selectItemsWhoseNamesStartWith = (items, namePrefix) =>
  items.filter(item => item.name.startsWith(namePrefix))

Optimizing Selectors with Memoization

Selector 함수는 종종 상대적으로 "비싼" 계산을 수행하거나 새로운 객체 및 배열 참조들인 파생 값을 생성해야 합니다. 다음과 같은 몇 가지 이유로 애플리케이션 성능에 문제가 될 수 있습니다.

이 때문에 동일한 입력이 전달되는 경우 결과를 다시 계산하는 것을 피할 수 있는 최적화된 selector들을 작성하는 방법이 필요합니다. 이 필요가 memoization에 대한 아이디어가 나오게된 동기입니다.

Memoization은 caching의 한 형태입니다. 여기에는 함수에 대한 입력을 추적하고, 나중에 참조할 수 있도록 입력들과 결과들 저장하는 작업이 포함됩니다. 이전과 동일한 입력으로 함수를 호출하면, 함수는 실제 작업을 건너뛸 수 있으며, 해당 입력 값을 마지막으로 받았을 때 생성한 것과 동일한 결과를 반환할 수 있습니다. 이렇게 하면 입력이 변경된 경우에만 작업을 수행하고 입력이 동일한 경우 동일한 결과 참조를 일관되게 반환하여 성능을 최적화합니다.

Writing Memoized Selectors with Reselect

Reselect는 memoized selector를 작성하기 위해 Redux 생태계에서 오랫동안 사용해온 라이브러리입니다.

createSelector Overview

Reselect는 memoized selector들을 생성하기 위해 createSelector라는 함수를 제공합니다. createSelector는 하나 이상의 "input selector" 함수와 "output selector" 함수를 인자로 받고, 사용할 수 있는 새로운 selector 함수를 반환합니다.

createSelector는 input selector를 인자로 받을 때 개별 인수들로 받을 수도 있고, 또는 한 개의 배열로 받을 수도 있습니다. 모든 input selector들의 결과 값들은 output selector에 별도의 인수들로 제공됩니다.

const selectA = state => state.a
const selectB = state => state.b
const selectC = state => state.c

const selectABC = createSelector([selectA, selectB, selectC], (a, b, c) => {
  // do something with a, b, and c, and return a result
  return a + b + c
})

// Call the selector function and get a result
const abc = selectABC(state)

// could also be written as separate arguments, and works exactly the same
const selectABC2 = createSelector(selectA, selectB, selectC, (a, b, c) => {
  // do something with a, b, and c, and return a result
  return a + b + c
})

Selector를 호출하면 Reselect는 사용자가 인수로 제공한 모든 input selector들을 실행하고 반환된 값을 확인합니다. 결과 중 하나라도 === 이전과 다른 경우, input selector들의 새로운 결과를 output selector에 인수로 전달하며 재실행시킵니다. input selector들의 모든 결과값이 이전의 값과 같으면 output selector 재실행을 건너뛰고 이전의 캐시된 최종 결과만 반환합니다.

이는 input selector에서는 항상 값을 추출해야하고, output selector에서는 데이터를 transformation하는 작업을 해야만 한다는 말 입니다.

Reselect

Reselect 는 memoized "selector" function들을 생성하기 위한 라이브러리이다. 주로 리덕스와 사용되며, 공식 Redux Toolkit Package에 이미 포함되어있다.(별도 install 필요없음) 별개로 그냥 불변 JS 데이터에도 사용할 수 있다.

hon9g commented 2 years ago

Memoized Selector

state외에 로직에 필요한 추가 인자를 받는 selector 작성 케이스

  1. (기존) 매번 createSelector를 생성하고 return 함 Selector 메모이제이션 X, 컴포넌트 리렌더링 O. 매번 호출마다 새 createSelector를 재생성하여 반환하고 있습니다. useSelector는 (같은 입력값 & 같은 내용의 함수더라도) 매번 새 함수를 받고 있기 때문에 컴포넌트를 매번 리렌더링 합니다.

    const a = (arg1) => createSelector((state1) => some logic w/ arg1, state1 and return result)
  2. createSelector를 메모이제이션을 하려는 의도에 맞게 사용. Selector 메모이제이션 O. 컴포넌트 리렌더링 X. 같은 input selelctor 인자를 받는 경우 output selector 재연산없이 캐싱된 값을 사용함.

    const a = createSelector((state1) => state1.val1)
    const b = createSelector(
    a,
    (arg1) => arg1,
    (s1, a1) => some logic w/ s1, a1 and return result
    )
  3. ~Selector에서 별도의 메모이제이션을 하지 않는다.~ ~Selector 메모이제이션 X. 컴포넌트 리렌더링 X.~ ~굳이 createSelector 함수가 아닌 값을 리턴. useSelector에서는 === 비교 연산으로 리렌더를 결정하므로, primitive value를 다룰 때는 차라리 1번과 3번이 성능 차이가 거의 없거나 3번이 더 나을 수도 있을 것 같습니다. (함수를 매번 재생성하지 않기 때문에)~

    const a = (state1, arg1) => some logic w/ arg1, state1 and return result

    -> 다른 셀렉터에서 state를 가져와야 함. 현재로써는 다른 셀렉터도 특정 arg가 있어야 동작하는 식으로 작성되어 있음. -> state없이 인자만 넘기면 에러발생. selector는 state로부터 유래된 값으로부터 값을 파생시키는 역할이라서?

  4. 아예 각각 값만 가져오고 UI에서 두 로직을 조합한다.

  5. 다른 셀렉터들에서 의존적인 arg를 글로벌 state에 넣어준다. (너무 광범위한 수정 필요함 XX)

  6. 메모이제이션은 뷰에서 useMemo로 처리해준다. X

    ESLint: React Hook "useSelector" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.(react-hooks/rules-of-hooks)
hon9g commented 2 years ago

성능차이 시각화 어떻게..?

Chrome Performance tab

Another way to profile JavaScript is to use the Chrome profiler while debugging. This won't give you accurate results as the code is running in Chrome but will give you a general idea of where bottlenecks might be. Run the profiler under Chrome's Performance tab. A flame graph will appear under User Timing. To view more details in tabular format, click at the Bottom Up tab below and then select DedicatedWorker Thread at the top left menu. - React Native doc: Profiling

-> React Native Debugger에서 해봤을때, 크롬에 떠있는 디버깅 UI 에 대해 기록됨..

React Native Debugger

설치:

brew update && brew install react-native-debugger

Redux store랑 연동 해주기 위해 crateStore의 2번째 인자로 composeWithDevTools를 넘겨줘야 함. 미들웨어는 composeWithDevTools의 인자로 넘겨주면 됨.

-> Redux State 변화는 쉽게 볼 수 있게 됨.

리렌더링 되는 부분을 시각화하려면 React Dev tool도 연동해야 함.

설치:

yarn install react-devtools

버전을 맞춰서 재설치 필요한 경우

yarn global remove react-devtools & yarn global remove react-devtools-core
yarn global add react-devtools@"<4.11.0"

했는데 안되네..!

설치 후 디버거를 리로드 하면 연동 완료.