상권's

TIL 26 (Redux) (2021.11.01) 본문

~2022 작성 글/TIL

TIL 26 (Redux) (2021.11.01)

라마치 2021. 11. 1. 20:48

clearTimeout()

The global clearTimeout() method cancels a timeout previously established by calling setTimeout().

 

setTimeout을 통해서 일정 시간 뒤에 불러지는 함수나 코드를 그 시간이 도달하기 전에 삭제할 수 있는 기능입니다.

 

Syntax

clearTimeout(timeoutID)

Parameters

timeoutID

The identifier of the timeout you want to cancel. This ID was returned by the corresponding call to setTimeout().

 

=> 리액트 복습을 하면서 상세페이지에 갔을 때, 제고가 몇 개 이하이면 2초동안 매진 임박여부를 보여주는 기능을 구현했습니다. 근데 이 기능이 실행되기 전에 페이지를 나갈 경우, 혹시 버그가 발생할 수 있을 거 같아 clearTimeout을 통해 삭제시켜주었습니다.


-오늘의 코플릿 2021.11.01-
길이가 m, n이고 오름차순으로 정렬되어 있는 자연수 배열들을 입력받아 전체 요소 중 k번째 요소를 리턴해야 합니다.
arr1과 arr2가 오름차순으로 합쳐졌을 때,
k번째 요소를 리턴
단순히 처음부터 끝까지 찾아보는 방법(O(K)) 대신 다른 방법(O(logK))을 탐구해 보세요.
이진 탐색(binary search)을 응용하여 해결합니다.
  // 단순히 합친 다음에 정렬을 한다.
  // 그리고 k번째 요소를 찾는다. => 콘솔 상에는 문제가 없지만, 테스트 시간이 초과하낟.

  //그렇다면.. 

  // let n = arr1.length
  // let m = arr2.length
  // 이진탐색트리...

첫 수도코드 => 이렇게 문제를 풀려고 하니 두 배열 길이의 합이 일백만이 넘어가는 문제 때문에 시간 초과로 테스트가 진행이 안되었습니다. 이진 탐색을 이용해야하는데.. 해결 방법이 잘 생각이 나지 않았습니다. 다른 방법을 찾아본 후, 해당 문제를 해결해보도록 하겠습니다.

=> 2021.11.08 이진탐색트리로 효율적인 풀이 방법은 아직 정확하게 이해를 하진 못했지만, 스터디를 통해서 해당 문제를 구현해보았습니다. 추가적인 학습을 통해서 시간 복잡도도 준수할 수 있는 코드를 구현해보도록 하겠습니다.

const getItemFromTwoSortedArrays = function (arr1, arr2, k) {
  let right = 0;
  let left = 0;
  let count = 0;
  let target;

  while( k > count ) {
    if ( arr1[left] < arr2[right] ) {
      target = arr1[left]
      left++
    }
    else {
      target = arr2[right]
      right++
    }
    count++
  }
  return target;
};

프론트엔드 개발에서의 상태 관리

 

상태관리에 대해서 알아보기 전에 먼저, 상태를 구분해보도록 하겠습니다. 상태를 구분하는 데에는 절대적인 기준이나 법칙이 있는 것은 아니지만, 로컬 상태, 전역 상태로 나눠서 접근해보겠습니다.

로컬 상태특정 컴포넌트 안에서만 관리되는 상태이며,

전역 상태프로덕트 전체 혹은 여러가지 컴포넌트가 동시에

관리하는 상태를 말합니다.

 

전역 상태는 다른 컴포넌트와 상태를 공유하고 영향을 끼치는 상태입니다.

 

서로 다른 컴포넌트가 동일한 상태를 다룬다면, 이 출처는 오직 한 곳이어야 합니다. 만일 사본이 있을 경우, 두 데이터는 서로 동기화(sync)하는 과정이 필요한데, 이는 문제를 어렵게 만듭니다. 한 곳에서만 상태를 저장하고 접근하세요. 여기서 '하나의 출처'는 다른 말로 이야기하면 '전역 공간'이라고 볼 수 있습니다.

데이터 무결성을 위해, 동일한 데이터는 항상 같은 곳에서 데이터를 가지고 오도록 합시다. Single source of truth(신뢰할 수 있는 단일 출처) 원칙은 프론트엔드 뿐만 아니라 다양한 곳에서 언급되는 원칙입니다. 데이터가 존재하고, 그 데이터를 보여줘야 하는 프론트엔드에서는 철저하게 우리가 의도한대로 예외 상황없이 데이터를 잘 보여주어야 할 것입니다.

