react – 读项目源码,了解redux的高级用法

精读bounce-market源码, 使用@redux-requests/react、redux-smart-actions、 @redux-requests/react来处理react的数据

我们下面通过几个步骤来了解功能的使用和修复bug的方式了解它

1

这个是做一个异步数据的获取,这个异步功能是获取钱包的签名(异步),并保存道redux的功能,看下代码的处理方式

import { getQuery } from '@redux-requests/core';

const {
    data: { address },
} = getQuery(store.getState(), {
    type: setAccount.toString(),
    action: setAccount,
});
复制代码

对应setAccount代码

import { createAction as createSmartAction } from 'redux-smart-actions';

export const setAccount = createSmartAction(
  'AccountActions/setAccount',
  () => ({
    request: {
      promise: (async function () {
        // ... 对应异步代码
        return {
          // ...对应数据...
          balance: new BigNumber(web3.utils.fromWei(balance)),
        };
      })(),
    },
    meta: {
      suppressErrorNotification: true,
      getData: (data: ISetAccountData) => data,
      onSuccess: (
        response: { data: ISetAccountData },
        action: RequestAction,
      ) => {
        const provider = response.data.provider;
        delete response.data.provider;
        if (action.meta) {
          action.meta.provider = provider;
        }
        return response;
      },
    },
  }),
);
复制代码

业务代码就这样短,直接获取数据。

这时候可有两个疑问

  • 这个不是请求么,为啥是获取数据呢
  • 要如何请求呢

对于第一个问题,我们来分析这个获取数据的方式

我们可以看到有一个createSmartAction函数,这个是redux-smart-actions库里面的createAction方法。既然是action,那么只是获取数据而已。下面深入了解下它的运作过程。

流程是获取@redux-requests/core下面的getQuery方法然后执行并传一个createSmartAction

根据上面的用法我们可以推测出来他的用法,那就是类似useSelectorhooks, 是用来获取redux的数据,不过包装的更高级一点,但是具体怎么玩,还需要看文档。

不过不管action怎么花里胡哨的,始终不过是做dispatch的处理,真正的数据存储还是需要从store上面找

使用@reduxjs/toolkitconfigureStore做store的初始化,然后@redux-requests/core做reducer的处理。

@redux-requests/core

@redux-requests/core本身是Axios的的扩展,所以有了一些功能,比如缓存,官方自己的描述是:Race conditions, requests aborts, caching, optimistic updates, error handling

官方文档

这是store配置,可以看出和正常编写有很大的不同,细节和原始数据全部被隐藏起来了

  const configureStore = () => {
+   const { requestsReducer, requestsMiddleware } = handleRequests({
+     driver: createDriver(axios),
+   });
+
    const reducers = combineReducers({
+     requests: requestsReducer,
    });
    const store = createStore(
      reducers,
+     applyMiddleware(...requestsMiddleware),
    );
    return store;
  };
复制代码

上面有一个东西注意下,有一个axios,也就是@redux-requests/core是axios的中间件,但是融合了redux. 所以在action的时候是可以用axios一样的方法,这个库也是比较出名的,大家不会感到陌生。

但是既然融合了redux,我们也就也可以直接生成一个promise的,所以上面的代码有request.promise,但是作为一个请求库,更多的是要这样发起请求, 这个action可以很简单,比如

const fetchBooks = () => ({
  type: FETCH_BOOKS,
  request: {
    url: '/books',
    // you can put here other Axios config attributes, like data, headers etc.
  },
})
复制代码

当加上redux-smart-actions的时候,就变成

import { createAction } from 'redux-smart-actions';

const deleteBook = createAction('DELETE_BOOK', id => ({
  request: {
    url: `/books/${id}`,
    method: 'delete',
  },
  meta: {
    mutations: {
      FETCH_BOOKS: data => data.filter(book => book.id !== id),
    },
  },
}));
复制代码

可是为什么要用redux-smart-actions

redux-smart-actions

对于action其实就是少写一个type。但是它还有融合thunk和createReducer功能

官方文档

其实createAction源码非常简单(其他方法的源码也简单),其实看源码没啥心智负担,这个库比较容易理解,我们看下createAction源码吧

export const createAction = (name, action = () => ({})) => {
  const actionCreator = (...params) => ({ type: name, ...action(...params) });
  actionCreator.toString = () => name;
  return actionCreator;
}
复制代码

2

第一步的时候我们提出两个问题,但是第二个还没解答

–> 要如何请求呢?

对于上面getBook的例子可以, store.dispatch(fetchBook('1'))去获得数据。(因为这里涉及的库,核心是redux-requests,而这个就是redux-requests做出的反应)

不过,实际中,我们不是简单获取数据,我们还需要修改数据。以及设置默认数据。

但是这个不一样,store并没有数据,因为最后数据是挂载到redux里面的requests里面,所以是无法直接根据action去store找修改,那么我们可以变通一下,对于非url的异步数据,如果要做持久化存储还是其他原因需要设置数据,怎么办?

关于react web3签名持久化的做法可以看这个文章

根据实际情况,我们可以做一个登陆持久化,比如第一次签名,第二次不签名,从其他应用获取非过期的签名数据,但是数据的代码不方便直接写action里面,那么我们可以从外部dispatch,然后再根据字段进行拦截返回。

