Reducers vs ExtraReducers作为处理thunks的一种方式

huangapple go评论78阅读模式
英文:

Reducers vs ExtraReducers as a way to handle thunks

问题

I'm currently converting an old redux-codebase to use Redux-Toolkit (RTK), and I can't seem to understand the difference between these approaches. Consider this slice:

interface MyState {
  someItems: string[];
}

const myInitialState: MyState = {
  someItems: []
};

const mySlice = createSlice({
  name: 'mySlice',
  initialState: myInitialState,
  reducers: {
    setItems(state, action: PayloadAction<string[]>) {
      state.someItems = action.payload;
    }
  },
  extraReducers: builder => {
    builder.addCase(fetchItems.fulfilled, (state, action) => {
      state.someItems = action.payload;
    });
  }
});

My traditional way to use redux has been to call the reducer directly from a Thunk, but I see that the way defined in extraReducers is also possible.

const fetchItems = createAppAsyncThunk(
  'mySlice/fetchItems',
  async (_, thunkAPI) => {
    try {
      //Get data from backend
      const response = await getItems();

      //Option 1
      thunkAPI.dispatch(mySlice.actions.setItems(response));
      //Option 2
      return response;
    } catch (e) {
      return thunkAPI.rejectWithValue('failed');
    }
  }
);

What is the difference between these approaches? Are any of them preferred over the other?
Lastly when doing changes I often have to reload the data for latest updates. This time I believe I have to use dispatch inside the Thunk. Or are there alternatives?

const editItem = createAppAsyncThunk<void, string>(
  'mySlice/fetchItems',
  async (changedValue, thunkAPI) => {
    try {
      //Send data to backend
      await sendSomethingToBackend(changedValue);

      thunkAPI.dispatch(fetchItems());
    } catch (e) {
      return thunkAPI.rejectWithValue('failed');
    }
  }
);
英文:

I'm currently converting an old redux-codebase to use Redux-Toolkit (RTK), and I can't seem to understand the difference between these approaches. Consider this slice:

interface MyState {
  someItems: string[];
}

const myInitialState: MyState = {
  someItems: []
};

const mySlice = createSlice({
  name: &#39;mySlice&#39;,
  initialState: myInitialState,
  reducers: {
    setItems(state, action: PayloadAction&lt;string[]&gt;) {
      state.someItems = action.payload;
    }
  },
  extraReducers: builder =&gt; {
    builder.addCase(fetchItems.fulfilled, (state, action) =&gt; {
      state.someItems = action.payload;
    });
  }
});

My traditional way to use redux has been to call the reducer directly from a Thunk, but I see that the way defined in extraReducers is also possible.