개발을 할 때, 이렇게 중요한 상태를 관리할 수 있는 각종 툴이 있습니다. 이러한 툴들은 먼저 전역 상태 저장소를 제공해줍니다. 

 

다음은, props drilling 문제를 해결합니다. 예를 들어, <A>라는 컴포넌트에 상태가 있고, <I>라는 컴포넌트가 해당 상태를 사용한다고 하면, 그 중간에 존재하는 <C>, <G> 등은 굳이 name이라는 상태가 필요하지 않음에도, 컴포넌트에 props를 만들어 자식 컴포넌트에 넘겨주어야 했습니다. 이를 props drilling(프로퍼티 내려꽂기) 문제라고 부릅니다. 전역 상태 저장소가 있고, 어디서든 해당 저장소에 접근할 수 있다면 이러한 문제는 해결될 것입니다.


Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다.
Redux는 여러분이 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와줍니다. 여기에 더해서 시간여행형 디버거와 결합된 실시간 코드 수정과 같은 훌륭한 개발자 경험을 제공합니다.
여러분은 Redux를 React나 다른 뷰 라이브러리와 함께 사용할 수 있습니다. Redux는 매우 작지만(의존 라이브러리 포함 2kB), 사용 가능한 애드온은 매우 많습니다.

React를 통해서는 상태와 속성(props)을 이용한 컴포넌트 단위 개발 아키텍처를 학습했고,

Redux를 통해서는 컴포넌트와 상태를 분리하는 패턴을 학습할 수 있습니다.

 

Redux를 사용해서 상태 변경 로직을 컴포넌트로부터 분리하면, 표현에 집중한 보다 단순한 함수 컴포넌트로 만들 수 있습니다.

 

먼저 Redux의 기본 개념: 세 가지 원칙

1. Single sources of truth => Store

2. State is read-only => Action

- Action(객체)을 통해서만 State를 변경할 수 있습니다.

3. Changes are made with pure functions => Reducer

- 변경은 순수함수로만 가능합니다.

앞 번에 Redux에 대해서 간단하게 학습하며 정리했던 부분입니다

Redux의 장점

1. 상태를 예측 가능하게 만들어 준다. =>  Reducer는 순수함수이기 때문에 다음 상태를 쉽게 예측할 수 있습니다.

2. 유지보수에 용이. => state와 props를 추적하지 않고 관리를 할 수 있다.

3. 디버깅에 유리. => action과 state log를 기록을 통해 디버깅 손쉽게 할 수 있다.(Redux dev tool)

4. 테스트를 붙이기 쉽다.


Action

Action은 말 그대로 어떤 액션을 취할 것인지 정의해 놓은 객체입니다.

{ type: ‘ADD_TO_CART’, payload: request }

보통 다음과 같은 모양으로 구성됩니다. 여기서 type은 필수로 지정을 해 주어야 하며, 그 외의 것들은 선택적으로 사용할 수 있습니다.

이렇게 모든 변화를 action을 통해 취하는 것은 우리가 만드는 앱에서 무슨 일이 일어나고 있는지 직관적으로 알기 쉽게 하는 역할을 합니다.

 

Dispatch

Dispatch는 Action을 전달하는 메소드 입니다. dispatch의 전달인자로 Action 객체가 전달됩니다. 그리고 Reducer를 호출해 state의 값을 바꾸는 역할을 합니다.

 

Store

말 그대로 state가 관리되는 오직 하나뿐인 저장소의 역할을 합니다. Redux 앱의 state가 저장되어 있는 공간이죠. 다음은 createStore 메소드를 활용해 reducer를 연결하는 방법인데요, createStore와 더불어 다른 리듀서의 조합을 인자로 넣어서 스토어를 생성할 수 있습니다. (실제 소스 코드에서는 미들웨어와 Redux devtools 지원을 위해 두번째 인자에 추가적인 내용이 들어가있습니다.)

const store = createStore(rootReducer);

 

Reducer

(previousState, action) => nextState

Reducer 는 현재의 state와 Action을 이용해서 새로운 state를 만들어 내는 pure function 입니다. 또한 보이는 코드는 쇼핑몰에서 크게 볼 수 있는 장바구니 추가 액션에 대한 코드입니다.

const itemReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TO_CART:
      return Object.assign({}, state, {
        cartItems: [...state.cartItems, action.payload]
      })
    default:
      return state;
  }
}

보통 위와 같은 모양으로 구성됩니다. 위의 예시에서는 switch문을 통해서 코드를 작성했지만 if문으로 작성해도 무방합니다.

 

