Redxu(RTK) 基础 第2节 应用的结构

太长不看

主要介绍了 thunk配合Typescript怎么写吖

应用的结构

Typescript版本的 code 作业在这里...

这一篇实际上和快速上手的内容差不多,程序范例里其实就是多了一个 thunk ,另外介绍了redux devTools 应该怎么使用。

在计算机编程中,thunk是一个用于向另一个子程序注入计算的子程序。Thunks主要用于延迟计算,直到需要其结果,或在其他子程序的开始或结束时插入操作。它们在编译器代码生成和模块化编程中还有许多其他应用。

我理解,redux的thunk 也是一个actionCreator,但是他不直接返回action,而是返回一个(能创造一个action对象的)函数。
正常同步操作,dispatch的参数就是一个action对吧。
但是,一个异步的操作,传不了state,比如你需要跟服务器查你还剩多少余额,肯定得等服务器返回数据,把这个数据装进payload,组装成action,然后再dispatch。

thunk 返回的函数,就是干上面的事儿,做一个异步操作,等有结果了封装action,并dispatch。

Redux Thunk is a middleware that lets you call action creators that return a function instead of an action object. That function receives the store’s dispatch method, which is then used to dispatch regular synchronous actions inside the function’s body once the asynchronous operations have been completed.

注意一点啊,thunk 他的构造形式有点绕,他一般接受一个数据(比如UI给的什么什么东西),然后返回一个函数,就叫t吧,
t的签名一定包含了一个dispatch 如下:
(dispatch)=>{
//异步操作
//根据异步操作的结果封装 action
//dispatch (action)
}

但我现在有个疑问,这个thunk里边的 dispatch从哪来的?
另外你要是用Typescript,thunk这块儿还得改改,因为默认的 dispatch参数是个AnyAction,thunk 返回一个函数他不是action。
具体怎么改,很简单,定义一个AppThunk 类型(注意,这个名字随便起的,核心在于,告诉Typescript,这是个thunk类型的东东),rtk给了一个ThunkAction类型,具体如下,这是文档上的sample里的内容吖。


// store.ts
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});
//  useDispatch<AppDispatch> 一定要用这个标注 dispatch,否则dispatch 默认的AnyAction和 Thunk类型是不会匹配的。
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;


//counterSlice.ts
//这是手写的thunk
export const incrementIfOdd =
  (amount: number): AppThunk =>
  (dispatch, getState) => {
    const currentValue = selectCount(getState());
    if (currentValue % 2 === 1) {
      dispatch(incrementByAmount(amount));
    }
  };


//Counter.tsx
import React, { ReactElement } from "react";
import { useSelector, useDispatch } from "react-redux";
import { AppDispatch } from "../../app/store";
import {
  increment,
  decrement,
  incrementAsync,
  incrementIfOdd,
} from "../counter/counterSlice";
interface Props {}

export default function Counter({}: Props): ReactElement {
  //数据,注意这个数据和slice中的name 是一致的
  const count = useSelector((state) => (state as any).counter.value);
  //功能,就是发射action
  // 注意看useDispatch泛型指定的参数 
  const dispatch = useDispatch<AppDispatch>();
  // 对 useDispatch指定的泛型,其实就是为了规避 thunk导致的类型不匹配吖,如果不这么做,就会出现
  // "类型“AppThunk<void>”的参数不能赋给类型“AnyAction”的参数。"这个错误吖 (wink!)
  return (
    <div>
      <div>count是: {count}</div>
      <button
        onClick={() => {
          dispatch(increment());
        }}
      >
        +
      </button>
      <button onClick={() => dispatch(incrementAsync(1))}>+ 异步</button>

      <button onClick={() => dispatch(incrementIfOdd(count))}>
        + 如果奇数
      </button>
      <button
        onClick={() => {
          dispatch(decrement());
        }}
      >
        -
      </button>
    </div>
  );
}

当然了,你要是嫌麻烦,就不用手写thunk,rtk给了 createAsyncThunk 这个工具,

export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount: number) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

这个incrementAsync 就是个thunk,可以直接抛给dispatch完成异步操作。
不信你看他的类型

具体Typescript类型体操是怎么做的,我弄不明白,反正不影响使用,就酱吧。。

