React-Redux

Lucy·2023년 3월 23일
0

React

목록 보기
6/7

Redux

Action 이벤트를 사용하여 애플리케이션 상태를 관리하고 업데이트하기 위한 패턴 및 라이브러리

  • 모든 UI 프레임워크와 통합할 수 있고, React와 함께 가장 자주 사용된다.

  • 독립형 JS 라이브러리이므로 UI 프레임워크가 없어도 사용 가능

왜 사용하나 ?

전역 상태가 예측 가능한 방식으로만 업데이트될 수 있도록 상태에 대한 중앙 집중식 저장소 역할을 한다.

아래의 그림과 같이 애플리케이션은
State,
현재 상태에 대한 UI인 View,
사용자의 입력에 따라 앱에서 발생하는 이벤트와 State에서의 업데이트인 Trigger로 이루어져 있는데,
Redux는 공유된 상태가 있는 경우 이를 단방향 데이터 흐름으로 관리되게 함으로써,
상태 관리와 관련된 개념을 정의 및 분리하고 뷰와 상태 간의 독립성을 유지하는 규칙을 적용함으로써 코드에 더 많은 구조와 유지 관리성을 보장 받는다.

언제 사용할까 ?

공유 상태 관리를 처리하는데 도움이 되지만 배워야 할 개념과 작성해야 할 코드가 많다.
따라서 다음의 경우에 유용하다.

  1. 앱의 여러 위치에서 필요한 많은 양의 애플리케이션 상태가 있다.
  2. 시간 경과에 따라 앱 상태가 자주 업데이트된다.
  3. 해당 상태를 업데이트하는 논리는 복잡할 수 있다.
  4. 앱에 중간/큰 규모의 코드베이스가 있으며 많은 사람들이 작업할 수 있다.

요소

Action

type 필드가 있는 JS 객체로 애플리케이션에서 발생한 이벤트

Ex.

const addTodoAction = {
  type: 'todos/todoAdded',	// (기능)/(발생 이벤트)
  payload: 'Buy milk'	    // 추가 정보로 필수 아님	
}

Reducer

현재 state와 Action 객체를 수신하고 필요한 경우 상태를 업데이트하는 방법을 결정하고 새 상태를 반환하는 함수

(state, action) => newState

규칙

  1. state 및 action 인수를 기반으로 새 상태 값만 계산한다.

  2. 기존 state를 수정할 수 없다. (상태 불변)

    • 따라서 기존 state의 복사본을 이용해 새 값으로 복사본을 업데이트한 다음 반환하는 형태로 구현된다.
  3. 비동기 논리를 수행하거나 임의의 값을 계산하거나 다른 side effect를 일으키지 않아야 한다.

    • store를 custom하기 위해 dispatch 시, middleware를 활용하여 side effect(Ex. 콘솔에 값 로깅, 파일 저장, 비동기 타이머 설정, AJAX HTTP 요청, 함수 외부의 상태 수정, 함수에 대한 인수 변경, 난수 생성)를 발생시킬 수 있다.

Ex.

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/incremented') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}

Store

애플리케이션의 전역 상태를 보관하는 컨테이너이자 JS 객체

기능 및 특징

Redux Store 내부에 보관된 상태를 직접 수정/변경하면 안 된다.
대신, 일반 작업 객체를 만든 다음 저장소에 작업을 보내(Action) 무슨 일이 일어났는지 알려주어 상태를 업데이트할 수 있다.
그 과정은 다음과 같다.

  1. Store에 Action이 발송
  2. Store는 root Reducer 함수를 실행하고 이전 상태와 Action을 기반으로 새 상태를 계산
  3. Store는 UI가 새 데이터로 업데이트될 수 있도록 상태가 업데이트되었음을 Subscriber에게 알림

Ex.

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}

Dispatch

업데이트하고 해당 state를 호출/전달하는 Redux store의 기능

Ex.

store.dispatch({ type: 'counter/incremented' })

console.log(store.getState())
// {value: 1}

Selectors

store의 state 값에서 특정 정보를 추출하는 함수

Ex.

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

핵심 개념

  1. Single Source of Truth

    • 전역 state는 하나의 store에 객체로써 저장되어 있다.
  2. State is Read-Only

    • state를 변경하는 유일한 방법은 action을 dispatch하는 것이다.
  3. Changes are Make with Pure Reducer Functions

DataFlow

초기 설정

  1. Redux 저장소는 루트 감속기 기능을 사용하여 생성됩니다.
  2. store는 루트 리듀서를 한 번 호출하고 반환 값을 초기 state 값으로 저장합니다.
  3. UI가 처음 렌더링되면 UI 구성 요소는 Redux 스토어의 현재 상태에 액세스하고 해당 데이터를 사용하여 무엇을 렌더링할지 결정합니다. 또한 향후 매장 업데이트를 구독하여 상태가 변경되었는지 알 수 있습니다.

업데이트

  1. 사용자가 버튼을 클릭하는 것과 같이 앱에서 어떤 일이 발생합니다.
  2. 앱 코드는 다음과 같이 Redux 스토어에 작업을 전달합니다.
    dispatch({type: 'counter/incremented'})
  3. store는 이전 state와 현재 state로 감속기 기능을 다시 실행 action하고 반환 값을 새 것으로 저장합니다.
  4. store은 등록된 UI의 모든 부분에 상점이 업데이트되었음을 알립니다.
  5. 스토어의 데이터가 필요한 각 UI 구성 요소는 필요한 상태 부분이 변경되었는지 확인합니다.
  6. 변경된 데이터를 보는(dispatch) 각 구성 요소는 새 데이터로 강제로 다시 렌더링하므로 화면에 표시되는 내용을 업데이트할 수 있습니다.