useSelector()

먼저 useSelector()는 컴포넌트와 state를 연결하는 역할을 합니다. 컴포넌트에서 useSelector 메소드를 통해 store의 state에 접근할 수 있습니다.

useSelector()

const result: any = useSelector(selector: Function, equalityFn?: Function)
import { useSelector } from 'react-redux';

function Cart(props) {
  let state = useSelector((state) => state )
  console.log(state.reducer)
  
  (생략)
}

Allows you to extract data from the Redux store state, using a selector function.

selector 함수를 이용해서 Redux store state에서 데이터를 추출 할 수 있습니다.

 

Connect: Extracting Data with mapStateToProps
mapStateToProps 사용해서 데이터를 추출한다.
As the first argument passed in to connect, mapStateToProps is used for selecting the part of the data from the store that the connected component needs. It’s frequently referred to as just mapState for short.
첫 번째 인자가 연결에 전달되었을 때 mapStateTops는 연결된 구성요소가 필요로 하는 저장소의 데이터 부분을 선택하는 데 사용됩니다. 짧게는 그냥 mapState라고 부르기도 합니다.

 

The selector is approximately equivalent to the mapStateToProps argument to connect conceptually.

=> selecot는 mapStateToProps 인자가 connect되는 것과 비슷합니다.

The selector will be called with the entire Redux store state as its only argument.

=> selector는 전체 Redux 저장소 상태를 유일한 인자로 사용하여 호출됩니다.

The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector). 

=> selector는 컴포넌트가 렌더링 될 때마다 작동하게 됩니다. 직전의 컴포넌트의 렌더링 이후에도 변경된 부분이 없다면, hook에 의해서 리렌더링 없이 cached result가 리턴될 수 있습니다.

useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched.

=> useSelector()는 리덕스 스토어를 참조하며, action이 dispatch 될 때면 앱의 selector를 동작시킵니다.

 

However, there are some differences between the selectors passed to useSelector() and a mapState function:

  • The selector may return any value as a result, not just an object. The return value of the selector will be used as the return value of the useSelector() hook. => selector의 반환값은 object 뿐만 아니라 다른 값들도 결과로 리턴될 수 있습니다. selector의 리턴값은 useSelector() 훅의 반환값으로 사용됩니다.
  • When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render. => action이 dispatch 되면, useSelector는 직전의 selector와 현재의 selector를 비교합니다. 만약 이 둘이 다르다면 리렌더링을 하고, 같다면 리렌더링을 하지 않습니다.
  • The selector function does not receive an ownProps argument. However, props can be used through closure or by using a curried selector. 
  • Extra care must be taken when using memoizing selectors
  • useSelector() uses strict === reference equality checks by default, not shallow equality (see the following section for more details).
Basic usage:

import React from 'react'
import { useSelector } from 'react-redux'

export const CounterComponent = () => {
  const counter = useSelector((state) => state.counter)
  return <div>{counter}</div>
}

Using props via closure to determine what to extract:

import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = (props) => {
  const todo = useSelector((state) => state.todos[props.id])
  return <div>{todo.text}</div>
}

useDispatch()

useDispatch()는 Action 객체를 Reducer로 전달해주는 메소드입니다. Action 이 일어날만한 곳은 클릭 등의 이벤트가 일어나는 컴포넌트입니다.

import React from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()

  return (
    <div>
      <span>{value}</span>
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}

This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.

이 Hook은 Redux 저장소에서 디스패치 함수에 대한 참조를 반환합니다. 필요에 따라 action을 dispatch하는 데에 사용할 수 있습니다.

 

Redux  다운로드

npm install redux react-redux

Redux 설정

index.js 에서 Redux를 사용할 <App />를 <Provider> 로 감싸줍니다. 

=> 이를 통해서 <App /> 내부에서 Store에다가 state를 보관하고 관리할 수 있습니다.

 

import { Provider } from 'react-redux'


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

 

createStore(reducer, [preloadedState], [enhancer])

앱의 전체 state가 담길 Redux store를 만들어 줍니다.

 

인자 reducer, [state], [enhancer] 출처

=> [enhancer](function): middleware,  time travel, persistence 등과 같은 third-party 기능으로 스토어를 개선하도록 지정할 수 있습니다. Redux와 함께 제공되는 유일한 store enhancer 는 applyMiddleware()입니다.

 

 

Returns => 모든 state가 담긴 객체를 리턴합니다. 이 store 내부의 state는 dispatch actiond을 통해서만 변경할 수 있습니다.

