You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Redux技术问题:如何展示JSON placeholder的全部帖子

问题分析与修复方案

核心问题点

  1. getPostAll异步Thunk未正确返回数据:你的getPostAll中最后一个.then()调用了console.log,而console.log返回undefined,导致Redux无法接收到帖子列表数据。
  2. 未处理getPostAll的Redux状态更新:extraReducers中没有为getPostAll的pending/fulfilled/rejected状态编写处理逻辑,获取到的数据无法存入Redux store。
  3. 组件中错误的dispatch调用方式:在JSX渲染阶段直接调用dispatch(getPostAll())会导致重复触发请求,且没有从store中读取数据进行渲染,而是试图直接遍历dispatch的返回值(promise对象,无法forEach)。
  4. 单个帖子与列表的状态存储冲突:现有代码中所有异步操作完成后都将state.post设为[action.payload],但获取全部帖子时应该直接将数组赋值给state.post。

修复后的代码

1. 修正PostSlice.js

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"

export const getPost = createAsyncThunk("post/getPost", async ({ id }) => {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  return res.json();
})

// 修复:移除console.log的then链,直接返回解析后的JSON数据
export const getPostAll = createAsyncThunk("posts/getPosts", async () => {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const data = await res.json();
  console.log(data, typeof data, data[0].id); // 调试日志可以保留,但不要作为返回值
  return data; // 必须返回数据给Redux
})

export const deletePost = createAsyncThunk(
  "post/deletePost",
  async ({ id }) => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
      method: "DELETE",
    });
    return res.json();
  }
)

export const createPost = createAsyncThunk(
  "post/createPost",
  async ({ values }) => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-type": "application/json",
      },
      body: JSON.stringify({
        title: values.title,
        body: values.body,
      }),
    });
    return res.json();
  }
)

export const updatePost = createAsyncThunk(
  "post/updatePost",
  async ({ id, title, body }) => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
      method: "PUT",
      headers: {
        Accept: "application/json",
        "Content-type": "application/json",
      },
      body: JSON.stringify({
        title,
        body,
      }),
    });
    return res.json();
  }
)

const PostSlice = createSlice({
  name: "post",
  initialState: {
    posts: [], // 重命名为posts更语义化,区分单个帖子和列表
    loading: false,
    error: null,
    body: "",
    edit: false,
  },
  reducers: {
    setEdit: (state, action) => {
      state.edit = action.payload.edit;
      state.body = action.payload.body;
    },
  },
  extraReducers: (builder) => {
    // 使用builder API(Redux Toolkit推荐写法)
    // 处理单个帖子请求
    builder
      .addCase(getPost.pending, (state) => {
        state.loading = true;
      })
      .addCase(getPost.fulfilled, (state, action) => {
        state.loading = false;
        state.posts = [action.payload]; // 单个帖子存入数组
      })
      .addCase(getPost.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message; // 捕获错误信息
      })
      // 处理全部帖子请求
      .addCase(getPostAll.pending, (state) => {
        state.loading = true;
      })
      .addCase(getPostAll.fulfilled, (state, action) => {
        state.loading = false;
        state.posts = action.payload; // 直接赋值数组
      })
      .addCase(getPostAll.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      })
      // 处理删除帖子
      .addCase(deletePost.pending, (state) => {
        state.loading = true;
      })
      .addCase(deletePost.fulfilled, (state, action) => {
        state.loading = false;
        // 删除后过滤掉对应id的帖子
        state.posts = state.posts.filter(post => post.id !== action.meta.arg.id);
      })
      .addCase(deletePost.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      })
      // 处理创建帖子
      .addCase(createPost.pending, (state) => {
        state.loading = true;
      })
      .addCase(createPost.fulfilled, (state, action) => {
        state.loading = false;
        state.posts.unshift(action.payload); // 新增帖子放到列表开头
      })
      .addCase(createPost.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      })
      // 处理更新帖子
      .addCase(updatePost.pending, (state) => {
        state.loading = true;
      })
      .addCase(updatePost.fulfilled, (state, action) => {
        state.loading = false;
        // 找到对应id的帖子并更新
        const index = state.posts.findIndex(post => post.id === action.payload.id);
        if (index !== -1) {
          state.posts[index] = action.payload;
        }
      })
      .addCase(updatePost.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
})

export const { setEdit } = PostSlice.actions;
export default PostSlice.reducer;

2. 修正Post.js组件

import React, { useEffect } from "react"
import { useDispatch, useSelector } from "react-redux"
import { getPostAll } from "../redux/features/PostSlice"

const Posts = () => { // 补全组件函数声明
  const dispatch = useDispatch();
  // 从Redux store中获取数据
  const { posts, loading, error } = useSelector(state => state.post);

  // 组件挂载时触发获取全部帖子的请求
  useEffect(() => {
    dispatch(getPostAll());
  }, [dispatch]);

  if (loading) {
    return <div className="text-center mt-4">加载中...</div>;
  }

  if (error) {
    return <div className="text-center mt-4 text-danger">加载失败:{error}</div>;
  }

  return (
    <>
      <div className="row mt-4 d-flex align-items-center justify-content-center">
        <h2>Dashboard</h2>
      </div>
      <div className="mt-4">
        <h2 className="text-center">All Posts</h2>
        <div className="container mt-4">
          <div className="row g-4">
            {/* 遍历posts数组渲染每个帖子 */}
            {posts.map(post => (
              <div key={post.id} className="col-md-6">
                <div className="card h-100">
                  <div className="card-body">
                    <h5 className="card-title">{post.title}</h5>
                    <p className="card-text">{post.body}</p>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </>
  )
}

export default Posts

关键说明

  • 异步Thunk返回值:异步Thunk必须返回要存入store的数据,不能返回console.log这类无意义的值。
  • Redux状态更新:使用Redux Toolkit推荐的builder API处理extraReducers,确保每个异步操作的状态都被正确处理。
  • 组件数据获取:通过useEffect在组件挂载时触发数据请求,避免渲染阶段重复调用dispatch;通过useSelector从store中读取数据,再用map渲染列表(forEach不会返回JSX元素,必须用map)。
  • 状态语义化:将state.post重命名为state.posts,更清晰地表示这是帖子列表。

内容的提问来源于stack exchange,提问作者Jeevan Lenka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:05:34