如何在另一个 asyncThunk 中调度 asyncThunk

how to dispatch asyncThunk inside another asyncThunk

我创建了两个异步 thunk:

export const foo = createAsyncThunk(
    'slice/foo',
    async ( ) => {
        const res = await fetch('/')
        return res.data
    }
);

export const bar = createAsyncThunk(
    'slice/bar',
    async ( ) => {
        const res = await foo()
        return res.data
    }
);

基本上我试图在 bar() 中调用 foo() 然后在 react 组件的 useEffect 中,我像这样调度 bar()

React.useEffect( async () => {
    await dispatch( bar() );
}, [bar]);

foo 正在派遣,但 bar 未派遣。

我做错了什么?

提前致谢。

编辑1。它有点像反模式,但我基本上想做的是链接 bar() 和 foo(),我不知道任何其他(正确的?)方式

编辑2。基于@Nadia Chibrikova 的评论,这也是我使用 thunkAPI 尝试过的:

    export const bar = createAsyncThunk(
        'slice/bar',
        async ( thunkApi ) => {
            thunkApi .dispatch(foo())
        }
    );

但我收到以下错误:

Cannot read property 'dispatch' of undefined

链接 Thunks

这不是一个很好的设计模式,但可以完成。您的“Edit2”很接近,但 thunkApi 是有效负载创建者的 second 参数。第一个参数是当您将动作创建者称为 bar(args) 时传递的参数。如果您不需要参数,那么您可以使用 _ 来表示该变量未被使用。

export const foo = createAsyncThunk(
  "slice/foo", 
  async () => {
    const res = await fetch("/");
    return res.json();
  }
);

export const bar = createAsyncThunk(
  "slice/bar", 
  async (_, { dispatch }) => {
    const fooAction = await dispatch(foo());
    return fooAction.payload;
  }
);

调用 dispatch(bar()) 将按以下顺序发送操作:

  1. "slice/bar/pending"
  2. "slice/foo/pending"
  3. "slice/foo/fulfilled"
  4. "slice/bar/fulfilled"

单独的操作

正如@Marcus Melodious 所推荐的,最好在组件中拥有完全独立的操作和处理调度。如果 bar 需要来自 foo 的一些数据(比如 idtoken),那么它应该将该数据作为其动作创建者的参数。

这是一个示例,我们获取 post,然后根据 post 的 post 属性 获取用户配置文件。

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

export const fetchUser = createAsyncThunk(
  "slice/fetchUser",
  async (userId) => {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    return res.json();
  }
);

您可以使用一个 useEffect 并查看调度结果

useEffect(() => {
  const execute = async () => {
    const postAction = await dispatch(fetchPost(POST_ID));
    const {userId} = postAction.payload;
    dispatch(fetchUser(userId));
  };

  execute();
}, []);

或者您可以从两个单独的 useEffect 挂钩发起两个请求。您的第二个钩子知道何时根据 useSelector.

中 Redux 状态的变化进行分派
const Test = () => {
  const dispatch = useDispatch();
  const post = useSelector((state) => state.slice.post);
  const user = useSelector((state) => state.slice.user);

  const POST_ID = 1; // just a dummy constant for testing

  console.log(post, user);

  useEffect(() => {
    const loadPost = async () => {
      dispatch(fetchPost(POST_ID));
    };
    loadPost();
  }, []); // run once on mount

  useEffect(() => {
    const loadUser = async (userId) => {
      dispatch(fetchUser(userId));
    };
    // check if post has has been set and has a userId
    if ( post && post.userId ) {
      loadUser(post.userId);
    }
  }, [post]); // run when post changes

  return <div/>;
};

这是两个钩子方法的完整代码示例:

import {
  createAsyncThunk,
  configureStore,
  createSlice
} from "@reduxjs/toolkit";
import { Provider, useDispatch, useSelector } from "react-redux";
import React, { useEffect } from "react";

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

export const fetchUser = createAsyncThunk(
  "slice/fetchUser",
  async (userId) => {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    return res.json();
  }
);

const slice = createSlice({
  name: "slice",
  initialState: {
    user: undefined,
    post: undefined
  },
  reducers: {},
  extraReducers: {
    [fetchPost.fulfilled]: (state, action) => {
      state.post = action.payload;
    },
    [fetchUser.fulfilled]: (state, action) => {
      state.user = action.payload;
    }
  }
});

const store = configureStore({ reducer: { slice: slice.reducer } });

const PostHeader = ({ postId }) => {
  const dispatch = useDispatch();
  const post = useSelector((state) => state.slice.post);
  const user = useSelector((state) => state.slice.user);

  console.log(post, user);

  useEffect(() => {
    const loadPost = async () => {
      dispatch(fetchPost(postId));
    };
    loadPost();
  }, [postId, dispatch]); // run once on mount

  useEffect(() => {
    const loadUser = async (userId) => {
      dispatch(fetchUser(userId));
    };
    // check if post has has been set and has a userId
    if (post && post.userId) {
      loadUser(post.userId);
    }
  }, [post, dispatch]); // run when post changes

  return (
    <div>
      {!!post && <h1>{post.title}</h1>}
      {!!user && <h2>By {user.name}</h2>}
    </div>
  );
};

export default function App() {
  return (
    <Provider store={store}>
      <PostHeader postId={1} />
    </Provider>
  );
}