엘리스 SW 엔지니어 트랙 59일차
온라인 강의날
Redux
앱 전체 상태 관리하기 위해 나온 라이브러리
많은 개념들이 Flux 패턴에서 가져왔다
주로 React와 같이 사용
언제 Redux 사용할까?
- 앱 전체 상태 관리시
- 복잡한 비동기 처리시
- 앱의 상태가 복잡하고 체계적으로 관리하고 싶을 때
- 상태 관리 패턴 도입해서, 여러 개발자와 협업시
- logger, devtool 등 활용해서 상태 관리할 때
핵심 원칙 3가지
1. Single source of truth
Store는 단 하나, 모든 앱의 상태는 여기에 보관
2. Immutability
상태는 오직 읽을 수만 있다. 변경하려면 도든 상태가 변경되어야 한다
3. Pure function(side effect)
상태의 변경은 사이드 이펙트를 만들지 않아야 한다
Action
상태의 변경을 나타내는 개념
주로 객체로 type과 payload를 가지고 있다
// action
const action = {
type: 'namespace/getMyData',
payload: {
id: 123
}
}
Action Creator
Action 만들어주는 함수
사용하면 재사용성을 높일 수 있다
// action creator
const addObj = (id) => ({
type: 'namespace/getData',
payload: {
id: String(id).slice(1)
}
})
Store
앱 전체의 상태를 보관하는 곳
reducer는 새로운 상태를 만들고, Store는 그 상태를 저장
// store
const store = createStore(reducer, initialState)
Reducer
Action을 받아서 새로운 State 만든다
상태 변경 시 다른 곳에 영향을(사이드 이펙트) 안줘야 한다
(console.log, axios 등 액션 보내기전에 이미 확인하고 reducer로 보내줘야 한다)
// reducer
const reducer = (state, action) => {
switch (action.type) {
case 'namespace/getMyData':
const obj = { id: action.payload.id }
return { ...state, obj }
default:
return state
}
}
const store = createStore(reducer, initialState)
Dispatch
Action을 store(Redux)로 보내는 함수
dispatch 후에 middleware를 거쳐서 reducer에 도착한다
// dispatch
function MyApp() {
const dispatch = useDispatch()
return (
<button
onClick ={
() => dispatch(addObj(1234))
}
>Submit</button>
)
}
Selector
특정 state 조각을 store로부터 가져오는 함수
state를 raw data로 저장하고,
계산된 값을 selector로 가져오는 패턴이 유용하다
// selector
function MyApp() {
const obj = useSelector(state => state.obj)
return (
<div>
{JSON.stringify(obj)}
</div>
)
}
Redux 구조
action의 여행 to reducer
action은 middleware를 거쳐가면서 reducer에 도착한다
reducer는 새로운 state(상태)를 만든다
이 state를 전부 받거나, selector를 통해서 받는다
자유롭게 확장할 수 있다고 한다
middleware, enhancer 등 이용해서 확장한다
라이브러리
redux-thunk: Middleware, action object가 Promise 리턴하는지 쳌
Enhancer: 전체 state 중심으로 redux 동작 확장
redux-logger 같은 녀석도 있다.
redux-toolkit
redux 공식 추천 helper 라이브러리
유용한 라이브러리 포함시켜 redux 코드 쉽게 작성을 위함
기존에 만들어야 되는 많은 보일러 플레이트 제거
redux-toolkit API 예시
configureStore
createStore 함수 감싼다
named parameter로 쉽게 store 생성
reducer 객체를 받아서 reducer들을 합쳐준다(combineReducers 적용)
// configureStore
const store = configureStore({
reducer : {
posts: postsReducer,
users: usersReducer
}
})
createAction
Action creator를 만드는 함수
만들어진 action creator에 데이터 넘기면 payload 필드로 들어간다
자신이 생성한 액션의 타입 String을 리턴한다
// createAction
const addPost = createAction('post/addPost')
addPost({ title: 'post 1' })
// 위처럼 해주면 아래 처럼 만들어진다
/*
{
type: 'post/addpost',
payload: { title: 'post 1' }
}
*/
createReducer
reducer 만들어준다
builder의 addCase 메서드로 action 마다 state 변경 정의
// createReducer
const postsReducer = createReducer(initState, builder => {
builder.addCase(addPost, (state, action) => {
state.posts.push(action.payload)
})
})
createSlice
Action creator, reducer 등 하나의 객체에 모은 것
// createSlice
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
addPost(state, action) {
state.posts.push(action.payload)
}
}
})
const { addPost } = postsSlice.actions
const reducer = postsSlice.reducer
createSelector
selector 함수 만들어준다
selector 만들면 좋은 이유
=> 내부적으로 데이터 캐시, 데이터 변동 없으면 캐시된 데이터 리턴
// createSelector
const postsSelector = state => state.posts
const userSelector = state => state.user
const postsByUserIdSelector = createSelector(
postsSelector,
userSelector,
(posts, user) => posts.filter(post => post.username === user.name)
)
Redux와 React
react-redux 라이브러리 이용
react-redux API
Provider
Redux store를 React와 연결하기 위해 Provider로 감싸야된다
// Provider
const store = configureStore({ reducer: rootReducer })
function App() {
return (
<Provider store={store}>
<MyPage />
</Provider>
)
}
useDispatch
redux의 dispatch 함수 가져오기 위한 API
action creator가 만든 action을
dispatch로 redux 내부로 보낸다
// useDispatch
const addPost = createAction('addPost')
function MyPage() {
const dispatch = useDispatch()
const handleClick = () => dispatch(addPost())
return (
<button
onClick={handleClick}
>Submit</button>
)
}
useSelector
store로부터 데이터 가져오는 API
데이터 변경하면 안된다
// useSelector
function MyPage() {
const posts = useSelector(state => state.posts)
return posts.map(
post => <Post {...post} />
)
}
Redux 이용 비동기 처리
비동기를 위해서 middleware 추가해야 된다
redux-thunk를 이용하면 비동기 Action 쉽게 처리 할 수 있다
redux-thunk는 Promise 이용하는 middleware
redux-thunk 외에도 redux-sage, redux-observable 등 있다
createAsyncThunk
redux-toolkit은 thunk middleware를 기본값으로 추가한다
또 createAsyncThunk API도 제공한다
fullied, rejected, pending 3가지 상태에 대해 각각 reducer 작성한다
TypeScript 환경에서 reducer 작성 시, builder callback 사용해서 작성해야 정확하게 타이핑 가능
action type과 async 함수(payload creator)를 받는다
pending, fulfilled, rejected가 뒤에 붙어 reducer로 들어온다
예시) posts/addPost/pending
// createAsyncThunk
const addPost = createAsyncThunk('posts/addPost',
async (title) => {
const result = await PostAPI.addPost({ title })
return result.data
}
)
useEffect(() => {
dispatch(addPost("post 1"))
}, [])
thunk 함수 dispatch 하면 Promise가 리턴된다
.then() 메서드로 연속적인 비동기 처리 가능
// 연속적인 비동기
dispatch(addPost("post1"))
.then(() =>
dispatch(updatePost("post2"))
)
Promise.all 이용해서 비동기 처리 동시에 처리
주의점
thunk의 Promise가 rejected 되어도 .then()으로 들어온다
// Promise.all
Promise.all([
dispatch(addPost("post1))
dispatch(updatePost("post2))
])
.then(() => console.log("DONE))
참고 링크
redux org
'Web Dev > ELICE' 카테고리의 다른 글
61 :: json-server, RESTFul API, Development, Production (0) | 2022.01.18 |
---|---|
60 :: 테스팅, TDD, React 테스팅, jest (0) | 2022.01.15 |
58 :: React에서 Redux, 테스트 (0) | 2022.01.13 |
57 :: 상태 관리, Flux Pattern, 상태 관리 Hooks (0) | 2022.01.12 |
56 :: Redux, 상태 관리, Context API (0) | 2022.01.11 |