React 심화2
01. Redux 소개
Redux 소개
- 앱 전체 상태를 '쉽게' 관리하기 위한 라이브러리
- Redux의 많은 개념들이 Flux pattern에서 차용됨.
- 주로 React 앱과 같이 사용
- redux.js.org에서 수많은 문서를 참고할 수 있고 웹상에 Redux를 활용한 앱 구축 사례가 많음.
언제 Redux를 써야 하는가
- 앱 전체의 상태 관리가 필요할 때.
- 복잡한 비동기 처리가 있는 상태관리가 필요할 때
- 앱의 상태가 복잡하고 이를 체계적으로 관리하고 싶을 때
- 상태 관리 패턴을 도입하여 여러 개발자와 협업하고 싶을 때
- logger, devtool 등을 활용하여 상태를 관리할 필요가 있을 때
핵심 원칙
Single source of truth
: Store는 단 하나이며, 모든 앱의 상태는 이곳에 보관됨.
Immutability
: 상태는 오로지 읽을 수만 있다. 변경하려면 모든 상태가 변경되어야 함.
Pure function
: 상태의 변경은 어떠한 사이드 이펙트도 만들지 않아야 함.
Action
- Action은 상태의 변경을 나타내는 개념.
- 최소한의 의미를 가지는 atomic
- 어떤 형태든지 상관없으나, 주로 type, payload를 포함하는 JS 객체
const action1 = {
type: 'namespace/getMydata',
payload: {
id: 123
}
}
Action Creator
- Action을 생성하는 함수
- 직접 Action을 생성하는 것보다 Action Creator를 사용하면 재사용성이 좋고 하나의 레이어를 추가할 수 있음.
const addObj = (id) => ({
type: 'namespace/getMyData',
payload: {
id: String(id).slice(1)
}
})
Store
- 앱 전체의 상태를 보관하는 곳
- Action에 따라 reducer에서는 새로운 상태를 만들어내며, Store는 그 상태를 저장
- Store의 상태는 불변하며, 매 액션이 발생할 때마다 새로운 객체가 만들어짐.
const store = createStore(reducer, initialState)
Reducer
- Action을 받아 새로운 State를 만듦.
- (state, action) => state의 인터페이스를 따름.
- 상태 변경 시 사이드 이펙트가 없어야 함
const reducer = (state, action) => {
switch(action.type){
case 'namespace/getMyData':
const obj = {id : action.payload.id}
return {...state, obj}
default :
return state
}
}
const store = createSotre(reducer, initialState)
Dispatch
- Action을 redux로 보내는 함수
- dispatch 후에 action은 middleware를 거쳐 reducer에 도달
function MyApp(){
const dispatch = useDispatch()
return (
<button
onclick ={
() => dispatch(
addObj(1234)
)}
>Submit</button>
)
}
Selector
- 특정 state조각을 store로부터 가져오는 함수
- store의 state는 raw data를 저장하고, 계산된 값 등을 selector로 가져오는 등의 패턴을 구사할 때 유용.
function MyApp(){
const obj = useSelector(state => state.obj)
return (
<div>
{JSON.stringify(obj)}
</div>
)
}
02. Redux의 구조
Redux의 구조
- 자유롭게 확장 가능
- 내부적으로 action과 데이터가 어떻게 흐르는지 이해하고 middleware, enhancer 등을 이용하여 redux를 확장
middleware
- action은 dispatch 이후 모든 middleware를 먼저 통과한 후에 reducer에 도달.
- redux-thunk, redux-logger 등의 라이브러리를 적용
enhancer
- action은 dispatch 이후 모든 middleware를 먼저 통과한 후에 reducer에 도달
- redux devtools 등의 라이브러리를 활용
- enhancer(state)
- redux에서 공식적으로 추천하는 helper라이브러리
- 기존에 만들어야하는 수많은 보일러플레이트를 제거하고 유용한 라이브러리를 포함하여 redux 코드를 쉽게 작성하게 함
- redux-devtools, immerjs, redux-thtnk, reselect 등의 라이브러리가 미리 포함됨.
- redux의 createStore 함수를 래핑
- named parameter(이름이 있는 객체)로 쉽게 store를 생성
- reducer : 객체를 받아, combineReducer를 적용함.
const store = configureStore({
reducer : {
posts: postsReducer,
users: usersReducer
}
})
- Action creator를 만드는 함수
- 만들어진 action creator에 데이터를 넘기면, payload 필드로 들어감
- 생성된 action creator는 toString() 메서드를 오버라이드해 자신이 생성하는 액션의 타입 String을 리턴
const addPost = createAction('post/addPost')
addPost({title: 'post1'})
- reducer를 만듦
- builder의 addCase 메서드를 이용하여 action 마다 state의 변경을 정의
- immerjs를 내부적으로 사용하므로 mutable code를 이용해 간편하게 변경 코드를 작성
const postsReducer =
createReducer(initState, builder => {
builder.addCase(addPost, (state, action) =>
{state.posts.push(action.payload)
})
})
- Slice는 Action creator, reducer 등 별도로 만들어야하는 여러 Redux 구현체를 하나의 객체로 모은 것.
- createSlice 함수를 이용하며 많은 보일러 플레이트를 없애고 쉽게 action creator, reducer를 만듦.
const postsSlice = createSlice({
name : 'posts',
initialState,
reducer : {
addPost(stste, action) {
state.posts
.push(action.payload)
}
}
})
const { addPost } = postsSlice.actions
const reducer = postSlice.reducer
- createSelector 함수를 이용해 state를 이용한 특정 데이터를 리턴하도록 함.
- 내부적으로 데이터를 캐시하며 데이터가 변동이 없다면 캐시된 데이터를 리턴함.
const postsSelector = state => state.posts
const userSelector = state => state.user
const postsByUserIdSelector = createSelctor(
postsSelector,
userSelector,
(posts,user) =>
posts.filter(post =>
post.username === user.username)
)
)
04. Redux를 React에 연결하기
react - redux
- redux를 react 앱에 연결하게 하는 라이브러리
- redux에서 관리하는 상태, dispatch 함수 등을 가져올 수 있음.
- 클래스 컴포넌트, 함수형 컴포넌트 모두에 연결 할 수 있음.
react - redux API- Provider
- Redux store를 React와 연결하기 위해서는 반드시 Provider로 컴포넌트를 감싸야만 함.
- Provider 안에서 렌더린된 컴포넌트들은 state에 접근할 수 있음.
const store = configureStore({
reducer : rootReducer
})
function APP(){
return(
<Provider store = {store}>
<MyPage />
</Provider>
)
}
react - redux API - useDispatch
- redux의 dispatch 함수를 가져오기 위한 API
- dispatch로 action creator가 생성한 action을 보내면 redux 내부로 보내지게 됨.
const addPost = createAction('addPost')
function MyPage(){
const dispatch = useDispatch()
const handleClick = () => dispatch(addPost())
return (
<button onClick = {handleClick}> Submit </button>
)
}
react - redux API - useSelector
- Redux store로부터 데이터를 얻기 위한 API
- selector function을 인자로 넘김
- selector function은 데이터에 어떤 변경을 가하면 안됨
- 데이터를 특정 형태로 계산하여 읽을 수 있음.
function Mypage(){
const posts = useSelector (state => state.posts)
return posts.map(
post => <Post {... post}/>)
)
}
05. Redux를 이용한 비동기처리
Redux를 이용한 비동기처리
- redux 비동기 처리를 위해서는 비동기를 위한 middleware(redux-saga, redux-observable..)를 추가하여야 함
- redux-thunk는 Promise를 이용한 비동기 Action을 쉽게 처리하도록 하는 middleware
createAsyncThunk
- redux-toolkit 에서는 thunk middleware를 디폴트로 추가
- redux-toolkit은 createAsyncThunk API를 제공함. fulfilled, rejected, pending 3가지 상태에 대해 각각 reducer를 작성
- TypeScript 환경에서 reducer 작성 시,builder callback을 사용하여 작성해야 정확한 타이핑 가능.
- createAsyncThunk는 두 인자 action type, async callback(payload creator)를 받음.
- action type을 주어지면, pending, fulfilled, rejected가 각각 postfix로 붙어 reducer로 들어옴.
e.g. posts/addPost/pending
const addPost = createAsyncThumk ('posts/addPost', async(title) => {
const result = await PostAPI.addPost({ title })
return result.data
})
useEffect(() => {
dispatch(addPost("post 1"))
}, [])
- createSlice의 extraReducers 함수를 이용해, builder에 각 상황에 대한 리듀서를 추가
- 공식적으로 builder pattern을 추천하는데, 타입스크립트에서 타이핑을 용이하기 하게 때문임
const postSlice = cerateSlice({
extraReducers: builder => {
builder
.addCase(addPost.pending, state => ... )
.addCase(addPost.fulfilled, state => ...)
.addCase(addPost.rejected, state => ...)
}
})