首页 > 其他分享 >Redxu(RTK) 基础 异步逻辑与数据请求 第5.2.1节 加载帖子第二部分 使用 createAsyncThunk 请求数据( thunk 和extraReducers)

Redxu(RTK) 基础 异步逻辑与数据请求 第5.2.1节 加载帖子第二部分 使用 createAsyncThunk 请求数据( thunk 和extraReducers)

时间:2023-03-08 16:33:06浏览次数:38  
标签:5.2 const 请求 fetchPosts posts thunk state action createAsyncThunk

本篇学习如何正确编写和使用thunk,并且学习通过获取thunk状态在页面上显示不同内容(比如提示正在加载、加载失败、或者是显示加载成功后的数据)的范式。

createAsyncThunk请求数据 (动手编写一个thunk,并为它设计异步拉取数据的功能)

我们已经讲明白thunk,也给posts加入了一个新的状态值,下一步可以着手发送HTTP请求来获取后端数据并更新到redux中了。

Redux Toolkit 的 createAsyncThunk API 生成 thunk,为你自动 dispatch 那些 "start/success/failure" action。
让我们从添加一个 thunk 开始,该 thunk 将进行 AJAX 调用以检索帖子列表。我们将从 src/api 文件夹中引入 client 工具库,并使用它向 '/fakeApi/posts' 发出请求。

注意上面讲到的 "start/success/failure" action ,之前我们在编写reducer的时候,rtk自动生成了actionCreator,
thunk类似,当使用createAsyncThunk函数中,第一个参数就是action的前缀名字,而后缀名字,则就是"pending/fulfilled/rejected"(这对应Promise的三个状态),
createAsyncThunk自动添加的"pending/fulfilled/rejected"后缀自动拼接出action,这样的后缀信息方便我们知晓thunk异步操作具体处于哪个状态,方便供程序使用(比如,正在请求就是pending,页面显示个转圈圈的提示组件)。
另外,我们坚持使用createAsyncThunk这个RTK提供的工具函数,而不是自己手动编写thunk,相对来说,前者效率高,而且不易出错。
createAsyncThunk 接收 2 个参数:一个前面说过了,第二个是一个函数,在这个函数内部编写异步逻辑,获取异步操作的数据并返回这个数据,注意返回的数据形式会是 Promise,或者一个被拒绝的带有错误的 Promise,所以官网称这个函数为一个 “payload creator” 回调函数
这个返回的数据会被包装成action的payload。
在这个payload creator中,文档建议使用try/catch形式配合async/await来编写异步逻辑。
注意下面的代码片段中的fetchPost,他就是一个使用了createAsyncThunk编写的thunk函数

export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
  const response = await client.get('/fakeApi/posts')
  return response.data
})

当dispatch(fecthPost)的时候,fetchPost这个thunk会先dispatch 一个 类型为"posts/fetchPosts/pending",
类似的,如果thunk内部的promise成功返回一个数据,一个 类型为"posts/fetchPosts/fulfilled"的action就会被dispatch,注意,这个action内部肯定还有promise返回的数据作为payload

在组件中 dispatch thunk(如何实际使用thunk)

我们已经编写好一个thunk了,下面找一个实际的组件去使用它,这里要配合useDispatch钩子和useEffect钩子,注意下面示例内容。

features/posts/PostsList.js
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
// omit other imports
import { selectAllPosts, fetchPosts } from './postsSlice'

export const PostsList = () => {
  const dispatch = useDispatch()
  const posts = useSelector(selectAllPosts)

  const postStatus = useSelector(state => state.posts.status)

  useEffect(() => {
    if (postStatus === 'idle') {
      dispatch(fetchPosts())
    }
  }, [postStatus, dispatch])

只要页面能正确显示post,就说明thunk是能正常使用了!
注意我们一定要判断posts.status,而不是每次渲染页面都要拉取数据。

Reducer 与 Loading Action

前面我们已经介绍过thunk可能会触发三种状态的action,
我们编写reducer的时候,reducer的名字加上slice的name就是该reducer要处理的action的"type",
而对thunk,他对应的action 的"type"是thunk的第一个参数+三种状态,这些action对应的reducer要通过extraReducers 编写,extraReducers编写范式十分明确,我把模板实例复制在下面:

 // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state: any) => {
        state.status = "loading";
      })
      .addCase(incrementAsync.fulfilled, (state: any, action: any) => {
        state.status = "idle";
        state.value += action.payload;
      })
      .addCase(incrementAsync.rejected, (state: any) => {
        state.status = "failed";
      });
  },