const fetchItems = createAppAsyncThunk(
  &#39;mySlice/fetchItems&#39;,
  async (_, thunkAPI) =&gt; {
    try {
      //Get data from backend
      const response = await getItems();

      //Option 1
      thunkAPI.dispatch(mySlice.actions.setItems(response));
      //Option 2
      return response;
    } catch (e) {
      return thunkAPI.rejectWithValue(&#39;failed&#39;);
    }
  }
);

What is the difference between these approaches? Are any of them preferred over the other?
Lastly when doing changes I often have to reload the data for latest updates. This time I believe I have to use dispatch inside the Thunk. Or are there alternatives?

const editItem = createAppAsyncThunk&lt;void, string&gt;(
  &#39;mySlice/fetchItems&#39;,
  async (changedValue, thunkAPI) =&gt; {
    try {
      //Send data to backend
      await sendSomethingToBackend(changedValue);

      thunkAPI.dispatch(fetchItems());
    } catch (e) {
      return thunkAPI.rejectWithValue(&#39;failed&#39;);
    }
  }
);

答案1

得分: 1

以下是代码部分的翻译:

给定:

const fetchItems = createAppAsyncThunk(
  'mySlice/fetchItems',
  async (_, thunkAPI) => {
    try {
      // 从后端获取数据
      const response = await getItems();
    
      // 选项 1
      thunkAPI.dispatch(mySlice.actions.setItems(response));
      // 选项 2
      return response;
    } catch (e) {
      thunkAPI.rejectWithValue('失败');
    }
  }
);

这两种方法之间的区别是什么?

在整体方案中,就单个 mySlice 状态片段而言,它们之间没有太大的区别。主要区别在于哪个地方可以处理“resolved”(已解决)的 thunk。

  • 分派显式动作:thunkAPI.dispatch(mySlice.actions.setItems(response));

    • 只有 mySlicesetItems reducer 函数能够响应并处理此动作。
  • 分派隐式 resolved(已完成)动作:return response;

    • 任何 Redux-Toolkit(RTK)切片的 extraReducers 函数都可以响应并处理 fetchItems.fulfilled 动作。

有没有其中一个更受偏爱?

这是主观的,但使用 Thunk 的 .pending.fulfilled.rejected 动作可能是普遍被接受的首选方法,因为它提供了更大的灵活性。RTK 的主要目标之一是减少你需要自己编写的代码量。使用自动生成的 Thunk 动作是这一目标的一部分。

const editItem = createAppAsyncThunk<void, string>(
  'mySlice/fetchItems',
  async (changedValue, thunkAPI) => {
    try {
      // 将数据发送到后端
      await sendSomethingToBackend(changedValue);
    
      thunkAPI.dispatch(fetchItems());
    } catch (e) {
      thunkAPI.rejectWithValue('失败');
    }
  }
);

最后,当进行更改时,我经常必须重新加载最新的数据。这次我相信我必须在 thunk 内部使用 dispatch。

是的,在这里,您绝对需要分派 fetchItems 动作,以重新获取sendSomethingToBackend 可能在后端更新的数据。

还有其他选择吗?

如果您正在获取和更新数据,那么对于您来说,一个替代方法可能是使用 Redux-Toolkit Query(RTK Query)。您可以创建一个管理查询和突变的 API 切片。将 fetchItems Thunk 转换为查询,将 editItem Thunk 转换为突变,并使用 缓存标签,突变可以使查询数据失效并触发自动重新获取查询。换句话说,进一步减少您编写的代码。

英文:

Given:

> const fetchItems = createAppAsyncThunk(
> 'mySlice/fetchItems',
> async (_, thunkAPI) => {
> try {
> //Get data from backend
> const response = await getItems();
>
> // Option 1
> thunkAPI.dispatch(mySlice.actions.setItems(response));
> // Option 2
> return response;
> } catch (e) {
> thunkAPI.rejectWithValue('failed');
> }
> }
> );
>
> What is the difference between these approaches?

In the grand scheme of things there's not much difference between the two with regards to the single mySlice state slice. The main difference comes down to what, or where, can handle the "resolved" thunks.

  • Dispatch an explicit action: thunkAPI.dispatch(mySlice.actions.setItems(response));

    • Only the mySlice setItems reducer function can respond and handle this action.
  • Dispatch an implicit resolved (fulfilled) action: return response;

    • Any Redux-Toolkit (RTK) slice's extraReducers functions can respond and handle the fetchItems.fulfilled action.

> Are any of them preferred over the other?

This is subjective, but using the Thunk's .pending, .fulfilled, and .rejected actions is likely the generally accepted preferred method as it provides greater flexibility. One of the paramount goals of RTK was to cut down on the amount of code you needed to write yourself. Using the automatically generated Thunk actions is part of this goal.

> const editItem = createAppAsyncThunk<void, string>(
> 'mySlice/fetchItems',
> async (changedValue, thunkAPI) => {
> try {
> //Send data to backend
> await sendSomethingToBackend(changedValue);
>
> thunkAPI.dispatch(fetchItems());
> } catch (e) {
> thunkAPI.rejectWithValue('failed');
> }
> }
> );
>
> Lastly when doing changes I often have to reload the data for latest
> updates. This time I believe I HAVE to use dispatch inside the thunk.

Yes, here you absolutely would need to dispatch the fetchItems action to refetch data that sendSomethingToBackend may've updated in the backend.

> Or are there alternatives?

If you are fetching and updating data then an alternative for you may be to use Redux-Toolkit Query (RTK Query). You'd create an API slice that manages queries and mutations. Convert the fetchItems Thunk to a query, and the editItem Thunk to a mutation, and using cache tags the mutations can invalidate queried data and trigger queries to re-fetch automatically. In other words, further cutting down of code you write.

huangapple
  • 本文由 发表于 2023年7月4日 23:55:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76614266.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定