💡 이 챕터에서 배우는 내용
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에서 제가 작성한 문서를 확인하세요.