💡 이 챕터에서 배우는 내용
createSlice로 리덕스 스토어에 리듀서 로직의 "슬라이스"를 추가하는 방법useSelector훅으로 컴포넌트의 리덕스 데이터 읽기useDispatch훅으로 컴포넌트의 액션을 디스패치 하기
소셜 미디어 피드 앱을 만들면서 리덕스 데이터의 흐름을 알아볼 것입니다.
src > features > posts > postsSlice.js 파일 생성
리듀서 함수를 만들기 위해 리덕스 툴킷의 createSlice 함수를 사용합니다.
리듀서 함수에는 일부 초기 데이터가 포함되어야 앱이 시작했을 때 리덕스 스토어가 해당 값을 불러올 수 있습니다.
createSlice를 임포트하고 초기 포스트 배열을 정의하고 해당 배열을 createSlice에 전달하고 createSlice가 생성될 수 있도록 포스트 리듀서 함수를 내보냅니다.
// features/posts/postsSlice.js
import { createSlice } from '@reduxjs/toolkit'
const initialStte = [
{ id: '1', title: 'First Post!', content: 'Hello' },
{ id: '2', title: 'Second Post', content: 'More text' }
]
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {}
})
export default postsSlice.reducer
새로운 슬라이스를 만들때마다 리덕스 스토어에 리듀서 함수를 추가해야 합니다.
app > store.js 파일을 열고 postReducer 함수를 임포트하고 postReducer가 posts라는 이름의 리듀서 필드에 전달되기 위해 configureStore에 대한 호출을 업데이트합니다.
// app/store.js
import { configureStore } from '@reduxjs/toolkit'
import postsReudcer from '../features/posts/postsSlice'
export default configureStore({
reducer: {
posts: postsReducer
}
})
posts 필드를 가지려면 최상위 레벨의 상태 객체를 원하고 액션이 디스패치될 때 state.posts의 모든 데이터는 postsReducer 함수에 의해 업데이트될 것이라는 것을 알려줍니다.
posts 폴더에 있어야 하고 PostsList.js라는 새로운 파일을 생성합니다.포스트 리스트를 렌더링할 때 어딘가에서 데이터를 가져와야 합니다.
useSelector 훅을 사용하여 리덕스 스토어에서 데이터를 읽을 수 있습니다.초기의 PostsList 컴포넌트는 리덕스 스토어의 state.post 값을 읽고 포스트 배열을 반복해서 각각을 화면에 보여줍니다.
// features/posts/PostsList.js
import React from 'react'
import { useSelector } from 'react-redux'
export const PostsList = () => {
const posts = useSelector(state => state.posts)
const renderedPosts = posts.map(post => (
<article className="post-excerpt" key={post.id}>
<h3>{post.title}</h3>
<p className="post-content">{post.content.substring(0, 100)}</p>
</article>
))
return (
<section className="posts-list">
<h2>Posts</h2>
{renderedPosts}
</section>
)
}
그 다음 App.js 에서 라우팅을 업데이트해서 PostsList 컴포넌트가 나오도록 합니다.
App.js에서 PostsList 컴포넌트를 임포트하고 <PostsList /> 컴포넌트를 추가합니다.// App.js
import React from 'react'
import {
BrowserRouter as Router,
Switch,
Rout,
Redirect
} from 'react-router-dom'
import { Navbar } from './app/Navbar'
import { PostsList } from './features/posts/PostsList'
function App() {
return (
<Router>
<Navbar />
<div className="App">
<Switch>
<Route
exact
path="/"
render={() => (
<React.Fragment>
<PostsList />
</React.Fragment>
)}
/>
<Redirect to="/" />
</Switch>
</div>
</Router>
)
}
export default App
이렇게 추가하고 나면 앱의 메인페이지는 아래처럼 보입니다.

