반응형

React Redux Toolkit Posts로 연습하기

 

./api/interface.ts

export interface PostsListProps {
    posts: [
        {
            id: string,
            title: string,
            content: string
        }
    ]
}

 

./app/store.ts

counter내용은 이전에 만든 내용입니다.

// 툴킷에서 구성 저장소를 가져옵니다.
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/count/counterSlice";
import postsReducer from "../features/posts/postsSlice";

export const store = configureStore({
    reducer: {
        counter: counterReducer,
        posts: postsReducer
    }
});

 

./features/posts/postsSlice.ts

import { createSlice } from "@reduxjs/toolkit";
import { PostsListProps } from "../../api/interface";

const initialState = [
    {
        id: "1",
        title: "제목입니다.",
        content: "Hello World 내용입니다~",
    },
    {
        id: "2",
        title: "Slice TEST !",
        content: "Slice TEST ing~~",
    },
];

const postsSlice = createSlice({
    name: "posts",
    initialState,
    reducers: {
        postAdded(state, action) {
            state.push(action.payload);
        }
    }
});

export const seletAllPosts = (state: PostsListProps) => state.posts;

export const { postAdded } = postsSlice.actions;

export default postsSlice.reducer;

 

./features/posts/PostsList.tsx

import styles from "./PostsList.module.scss";

import { useSelector } from "react-redux";
// import { PostsListProps } from "../../api/interface";
import { seletAllPosts } from "./postsSlice";
import { AddPostForm } from "./AddPostForm";

export const PostsList = () => {

    // 아래처럼 사용도 가능합니다.
    // const posts = useSelector((state: PostsListProps) => state.posts);
    // postsSlice.ts에서 내용을 처리해준 방법입니다.
    const posts = useSelector(seletAllPosts);

    const renderedPosts = posts.map(post => (
        <section key={post.id} className={styles.posts_wrap}>
            <h3>{post.title}</h3>
            <p>{post.content.substring(0, 100)}</p>
        </section>
    ))
    return (
        <article className={styles.posts_list_wrap}>
            <h2>Posts</h2>
            <AddPostForm />
            <div className={styles.posts_list}>
                {renderedPosts}
            </div>
        </article>
    );
};

 

./features/posts/AddPostForm.tsx

import { ChangeEvent, useState } from "react";
import { useDispatch } from "react-redux";
// nanoid = 임의의 ID를 생성합니다. uuid같은 느낌입니다.
import { nanoid } from "@reduxjs/toolkit";

import { postAdded } from "./postsSlice";

export const AddPostForm = () => {

    const dispatch = useDispatch();

    const [title, setTitle] = useState<string>("");
    const [content, setContent] = useState<string>("");

    const onTitleChanged = (e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value);
    const onContentChanged = (e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value);

    const onSavePostClicked = () => {
        if (title.length > 1 && content.length > 5) {
            dispatch(postAdded({
                id: nanoid(),
                title,
                content
            }))

            setTitle("");
            setContent("");
        }
    }

    return (
        <section>
            <h2>새로운 포스트 생성하기</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" onClick={onSavePostClicked}>Save Post</button>
            </form>
        </section>
    );
};

 

./App.tsx

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Counter } from './features/count/Counter';
import { PostsList } from './features/posts/PostsList';
import { Main } from './pages/Main';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Main />} />
        <Route path="/count" element={<Counter />} />
        <Route path="/posts-list" element={<PostsList />} />
      </Routes>
    </Router>
  );
}

export default App;

 

./index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

// redux
import { store } from './app/store';
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

 

 


 

PostsList.tsx에서 해야 하는 일들을 postsSlice.ts로 옮긴 버전입니다.

 

./features/posts/postsSlice.ts

import { createSlice, nanoid, PayloadAction } from "@reduxjs/toolkit";
import { PostsListProps, PostsProps } from "../../api/interface";

const initialState = [
    {
        id: "1",
        title: "제목입니다.",
        content: "Hello World 내용입니다~",
    },
    {
        id: "2",
        title: "Slice TEST !",
        content: "Slice TEST ing~~",
    },
];

const postsSlice = createSlice({
    name: "posts",
    initialState,
    reducers: {
        postAdded: {
            reducer(state, action: PayloadAction<PostsProps>) {
                state.push(action.payload);
            },
            prepare(title: string, content: string) {
                return {
                    payload: {
                        id: nanoid(),
                        title,
                        content
                    }
                }
            }
        }
    }
});

export const seletAllPosts = (state: PostsListProps) => state.posts;

export const { postAdded } = postsSlice.actions;

export default postsSlice.reducer;

reducer(state, action: PayloadAction<PostsProps>), prepare(title: string, content: string) 이 두 부분에서 조금 막혔었습니다.

prepare의 반환 값과 action의은 동일한 유형을 가져야 하는데, reducer에 대한 입력 값은 추론할 수 없기 때문에 수동으로 지정해야 했었는데 자꾸 이상한 방향으로 갔던 것 같습니다..ㅠ

 

./features/posts/AddPostForm.tsx

import { ChangeEvent, useState } from "react";
import { useDispatch } from "react-redux";
// nanoid = 임의의 ID를 생성합니다. uuid같은 느낌입니다.
// import { nanoid } from "@reduxjs/toolkit";

import { postAdded } from "./postsSlice";

export const AddPostForm = () => {

    const dispatch = useDispatch();

    const [title, setTitle] = useState<string>("");
    const [content, setContent] = useState<string>("");

    const onTitleChanged = (e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value);
    const onContentChanged = (e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value);

    const onSavePostClicked = () => {
        if (title.length > 1 && content.length > 5) {
            dispatch(postAdded(title, content))

            setTitle("");
            setContent("");
        }
    }

    return (
        <section>
            <h2>새로운 포스트 생성하기</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" onClick={onSavePostClicked}>Save Post</button>
            </form>
        </section>
    );
};

dispatch() 부분이 깔끔하게 변한것을 확인할 수 있습니다.

반응형

+ Recent posts