新增持久化代码

dispatch(setAccount({ address, token, chainId: chainId as BlockchainNetworkId, web3, balance, }))
复制代码

响应更新

export const setAccount = createSmartAction(
  'AccountActions/setAccount',
  (updateData?: ISetAccountData) => ({
    request: {
      promise: (async function () {
        if (updateData) {
          return updateData
        }
      }
...
复制代码

上面代码我们已经知道了,看起来数据是给createSmartAction,其实和他一点关系都没有,只是一层包装而已,真正的老大是redux-requests

不过 meta suppressErrorNotificationgetDataonSuccess是干吗的,除了这些属性,还有没有一些比较重要和使用的属性?

cache

这个属性是true的时候永久缓存,当是数字的时候代表缓存频率,比如10就是10秒内重复请求都会走缓存。

但是,突发情况,就算是缓存时间都需要重新数据数据怎么办?

cacheKey

这个数据是强制取消key,也就是cachekey变化的时候,不走缓存。还有一个是检查param参数的,key值叫requestKey.

由于缓存要么存内存要么缓硬盘,不管放哪里都是有限的空间,数据量太大的话可能会内存爆满导致卡顿,于是可以限制缓存数量

requestCapacity

这个就是限制缓存数据的。

除了缓存数量,有些情况需要强制清空,可以用clearRequestsCache

至于onSuccessgetData都是成功响应后的数据,不过参数和用途上有区别

onSuccess: (response, requestAction, store) => response
getData: data => any
复制代码

ssr

ssr: ‘client’ | ‘server’

还支持srr等,详看handle-requests

meta支持的还是挺多的。

而且这货还支持graphql, 配合@redux-requests/graphql就可以了,用法也很简单。

配置

import { handleRequests } from '@redux-requests/core';
import { createDriver } from '@redux-requests/graphql';

handleRequests({
  driver: createDriver({ url: 'http://localhost:3000/graphql' }),
});
复制代码

使用

import { gql } from '@redux-requests/graphql';

const fetchBooks = () => ({
  type: 'FETCH_BOOKS',
  request: {
    query: gql`
      {
        books {
          id
          title
          author
          liked
        }
      }
    `,
    headers: {
      SOMEHEADER: 'SOMEHEADER',
    },
  },
});
复制代码

meta其实差不多完了,但是还有一个suppressErrorNotification值,这个我在redux-requests里面没有看到,于是在项目代码发下了它的踪迹。

在handleRequests的时候,全局处理了。

...
onError: (error, action, store) => {
    if (!action.meta?.suppressErrorNotification) {
      store.dispatch(
        NotificationActions.showNotification({
          message: extractMessage(error),
          severity: 'error',
        }),
      );
    }

    throw error;
  },
});
复制代码

3

BUY 页面代码解析

View

<Queries<
      ResponseData<typeof fetchItem>,
      ResponseData<typeof fetchWeb3PoolDetails>
    >
      requestActions={[fetchItem, fetchWeb3PoolDetails]}
    >
      {({ data: item }, { data: poolDetails }) => (
      ---CODE---
复制代码
dispatch(fetchItem({ contract: data.tokenContract, id: data.tokenId }));
复制代码
export const fetchItem = createSmartAction<
  RequestAction<IApiNFTDetails, INFTDetails>
>('fetchItem', (params: { contract: string; id: number }, meta) => ({
  request: {
    url: '/api/v2/main/auth/getoneitembyid',
    method: 'post',
    data: { ct: params.contract, id: params.id },
  },
  meta: {
    getData: data => mapNFTDetails(data),
    onRequest: (
      request: any,
      action: RequestAction,
      store: Store<RootState> & { dispatchRequest: DispatchRequest },
    ) => {
      const { data } = getQuery(store.getState(), {
        type: setAccount.toString(),
        action: setAccount,
      });
      request.data.accountaddress = data?.address;
      return request;
    },
    auth: true,
    driver: 'axiosSmartchain',
    asMutation: false, 
    ...meta,
  },
}));
复制代码

fetchWeb3PoolDetails和fetchItem都是一样的技术,就是业务不一样,这里省略。

流程就是 Queries 组件引用createSmartAction数组,然后dispatch执行action成功后,数据返回最后页面直接渲染。

Queries是一个协调redux数据和渲染的组件,任务就是把集中触发action并处理异常,正常的话返回数据,代码:

export function Queries<T1 = void, T2 = void, T3 = void, T4 = void, T5 = void>({
  requestActions,
  children,
  requestKeys,
  noDataMessage,
}: ILoadingProps<T1, T2, T3, T4, T5>) {
  const queries = useAppSelector(state =>
    requestActions.map((item, index) =>
      getQuery(state, {
        type: item.toString(),
        action: item,
        requestKey: requestKeys?.[index],
      }),
    ),
  );

  if (isLoading(queries)) {
    return (
      noDataMessage || (
        <Box
          py={5}
          position="relative"
          width="100%"
          display="flex"
          justifyContent="center"
        >
          <QueryLoading />
        </Box>
      )
    );
  }

  const error = hasError(queries);

  if (error) {
    return <QueryError error={error} />;
  }

  if (isEmpty(queries)) {
    return <QueryEmpty />;
  }

  return <>{(children as any)(...queries)}</>;
}

复制代码

–完–

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享