redux에서 createAsyncThunk는 무엇입니까?

먼저 부작용이란 API에서 데이터를 가져오는 것과 같이 기존 클라이언트 애플리케이션 외부의 모든 외부 상호 작용을 말합니다.

Redux에서 미들웨어는 항상 비동기 작업을 수행하는 데 사용되었습니다. 비동기 작업은 API에서 데이터를 가져오는 것과 같이 기다려야 하는 작업을 의미합니다. 미들웨어는 개발자가 부작용이 있는 논리를 작성할 수 있도록 설계되었습니다. 예를 들어 redux-thunk 라는 패키지가 있습니다.

Redux-thunk는 비동기 논리(작업)에 사용됩니다.

Redux 툴킷은 기본적으로 redux-thunk를 포함하므로 redux-thunk와 같은 기본 제공 종속성이 제공되므로 createAsyncThunk를 사용하여 비동기 요청을 할 수 있습니다.

createAsyncThunk

CreateAsyncThunk는 슬라이스에서 비동기 작업을 수행하는 곳입니다. 두 개의 매개변수를 받습니다.
  • 액션 이름, 표준 규칙은 "[슬라이스 이름]/[액션 이름]"입니다.
    "posts/fetchPosts"
  • API 호출을 수행하고 완료되면 결과를 반환하는 콜백 함수입니다. API 호출은 약속(비동기 작업의 상태를 나타내는 개체, 이 경우 API 호출)을 반환합니다.
  • createAsyncThunk 를 사용하여 생성된 각 작업에 대해 반환된 약속에 대해 세 가지 가능한 상태가 있습니다. pending , fulfilled , rejected .

    Redux가 API 호출의 세(3) 단계에서 수행해야 할 작업을 결정합니다. 슬라이스 내부에 extraReducers , pendingfulfilled API 반환을 처리하는 몇 가지 함수를 포함하는 rejected 라는 속성을 추가합니다.

    extraReducers
    extraReducers를 사용하여 createAsyncThunk에 의해 생성된 작업을 처리합니다. 약속의 상태에 따라 상태를 업데이트합니다.

    나는 당신이 redux 툴킷에 대해 조금 알고 있다고 가정하고 설정을 통해 빠르게 실행할 것입니다.

    주목

    단일 기능에 대한 모든 파일은 동일한 폴더에 있어야 합니다. 즉, 게시물과 관련된 모든 항목은 posts라는 폴더에 있어야 합니다.

    가게를 차리다



    // src/app/store.js
    
    import { configureStore } from '@reduxjs/toolkit'
    import postsReducer from '../features/posts/postsSlice'
    
    
    export const store = configureStore({
      reducer: {
       // reducer for slice goes here
      },
    })
    
    export default store
    

    스토어를 앱에 제공



    enitre 앱을 스토어로 래핑합니다.

    // index.js
    import App from './App';
    import { store } from './app/store'
    import { Provider } from 'react-redux'
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </React.StrictMode>
    );
    


    슬라이스 만들기




    // src/features/posts/postsSlice
    
    import { createSlice } from '@reduxjs/toolkit'
    import { createAsyncThunk } from '@reduxjs/toolkit'
    import axios from "axios"
    
    const BASE_URL = "https://jsonplaceholder.typicode.com/posts"
    
    const initialState = {
        posts: [],
        status: "idle",
        error: ""
    }
    
    export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
        const response = await axios.get(BASE_URL)
        console.log(response.data)
        return response?.data
    })
    
    export const deletePost = createAsyncThunk("post/deletePost", async (initialPost) => {
        const {id} = initialPost
        try {
            const response = await axios.delete(`${BASE_URL}/${id}`);
            if (response?.status === 200) return initialPost;
            return `${response.status} : ${response.statusText}`;
        } catch (error) {
            return error.message
        }
    })
    
    const postsSlice = createSlice({
      name: 'posts',
      initialState,
      reducers: {
        // ==> normal reducer functions go here
    },
      extraReducers(builder) {
            builder
                .addCase(fetchPosts.pending, (state, action) => {
                    state.status = "loading"
                })
                .addCase(fetchPosts.fulfilled, (state, action) => {
                    state.status = "succeeded"
                    state.posts = state.posts.concat(action.payload);
                })
                .addCase(fetchPosts.rejected, (state, action) => {
                    state.status = "failed"
                    state.error = action.error.message
                })
                .addCase(deletePost.fulfilled, (state, action) => {
                    if (!action?.payload.id) {
                        console.log("could not delete");
                        console.log(action.payload)
                        return 
                    }
    
                    const { id } = action.payload;
                    const OldPosts = state.posts.filter(post => 
                    post.id !== id)
                    state.posts = OldPosts
                })
        }
    })
    
    export default postsSlice.reducer;
    
    


    상태에 액세스하기 위해 많은 선택자를 만듭니다.



    선택기를 사용하면 상태의 특성이 변경되는 경우 모든 항목을 한 곳에서 업데이트할 수 있습니다.

    주목
    이 작업은 여전히 ​​게시물 슬라이스 내부에서 수행됩니다.

    // src/posts/postsSlice
    
    export const selectAllPosts = (state) => state.posts.posts
    export const getPostsError = (state) => state.posts.error
    export const getPostsStatus = (state) => state.posts.status
    


    스토어에 슬라이스 리듀서 추가




    // src/app/store.js
    import { configureStore } from '@reduxjs/toolkit'
    import postsReducer from '../features/posts/postsSlice'
    
    export const store = configureStore({
      reducer: {
        posts: postsReducer
      },
    })
    
    export default store
    


    앱이 로드되는 즉시 이 게시물 배열을 가져옵니다.




    // index.js
    import { fetchPosts } from './features/posts/postsSlice';
    
    store.dispatch(fetchPosts());
    


    게시물 구성 요소




    // src/features/posts/Posts.jsx
    
    import React from 'react'
    import { useSelector } from 'react-redux/es/hooks/useSelector'
    import { selectAllPosts, getPostsError, getPostsStatus } from './postsSlice'
    import TableData from './TableData'
    
    const Posts = () => {
    
        // selectors to access state
        const posts = useSelector(selectAllPosts);
        const status = useSelector(getPostsStatus);
        const error = useSelector(getPostsError);
    
        let content;
    
    
    
        if (status === "loading") {
            content = <div className="text-center my-5">Loading...</div>
        } else if (status === "succeeded") {
            // change the order of the posts
            const orderedPosts = posts.slice().sort((a, b) => a - b)
    
            content = orderedPosts.map((post, i) => (
                <TableData key={i} post={post} />
            ))
        } else if (status === "failed") {
            content = (
                <>
                    <h1>Posts not found</h1>
                    <p className='text-center text-danger'>{error}</p>
                </>
            )
        }
    
    
    
      return (
        <section className="section">
            <div className="container">
                <div className="row">
                    <div className="col-12 text-center">
                        <h3>Here are all the posts</h3>
                    </div>
                </div>
                <div className="row">
                    <div className="col-12">
                        {content}                            
                    </div>
                </div>
            </div>
        </section>
      )
    }
    
    export default Posts
    


    TableData 구성 요소



    재사용 가능한 구성 요소를 만들기 위해 관심사 분리를 사용했습니다.

    // src/features/posts/TableData.jsx
    
    import React from 'react'
    import { deletePost } from './postsSlice'
    import { useDispatch } from 'react-redux'
    import { useNavigate } from "react-router-dom";
    
    const TableData = ({ post }) => {
    
        const navigate = useNavigate();
    
        const { id } = post;
    
        const dispatch = useDispatch();
    
        const handleDelete = () => {
            try {
                // dispatch action to store
                dispatch(deletePost({ id })).unwrap();
                navigate("/")
            } catch (error) {
                console.log(`Failed to delete the post ${error}`)
            }
        }
    
        return (
            <div className="item">
                <div>
                    <h3>{post.title}</h3>
                    <p className="postCredit">
                        {post.body}
                    </p>
                </div>
                <div>
                    <button className="btn btn-danger" onClick={handleDelete}>
                        delete
                    </button>
                </div>
            </div>
        ) 
    }
    
    export default TableData
    


    앱 구성 요소




    
    import './App.css';
    import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
    import Posts from './features/posts/Posts';
    
    function App() {
      return (
        <Router>
          <Routes>
             <Route path="/" element={<Posts />} />
          </Routes>
        </Router>
      );
    }
    
    export default App;
    


    CSS

    여기 내 CSS가 있습니다. App.css 또는 index.css에 넣을 수 있습니다.

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    html {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
      font-size: 1rem;
    }
    
    body{
      min-height: 100vh;
    }
    
    .App{
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
      min-height: 100vh; 
    }
    
    .section{
      background-color: whitesmoke;
      border: 1px solid blue;
      flex: 1;
    }
    
    /* custom styling */
    
    .item {
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap;
      padding: 18px;
      background-color: aqua;
      border: 2px solid dodgerblue;
      margin: 10px 0;
    }
    
    .item-text{
      font-size: 1.2rem;
      margin: 0;
      padding: 0;
    }
    

    package.json에 다음 패키지를 추가하고 npm install를 실행합니다.

    "overrides": {
        "autoprefixer": "10.4.5"
      },
    "@reduxjs/toolkit": "^1.8.3",
    "bootstrap": "^5.1.3",
    "react-router-dom": "^6.3.0",
    "react-redux": "^8.0.2",
    


    감사합니다

    github

    좋은 웹페이지 즐겨찾기