React-Redux

React의 공식 Redux UI 바인딩 라이브러리

npm install react-redux

React-Redux Hook

useSelector

React 구성 요소가 Redux store에서 데이터를 읽을 수 있게 해줌 (Redux store -> React element)

  • 선택자를 인수로 가짐

  • 선택자는 전체 Redux 상태를 인수로 사용하고 상태에서 일부 값을 읽고 해당 결과를 반환하는 함수

  • 자동으로 Redux store를 구독하고 action이 생길 때마다 useSelector를 다시 실행한다.

  • useSelector에 의해 반환된 값이 마지막으로 실행되었을 때 변경된 경우, 새로운 참조 데이터(===)가 다시 렌더링되도록 한다.

    	- 배열 state인 경우 성능적인 문제가 있을 수 있다. 따라서 React.memo()를 wrapping하여 실제로 상태가 변경되었을 때만 재렌더링하거나, 배열에서 id 배열만 읽도록 하고 해당 id를 props로 전달하는 방법이 있다(* 참조).
  • 한 Component에서 여러 개의 useSelector를 호출할 수 있다.

Ex.

import React from 'react'
import { useSelector } from 'react-redux'
import TodoListItem from './TodoListItem'

const selectTodos = state => state.todos			// 선택자

const TodoList = () => {
  const todos = useSelector(selectTodos)

  // since `todos` is an array, we can loop over it
  const renderedListItems = todos.map(todo => {
    return <TodoListItem key={todo.id} todo={todo} />
  })

  return <ul className="todo-list">{renderedListItems}</ul>
}

export default TodoList

Ex * .

import React from 'react'
import { useSelector, shallowEqual } from 'react-redux'
import TodoListItem from './TodoListItem'

const selectTodoIds = state => state.todos.map(todo => todo.id)

const TodoList = () => {
  const todoIds = useSelector(selectTodoIds, shallowEqual)

  const renderedListItems = todoIds.map(todoId => {
    return <TodoListItem key={todoId} id={todoId} />
  })

  return <ul className="todo-list">{renderedListItems}</ul>
}
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'

import { availableColors, capitalize } from '../filters/colors'

const selectTodoById = (state, todoId) => {
  return state.todos.find(todo => todo.id === todoId)
}

// Destructure `props.id`, since we only need the ID value
const TodoListItem = ({ id }) => {
  // Call our `selectTodoById` with the state _and_ the ID value
  const todo = useSelector(state => selectTodoById(state, id))
  const { text, completed, color } = todo

  const dispatch = useDispatch()

  const handleCompletedChanged = () => {
    dispatch({ type: 'todos/todoToggled', payload: todo.id })
  }

  // omit other change handlers
  // omit other list item rendering logic and contents

  return (
    <li>
      <div className="view">{/* omit other rendering output */}</div>
    </li>
  )
}

export default TodoListItem

useDispatch

Component에서 Redux store로 action을 dispatch하여 그 결과를 제공함
(Redux store <- React element)

  • 실제 store.dispatch 함수를 반환한다.

Ex.

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

const Header = () => {
  const [text, setText] = useState('')
  const dispatch = useDispatch()

  const handleChange = e => setText(e.target.value)

  const handleKeyDown = e => {
    const trimmedText = e.target.value.trim()
    // If the user pressed the Enter key:
    if (e.key === 'Enter' && trimmedText) {
      // Dispatch the "todo added" action with this text
      dispatch({ type: 'todos/todoAdded', payload: trimmedText })
      // And clear out the text input
      setText('')
    }
  }

  return (
    <input
      type="text"
      placeholder="What needs to be done?"
      autoFocus={true}
      value={text}
      onChange={handleChange}
      onKeyDown={handleKeyDown}
    />
  )
}

export default Header

Store Provider

React-Redux hook가 올바른 Redux store를 찾는 위치와 방법으로,
Component에서 사용하려는 store를 React-Redux에 알려주어 React Component에서 store를 사용할 수 있도록 한다.

  • Component에 Redux store를 props로 가진 를 전달한다.

Ex.

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'

import App from './App'
import store from './store'

ReactDOM.render(
  // Render a `<Provider>` around the entire `<App>`,
  // and pass the Redux store to as a prop
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)

React-Redux Patterns

  • Global State -> Redux Store
  • 로컬한 Component State -> React Component
  • edit의 과정에서가 아니라 edit 완료 후 state를 store에 update한다.
  • useSelector를 여러 번 호출하여, store의 가능한 가장 작은 state를 return 하라.
  • 성능을 위해 ID로 목록 항목에서 데이터를 선택하라.

Ref

https://redux.js.org/tutorials/fundamentals/part-1-overview

https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#reducers

state, action, reducer https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers

store { enhancer , middleware } https://redux.js.org/tutorials/fundamentals/part-4-store

ui https://redux.js.org/tutorials/fundamentals/part-5-ui-react

profile
나아가는 OnlyOne 개발자

0개의 댓글