注意,extraReducers还有其他写法,推荐上面的,下面我再贴上文档写法:

但是,有时 slice 的 reducer 需要响应 没有 定义到该 slice 的 reducers 字段中的 action。这个时候就需要使用 slice 中的 extraReducers 字段。
extraReducers 选项是一个接收名为 builder 的参数的函数。builder 对象提供了一些方法,让我们可以定义额外的 case reducer,这些 reducer 将响应在 slice 之外定义的 action。我们将使用 builder.addCase(actionCreator, reducer) 来处理异步 thunk dispatch 的每个 action。

注意下方的addCase,既可以接受action type ,也可以接受action creator。(还可以接受createAsyncThunk 生成的thunk.xxx)

import { increment } from '../features/counter/counterSlice'

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // slice-specific reducers here
  },
  extraReducers: builder => {
    builder
      .addCase('counter/decrement', (state, action) => {})
      .addCase(increment, (state, action) => {})
  }
})

下面是文档上关于fetchPost这个thunk对应的 extraReducers的编写方法。

export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
  const response = await client.get('/fakeApi/posts')
  return response.data
})

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // omit existing reducers here
  },
  extraReducers(builder) {
    builder
      .addCase(fetchPosts.pending, (state, action) => {
        state.status = 'loading'
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.status = 'succeeded'
        // Add any fetched posts to the array
        state.posts = state.posts.concat(action.payload)
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  }
})

根据promise的不同结果,分别对应标记status和更新state的posts字段或者是error字段。

显示加载状态 (在thunk请求的不同阶段渲染不同内容)

在异步请求的loading阶段,我们想要在页面上显示一些提示性信息,比如“正在加载数据”,请看下面代码:

features/posts/PostsList.js
import { Spinner } from '../../components/Spinner'
import { PostAuthor } from './PostAuthor'
import { TimeAgo } from './TimeAgo'
import { ReactionButtons } from './ReactionButtons'
import { selectAllPosts, fetchPosts } from './postsSlice'

const PostExcerpt = ({ post }) => {
  return (
    <article className="post-excerpt" key={post.id}>
      <h3>{post.title}</h3>
      <div>
        <PostAuthor userId={post.user} />
        <TimeAgo timestamp={post.date} />
      </div>
      <p className="post-content">{post.content.substring(0, 100)}</p>

      <ReactionButtons post={post} />
      <Link to={`/posts/${post.id}`} className="button muted-button">
        View Post
      </Link>
    </article>
  )
}

export const PostsList = () => {
  const dispatch = useDispatch()
  const posts = useSelector(selectAllPosts)

  const postStatus = useSelector(state => state.posts.status)
  const error = useSelector(state => state.posts.error)

  useEffect(() => {
    if (postStatus === 'idle') {
      dispatch(fetchPosts())
    }
  }, [postStatus, dispatch])

  let content

  if (postStatus === 'loading') {
    content = <Spinner text="Loading..." />
  } else if (postStatus === 'succeeded') {
    // Sort posts in reverse chronological order by datetime string
    const orderedPosts = posts
      .slice()
      .sort((a, b) => b.date.localeCompare(a.date))

    content = orderedPosts.map(post => (
      <PostExcerpt key={post.id} post={post} />
    ))
  } else if (postStatus === 'failed') {
    content = <div>{error}</div>
  }

  return (
    <section className="posts-list">
      <h2>Posts</h2>
      {content}
    </section>
  )
}

上面已经封装了一个新的Excerpt组件,他是在redux中已经正确拉取到post数据的时候把post的内容渲染到页面上的逻辑,把这段逻辑抽离到Excerpt中,意图让代码逻辑更清晰,而在另外一边,如果redux还没有顺利拉取到post数据,我们转而在页面上渲染Spinner组件(是文档提供的一个东东,顾名思义,可以一直转圈圈)。

到现在为止,我们可以正确编写和使用thunk,并且知道通过获取thunk状态在页面上显示不同内容(比如提示正在加载、加载失败、或者是显示加载成功后的数据)的范式。

标签:5.2,const,请求,fetchPosts,posts,thunk,state,action,createAsyncThunk
From: https://www.cnblogs.com/nulixuexipython/p/17188173.html

相关文章