但是我现在还有个问题,就是,thunk 中的dispatch 是怎么注入的?(我看了,但是没看懂,貌似dispatch里边实际上用了redux中间件,这个中间件就像express的中间件一样,所以能全局使用某些东东)
我看了好长时间源码,没看懂,慢慢觉得脑子不是我的了,说要离家出走。
所以现在还是当做,“我就是知道”dispatch 能给thunk注入一个dispatch 。 就酱。


上面的东西有点絮叨,下面开始还原文档吖。

一个使用Typescript编写的带有异步(thunk)操作的计数器示例

废话少说,还是按照 store 、slice 、UI的顺序来创建程序

相比上一个小节,这里直接添加redux配合Typescript的文档实践,这种操作能极大得发挥Typescript的作用吖。

import {Action, configureStore, ThunkAction} from "@reduxjs/toolkit";
// 下面这个是在slice写完后添加的内容吖
import counterReducer from "../features/counter/counterSlice";
const store = configureStore({
  reducer: {
// 下面这个是在slice写完后添加的内容吖
    counter:counterReducer},
});

export default store;

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
//  当thunk和 aciton类型不匹配,极大可能 是你的useDispatch钩子还在使用默认类型参数吖,
// 尝试把AppDispatch传入useDispatch的参数 位 .
// useDispatch<AppDispatch>
export type AppDispatch = typeof store.dispatch;
// 你如果非要手写异步thunk,用这个类型约束
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;


上面store初步完成了,还剩下reducer是空的,稍后slice完成后再补全就行了,还是要提醒一下, AppDispatch可以规避 thunk和action类型不匹配的问题。
另外,因为useDispatch和useSelector太常用了,所以官方建议把他们用 AppDispatch和RootState 类型再包装下,转而不在Typescript编写的app中使用默认类型的两个钩子,具体做法如下:

import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";

// Use throughout your app instead of plain `useDispatch` and `useSelector`

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

然后是 slice,这个slice核心在于教你怎么手动写一个thunk,这个thunk主要注意看AppThunk约束了谁,这个约束保证你的函数类型不错误,也保证了你的ui中和dispatch 的参数类型匹配。
另外也请注意RootState在这里约束了 手动写的 state Selector的 参数类型。

import { createSlice } from "@reduxjs/toolkit";
//  用这个 来 规定 带有payload的 action 的reducer中的值 的类型
import type { PayloadAction } from "@reduxjs/toolkit";
// 这个 给手动 选取状态的函数用
import type { AppThunk, RootState } from "../../app/store";

// Define a type for the slice state
interface CounterState {
  value: number;
}

// Define the initial state using that type
const initialState: CounterState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: "counter",
  // `createSlice` will infer the state type from the `initialState` argument
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const incrementAsync = (): AppThunk => (dispatch) => {
  setTimeout(() => {
    dispatch(increment());
  }, 1000);
};

// Other code such as selectors can use the imported `RootState` type
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)
// 注意,useSelector也能用,它跟Provider有关系,下面这个跟Provider没关系,emmm.
export const selectCount = (state: RootState) => state.counter.value;

export default counterSlice.reducer;

最后是UI,就写两个操作意思意思, 分别是加 和 异步 加

import React, { ReactElement } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hook";
import { increment, incrementAsync } from "./counterSlice";

interface Props {}

export default function Counter({}: Props): ReactElement {
  const dispatch = useAppDispatch();
  const count = useAppSelector((state) => state.counter.value);
  return (
    <div>
      {count}
      <button
        onClick={() => {
          dispatch(increment());
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          dispatch(incrementAsync());
        }}
      >
        + Async
      </button>
    </div>
  );
}

emmmm ,昨晚上忙活一晚上,也没搞清楚Typescript报错的原因,今天在人家Google上面一查,就知道具体哪错了。
整体来看,Typescript和JavaScript版本的 rtk使用没有任何逻辑上的区别,就是几个类型约束还有两个常用钩子,现在总结就下面几个:
AppThunk约束 手写的thunk
RootState 约束手写的selector
AppDispatch 约束钩子 dispatch (因为常用,要包装成 useAppDispatch)
TypedUseSelectorHook 用于包装useAppSelector。
大概就是这样。

另外注意,这篇只是知其然,具体为什么,真的不清楚,感觉再想想,就要长脑子了。

posted @ 2023-02-07 15:53  刘老六  阅读(127)  评论(0编辑  收藏  举报