​Action 객체는 Dispatch에게 전달되고, Dispatch는 reducer를 호출해서 새로운 state를 생성합니다.

import { createStore } from 'redux'

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text])
    default:
      return state
  }
}

const store = createStore(todos, ['Use Redux'])

store.dispatch({
  type: 'ADD_TODO',
  text: 'Read the docs'
})

console.log(store.getState())

적용

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

createStore를 저장한 변수를 props 쓰는 거처럼 넣어주게 되면 <Provide> 안 쪽의 컴포넌트에서 이용할 수 있습니다. store만 관리하는 다른 페이지를 만들었다면,  index.js 에서 import 해서 입력해주면 됩니다. => 이를 통해서 기능 별로 정리를 깔끔하게 할 수 있습니다. 

 

connect()

The connect() function connects a React component to a Redux store.

connect 함수는 리액트 컴포넌트와 리덕스 store을 연결시켜줍니다.

It provides its connected component with the pieces of the data it needs from the store, and the functions it can use to dispatch actions to the store.

이를 통해서 store에서 필요한 정보를 해당 컴포넌트에 제공합니다. 그리고 store에 action을 dispatch할 때 사용할 수 있는 함수도 제공합니다.

// TodoList.js

function mapStateToProps(state) { // state를 props화 해서 state에서 값을 불러올 수 있습니다
  const { todos } = state
  return { todoList: todos.allIds }
}

export default connect(mapStateToProps)(TodoList)
function mapStateToProps(state) {
  return {
    a: 42,
    todos: state.todos,
    filter: state.visibilityFilter,
  }
}

// component will receive: props.a, props.todos, and props.filter

 

combineReducers(reducers)

 

As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state.

앱이 복잡해지면, reducing 함수를 state의 각각 독립적인 부분을 관리하는 함수로 나눌 수 있습니다.

The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.

combineReducers helper 함수는 다른 reducing 함수를 가지고 있는 객체를 createStore에 전달할 수 있는 단일 reducing 함수로 만들어줍니다.

The resulting reducer calls every child reducer, and gathers their results into a single state object. 

단일로 만들어진 reducer는 모든 자식 reducer를 호출하고, 자식들의 result를 하나의 state 객체로 만들어줍니다.

The state produced by combineReducers() namespaces the states of each reducer under their keys as passed to combineReducers()

combineReducers()에 의해서 만들어진 state는 combineReducers()에 전달된 키 값의 벨류로 저장됩니다.

 

Example:

rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// This would produce the following state object
{
  potato: {
    // ... potatoes, and other state managed by the potatoReducer ...
  },
  tomato: {
    // ... tomatoes, and other state managed by the tomatoReducer, maybe some nice sauce? ...
  }
}

 

미들웨어 thunk

 

리덕스에서 비동기 작업을 처리할 때는 redux-thunk라는 미들웨어를 많이 사용합니다.

비동기 액션 생산자는, 상태에 따라 동기 액션 생산자를 호출해줍니다. 여기서 말하는 상태는 비동기 요청 시작/완료/실패 등을 포함할 수 있습니다. 비동기 액션 생산자는 리듀서로 연결되지 않고, 직접 dispatcher를 통해 스토어로 새로운 상태를 보내줍니다. 함수를 dispatch 할 때에는, 해당 함수에서 dispatch 와 getState 를 파라미터로 받아와주어야 합니다.

export const notify = (message, dismissTime = 5000) => dispatch => {
  const uuid = Math.random()
  dispatch(enqueueNotification(message, dismissTime, uuid))
  setTimeout(() => {
    dispatch(dequeueNotification())
  }, dismissTime)
}

export const enqueueNotification = (message, dismissTime, uuid) => {
  return {
    type: ENQUEUE_NOTIFICATION,
    payload: {
      message,
      dismissTime,
      uuid
    }
  }
}

export const dequeueNotification = () => {
  return {
    type: DEQUEUE_NOTIFICATION
  }
}

비동기 함수에 따른 액션, 즉 상태 변화를 분리해서 보아야 한다는 점이 기존 동기 액션과는 다릅니다.

notify라는 비동기 액션을 통해 구분되는 두 개의 상태 변화를 살펴봅시다. 먼저 notify가 왜 비동기 액션일까요? notify는, 메시지를 띄운 후 몇 초 후에 자동으로 사라져야 합니다. 즉, setTimeout을 이용한 비동기 함수입니다. 이 비동기 함수의 과정 중 일어나는 상태 변화는 다음과 같습니다.

  • enqueueNotification 메시지가 queue에 쌓임
  • dequeueNotification 메시지가 queue에서 사라짐

