🔸 상태(State) : 동적으로 변하는/표현될 데이터. 이 상태를 다룰 때, Side Effect를 잘 고려해야 함.
🔸 Side Effect : 함수(또는 컴포넌트)의 입력 외에도 함수의 결과에 영향을 미치는 요인 (ex. 네트워크 요청, API 호출)
🔸 앱을 개발할 때, 이런 side effect는 불가피하게 생기기 마련이기 때문에 최대한 줄이기 위해 컴포넌트 단위로 개발하는 것이 좋음. 이러한 점을 반영하여 상태의 위치를 잘 선택해야 함.
🔸 React의 주요 개발원칙인 '컴포넌트 우선 개발 방식'에서 API 요청(ex. fetch) 없이도 컴포넌트는 잘 작동이 됨. 이렇게 어떤 데이터가 들어오는지 상관하지 않고 (fake 데이터여도) 컴포넌트가 표현되는 것을 presentation 컴포넌트라고 함.
🔸 특정 컴포넌트 안에서만 관리되는 상태.
🔸 보통 컴포넌트 내에서만 영향을 끼치며, 다른 컴포넌트와 데이터를 공유하지 않는 폼(form) 데이터는 대부분 로컬 상태임.
🔸 ex) input box, select box 등과 같이 입력값을 받는 경우
🔸 프로덕트 전체 혹은 여러 가지 컴포넌트가 동시에 관리하는 상태.
🔸 다른 컴포넌트와 상태를 공유하고 영향을 끼침.
🔸 ex) 데이터 로딩 여부(로딩 중), 다크 모드 기능, 다국어 설정 기능, 히스토리 기능, 포토샵의 Undo/Redo 등
🔸 서로 다른 컴포넌트가 사용하는 상태의 종류가 다르면, 출처(source)가 달라도 상관없음.
🔸 하지만 서로 다른 컴포넌트가 동일한 상태를 다룰 때는, 출처는 한 곳이어야 함. 만약 사본이 있을 경우, 두 데이터는 서로 동기화(sync)하는 과정이 필요. 따라서 한 곳(전역 공간)에서만 상태를 저장하고 접근해야 함.
🔸 데이터 무결성 : 데이터의 정확성을 보장하기 위해 데이터의 변경이나 수정 시 제한을 두어 안정성을 저해하는 요소를 막고 데이터 상태들을 항상 옮게 유지하는 것
🔸 동일한 데이터는 항상 같은 곳에서 데이터를 가지고 오는 것을 Single source of truth(신뢰할 수 있는 단일 출처) 원칙이라고 하며, 무결성을 위한 방법론임.
🔸 Props의 전달 횟수가 5회 이내로 많지 않다면 Props Drilling 은 큰 문제가 되지 않지만, 규모가 커지고 구조가 복잡해지면서 Props의 전달 과정이 늘어난다면 아래와 같은 문제가 발생.
🔸 상태관리 라이브러리를 사용하는 방법 : 전역으로 관리하는 저장소에서 직접 state를 꺼내쓸 수 있기 때문에 Props Drilling을 방지하기에 매우 효과적.
🔸 물론 상태 관리 툴이 없어도 충분히 규모 있는 애플리케이션을 만들 수 있음.
🔸 React Context, Redux, Mobx 등
🔸 React는 상태와 속성(props)을 이용한 컴포넌트 단위 개발 아키텍처임.
🔸 React의 데이터 흐름에 따르면 컴포넌트3, 컴포넌트6에서만 사용되는 상태는 최상위 컴포넌트에 위치시키는 것이 적절, 하지만 이러한 상태 배치는 다음과 같은 비효율적인 면이 있음.
🔸 Redux는 전역 상태를 관리할 수 있는 저장소인 Store를 제공함으로써 이러한 문제들을 해결해줌.
🔸 Redux는 다음과 같은 순서로 상태를 관리함.
상태 변경 이벤트 발생 → 변경될 상태에 대한 정보가 담긴 Action 객체 생성.
Action 객체는 Dispatch 함수의 인자로 전달.
Dispatch 함수는 Action 객체를 Reducer 함수로 전달.
Reducer 함수는 Action 객체의 값을 확인하고, 그 값에 따라 전역 상태 저장소 Store의 상태를 변경.
상태가 변경되면, React는 화면을 다시 렌더링.
🔸 Action → Dispatch → Reducer → Store 순서로 데이터가 단방향으로 흐름.
🔸 store : Redux 앱의 state가 저장되어 있는 공간. 상태가 관리되는 오직 하나뿐인 저장소 역할.
// 📍 1. Redux에서 createStore를 불러옴.
import { legacy_createStore as createStore } from 'redux'
// 📍 2. store 생성
const store = createStore(rootReducer);
🔸 전역 상태 저장소 store를 사용하기 위해서는 <App>
컴포넌트를 <Provider>
로 감싸준 후 props
로 변수 store
를 전달
<Provider store={store}>
<App />
</Provider>
🔸 reducer(State, Action)
: Dispatch에게서 전달받은 Action 객체의 type
값에 따라서 state를 변경시키는 함수.
State
: 변경될 stateAction
: action 객체🔸 외부 요인으로 인해 기대한 값이 아닌 엉뚱한 값으로 상태가 변경되는 일이 없는 Reducer는 순수함수여야 함.
const count = 1
// Reducer를 생성할 때에는 초기 상태를 인자로 요구
const counterReducer = (state = count, action) => {
// Action 객체의 type 값에 따라 분기하는 switch 조건문
switch (action.type) {
//action === 'INCREASE'일 경우
case 'INCREASE':
return state + 1
// action === 'DECREASE'일 경우
case 'DECREASE':
return state - 1
// action === 'SET_NUMBER'일 경우
case 'SET_NUMBER':
return action.payload
// 해당 되는 경우가 없을 땐 기존 상태를 그대로 리턴
default:
return state;
}
}
// Reducer가 리턴하는 값이 새로운 상태가 됨
❗Reducer함수 첫번째 인자에는 기존 state가 들어오며, default value를 설정해줘야 함. 그렇지 않을 경우 undefined가 할당되기 때문에 그로 인한 오류가 발생. 추가 설명❗
🔸 여러 개의 Reducer를 사용하는 경우, Redux의 combineReducers
메서드를 사용해서 하나의 Reducer로 합쳐줄 수 있음.
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
counterReducer,
anyReducer,
...
});
🔸 어떤 액션을 취할 것인지 정의해 놓은 객체.
🔸 Action 객체 작성시, 아래와 같은 규칙이 있음.
type
은 필수로 지정해줘야 함 (해당 Action 객체가 어떤 동작을 하는지 명시해 주는 역할){ type: 'INCREASE' }
{ type: 'SET_NUMBER', payload: 5 }
🔸 보통 Action을 직접 작성하기보다는 Action 객체를 생성하는 함수를 만들어 사용하는 경우가 많으며, 이러한 함수를 액션 생성자(Action Creator) 라고 함.
// 📍 payload가 필요 없는 경우
const increase = () => {
return {
type: 'INCREASE'
}
}
// 📍 payload가 필요한 경우
const setNumber = (num) => {
return {
type: 'SET_NUMBER',
payload: num
}
}
🔸 Reducer
로 Action
을 전달해 주는 함수
🔸 전달인자로 Action
객체가 전달됨.
// 📍 Action 객체를 직접 작성하는 경우
dispatch( { type: 'INCREASE' } );
dispatch( { type: 'SET_NUMBER', payload: 5 } );
// 📍 액션 생성자(Action Creator)를 사용하는 경우
dispatch( increase() );
dispatch( setNumber(5) );
🔸 Action
객체를 전달받은 Dispatch
함수는 Reducer
를 호출함.
🔸 Redux Hooks는 React-Redux에서 Redux를 사용할 때 활용할 수 있는 Hooks 메서드를 제공함.
🔸 useDispatch()
: Action 객체를 Reducer로 전달해 주는 Dispatch 함수를 반환하는 메서드
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
dispatch( increase() )
console.log(counter) // 2
dispatch( setNumber(5) )
console.log(counter) // 5
🔸 useSelector(callBack)
: 컴포넌트와 state를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드
callBack
함수 : Store에 저장된 모든 state
가 담김. 그대로 return을 하게 되면 Store에 저장된 모든 state
를 사용할 수 있음.import { useSelector } from 'react-redux'
const counter = useSelector(state => state)
🔸 Single source of truth : 동일한 데이터는 항상 같은 곳에서 가지고 와야 함. Redux에는 데이터를 저장하는 Store라는 단 하나뿐인 공간이 있음과 연결이 되는 원칙.
🔸 State is read-only : React에서 상태갱신함수로만 상태를 변경할 수 있었던 것처럼, Redux의 상태도 직접 변경할 수 없음을 의미. Action 객체가 있어야만 상태를 변경할 수 있음과 연결되는 원칙.
🔸 Changes are made with pure functions : 변경은 순수함수로만 가능하다는 뜻. 상태가 엉뚱한 값으로 변경되는 일이 없도록 순수함수로 작성되어야 하는 Reducer와 연결되는 원칙.
🔸 기존 애플리케이션들은 보편적으로 MVC (Model View Controller) 패턴을 사용하여 구축됨.
🔸 MVC : Model에 데이터를 저장하고 Controller를 통해 Model의 데이터를 관리(CRUD)가 이루어짐. Model의 데이터가 변경되면 View로 전달되어 사용자에게 보여지고, 사용자가 View를 통해 데이터를 입력하면 View가 Model을 업데이트할 수 있는 양방향 데이터 흐름을 가짐.
🔸 그러나 애플리케이션 규모가 커지면서 View가 다양한 상호작용을 위해 여러 개의 Model을 동시에 업데이터하고 Model 역시 여러 개의 View에 데이터를 전달하는 상황이 발생.
🔸 즉, 복잡한 데이터 흐름과 상호 의존성이 증가하게 되어, 이러한 복잡성을 관리하기 위해 단방향 데이터 흐름을 가지는 Flux 패턴이 나옴.
🔸 Flux : 사용자 입력을 기반으로 Action을 만들고, Action을 Dispatcher에 전달하여 Store(Model)의 데이터를 변경한 뒤 View에 반영하는 단방향의 흐름으로 애플리케이션을 만드는 아키텍처.
🔸 Action : 데이터를 변경하는 행위. Dispatcher에게 전달되는 객체.
🔸 Action Creator : 새로 발생한 Action의 Type과 새로운 데이터(Payload)를 묶어 Dispatcher에게 전달.
{
type: 'SET_PROFILE',
data: {
'name': 'Park',
'age': 48
}
}
🔸 Dispatcher : 모든 데이터의 흐름을 관리하는 중앙 허브. Store들이 등록해놓은 Action 타입마다 콜백 함수들이 존재.
🔸 Action을 감지하면 Store들이 각 타입에 맞는 Store의 콜백 함수를 실행.
🔸 Store를 조작하는 것은 오직 Dispatcher를 통해서만 가능.
🔸 Store들 사이에 의존성이 있는 상황에서도 순서에 맞게 콜백 함수를 순차적으로 처리할 수 있도록 관리.
🔸 Store : 상태 저장소. 상태와 상태를 변경할 수 있는 메서드를 가짐.
🔸 어떤 타입의 Action이 발생했는지에 따라 그에 맞는 데이터 변경을 수행하는 콜백 함수를 Dispatcher에 등록.
🔸 Dispatcher에서 콜백 함수를 실행하여 상태가 변경되면 View에게 데이터가 변경되었음을 알림.
🔸 View : React 컴포넌트와 비슷.
🔸 Store에서 View에게 상태가 변경되었음을 알려주면, 최상위 View(Controller View)는 Store에서 데이터를 가져와 자식 View에게 내려 보내고 새로운 데이터를 받은 View는 화면을 리렌더링함.
🔸 또한 사용자가 View에 어떠한 조작을 하면 그에 해당하는 Action을 생성.
🔸 Flux