(1) 새로운 포스트 폼 추가
posts > AddPostForm.js 파일을 생성합니다. 포스트 제목으로 지정할 텍스트 인풋창과 포스트의 바디가 될 텍스트 구역을 추가합니다.
// features/posts/AddPostForm.js
import React, { useState } from 'react'
export const AddpostForm = () => {
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
return (
<section>
<h2>Add a new Post</h2>
<form>
<label htmlFor="postTitle">Post Title:</label>
<input
type="text"
id="postTitle"
name="postTitle"
value={title}
onChange={onTitleChanged}
/>
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
value={content}
onChange={onContentChanged}
/>
<button type="button">Save Post</button>
</form>
</section>
)
}
위 컴포넌트를 App.js에 임포트하고 <PostsList /> 컴포넌트 위에 추가합니다.
// App.js
<Route
exact
path="/"
render={() => (
<React.Fragment>
<AddPostForm />
<PostsList />
</React.Fragment>
)}
/>
페이지 헤더의 바로 밑에서 해당 폼을 볼 수 있습니다.
(2) 포스트 항목 저장
포스트 슬라이스는 포스트 데이터에 대한 모든 업데이트를 처리해야 합니다. createSlice 호출 부분에 reudcers라고 불리는 객체가 있습니다. 지금은 비어 있지만 포스트 추가를 위해 리듀서 함수를 추가해야 합니다.
reudcer 안에 2개의 인자(현재 state 값과 디스패치된 action 객체)를 받는 postAdded라는 이름의 함수를 추가합니다. 포스트 슬라이스는 자신이 맡은 데이터에 대해서만 알고있기 때문에 state 인자는 전체 리덕스 상태 객체가 아니라 그 자체로 포스트 배열이 될 것입니다.action 객체는 action.payload 필드로 새 포스트 항목을 가집니다. 그리고 state 배열에 새 포스트 객체를 넣을 수 있습니다.postAdded 리듀서 함수를 적을 때 createSlice는 자동으로 같은 이름의 "액션 생성자" 함수를 생성합니다. 액션 생성자는 내보낼 수도 있고, 사용자가 "Save Post" 버튼을 누르면 액션이 디스패치될 수 있도록 UI 컴포넌트에서 사용할 수도 있습니다.
// features/posts/postsSlice.js
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
postAdded(state, action) {
state.push(action.payload)
}
}
})
export const { postAdded } = postsSlice.actions
export default postsSlice.reducer
Array.push()를 호출하거나 createSlice()안에 state.someField = someValue 같은 객체 필드를 수정하는 것이 안전합니다. 왜냐하면 이머 라이브러리를 사용해서 해당 변동사항을 내부적으로 안전한 불변하는 업데이트로 바꾸기 때문입니다.(3) "Post Added" 액션 디스패치
AddPostForm은 텍스트 입력값과 아직 아무 일도 안하는 "Save Post" 버튼을 가지고 있습니다. postAdded 액션 생성자를 디스패치하고 사용자가 쓴 제목과 내용을 포함하는 새포스트 객체를 넘겨 주기 위한 클릭 핸들러를 추가해야 합니다.id 필드가 필요합니다. 지금은 초기의 테스트 포스트 아이디로 가짜 숫자를 사용하고 있습니다. 리덕스 툴킷은 고유의 랜덤 아이디를 생성할 수 있는 nanoid 함수를 가지고 있습니다.컴포넌트로 액션을 디스패치 하기 위해 스토어의 dispach 함수에 접근해야 합니다. 리액트 리덕스로부터 useDispatch훅을 호출하여 접근할 수 있습니다. 또한 이 파일에 postAdded 액션 생성자를 임포트해야 합니다.
컴포넌트에 사용할 수 있는 dispatch 함수를 가져오면 클릭 핸들러에서 dispatch(postAdded())를 호출할 수 있습니다. 리액트 컴포넌트의 useState 훅으로 제목과 내용 값을 가져올 수 있고, 새로운 아이디를 생성할 수 있고, postAdded()에 전달한 새 포스트 객체를 같이 넣을 수 있습니다.
// features/posts/AddPostForm
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { nanoid } from '@reduxjs/toolkit'
import { postAdded } from './postsSlice'
export const AddPostForm = () => {
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const dispatch = useDispatch()
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onSavePostClicked = () => {
if (title && content) {
dispatch(
postAdded({
id: nanoid(),
title,
content
})
)
setTitle('')
setContent('')
}
}
return (
<section>
<h2>Add a new Post</h2>
<form>
{/* 폼 입력 생략 */}
<button type="button" onClick={onSavePostClicked}>
Save Post
</button>
</form>
</section>
)
}
이제 제목과 텍스트를 입력하고 "Save Post"를 클릭하면 포스트 리스트의 포스트에 새로운 아이템이 추가되는 것을 볼 수 있습니다.
위의 예제는 완전한 리덕스 데이터 흐름 사이클을 보여줍니다.
useSelector로 스토어로부터 초기 포스트 데이터를 읽고 초기 UI를 렌더링합니다.postAdded 액션을 디스패치합니다.postAdded 액션을 보고 새로운 항목으로 포스트 배열을 업데이트합니다."posts/postAdded"를 클릭하면 "Action" 탭은 아래와 같이 보입니다.
AddPostForm 컴포넌트는 사용자가 타이핑한 제목과 내용 값을 추적하기 위해 안에 리액트 useState 훅을 가지고 있다는 것에 유의하세요.AddPostForm만 입력값 필드의 최신 값에 대해 알아야 합니다. 그래서 리덕스 스토어에서 임시의 데이터를 가지고 있으려하기 보다는 리액트 컴포넌트 상태의 데이터를 가지고 있으려고 합니다. 사용자의 입력값을 기반으로 한 최종 값을 스토어에 업데이트하기 위해 사용자가 폼을 다 작성했을 때 리덕스 액션을 디스패치 합니다.createSlice 함수는 "슬라이스 리듀서"를 생성하고 안전하고 불변한 업데이트로 바꿔주는 "변하는(mutating)" 코드를 작성할 수 있도록 해줍니다.configureStore에 reducer 필드에 추가되면 리덕스 스토어 안에서 데이터 상태 필드 이름들을 정의합니다.useSelector 훅을 이용해 스토어의 데이터를 읽을 수 있습니다.state 객체를 받고 값을 반환해야 합니다.useDispatch 훅을 이용해 액션을 디스패치합니다.createSlice는 슬라이스에 추가된 리듀서들의 액션 생성자 함수를 생성합니다.dispatch(someActionCreator())을 컴포넌트 안에 호출합니다.출처
🔗 공식 문서: https://ko.redux.js.org/tutorials/essentials/part-3-data-flow
🔗 Github: https://github.com/chaevivin/Front-end_study/blob/main/Redux/Redux_Data_Flow.md
더 자세하게 정리되어 있고, 번역된 문서를 보고싶다면 Github에서 제가 작성한 문서를 확인하세요.