위의 enqueue 및 dequeue 과정은 명백한 동기 액션입니다. 중요한 것은 비동기 액션은 상태 변경이 일어나는 동기 액션으로 구분지어 생각해야 한다는 점입니다.

대신에 비동기 액션인 notify 액션의 구현은 액션 생산자(Action Creator)를 이용한 기존의 방식과는 조금 다릅니다. 비동기 액션은, 비동기 상황을 구현한 후, dispatch를 통해 액션을 호출합니다. 

  동기 액션 비동기 액션
구현 방법 액션 생산자를 만들어, 액션 객체를 리턴합니다. 액션을 구현한 후, dispatch를 이용해 다른 동기 액션을 호출합니다.

Object.assign() 출처

Object.assign() 메서드는 출처 객체들의 모든 열거 가능한 자체 속성을 복사해 대상 객체에 붙여넣습니다. 그 후 대상 객체를 반환합니다.

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

구문

Object.assign(target, ...sources) Copy to Clipboard

매개변수

target

목표 객체. 출처 객체의 속성을 복사해 반영한 후 반환할 객체입니다.

sources

출처 객체. 목표 객체에 반영하고자 하는 속성들을 갖고 있는 객체들입니다.

반환 값

목표 객체.

 

설명

목표 객체의 속성 중 출처 객체와 동일한 키를 갖는 속성의 경우, 그 속성 값은 출처 객체의 속성 값으로 덮어씁니다. 출처 객체들의 속성 중에서도 키가 겹칠 경우 뒤쪽 객체의 속성 값이 앞쪽 객체의 속성 값보다 우선합니다.


1, 2, 3으로만 이루어진 수열 바코드를 만들어야 합니다. 무조건 1, 2, 3만 붙여서 바코드를 만들었다면 쉬웠겠지만, 아쉽게도 바코드를 만드는 데에 조건이 걸려 있습니다. 바코드에서 인접한 두 개의 부분 수열이 동일하다면 제작할 수 없다고 할 때, 주어진 길이 len의 바코드 중 가장 작은 수를 반환하는 함수를 작성하세요.

만들 수 없는 바코드 :112, 1231312, 232312
만들 수 있는 바코드 : 1312, 3, 231213

부분수열? 주어진 수열에서 연속된 모든 구간을 말합니다.
수열 123의 부분수열은 1, 2, 3, 12, 23, 123 입니다.

인접한 두 부분수열? 첫번째 부분수열과 두번째 부분수열이 연속된 경우를 말합니다.

수열 1234에서 인접한 부분수열 (우리는 두 부분수열이 같은 지 관심이 있으므로 길이가 서로 다른 경우는 무시한다)
1과 2, 2와 3, 3과 4, 12와 34입니다.

만들 수 없는 바코드를 보자면,
'11'2
12'3131'2
'2323'12
인접한 두 개의 부분 수열이 동일하기 때문에 만들 수 없습니다. 고로, '12131213'과 같이 네 자리씩 중복되어도 만들 수 없습니다. 자릿수와 상관없이, 인접한(붙어있는) 부분수열이 같다면 바코드를 만들 수 없습니다.
function barcode(len) {
    function isValid(str) {
        let half = Math.floor(str.length / 2) // '1213' half = 2

        for ( let i = 1; i <= half; i++ ) {
            if(str.slice(-i) === str.slice(2 * -i, -i)) return false;
        } // str.slice(-1) => 3 === str.slice(-2, -1) => 1 뒤에서 2번째에서 뒤에서 1번째까지 출력
        // str.slice(-2) => 13 === str.slice(-4, -2) => 12 뒤에서 2번째부터 끝까지 값, 뒤에서 4번째에서 뒤에서 2번째까지
        return true;
    }

    function makeBarcode(str) {
        if(str.length === len) return str;

        for(let n = 1; n <= 3; n++) {
            if(isValid(str + n)) {
                let minBarcode = makeBarcode(str + n);
                if(minBarcode) {
                    return minBarcode
                }
            }
        }
    }
    return makeBarcode('1')
}

 

'~2022 작성 글 > TIL' 카테고리의 다른 글

TIL 28 (정적, 동적 웹사이트, useContext)(2021.11.03)  (0) 2021.11.03
TIL 27 (2021.11.02)  (0) 2021.11.02
TIL 25 (2021.10.31)  (0) 2021.10.31
TIL 24 (클로저 함수)(2021.10.30)  (0) 2021.10.30
TIL 23 (2021.10.29)  (0) 2021.10.29
Comments