精读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
根据上面的用法我们可以推测出来他的用法,那就是类似useSelector
hooks, 是用来获取redux的数据,不过包装的更高级一点,但是具体怎么玩,还需要看文档。
不过不管action怎么花里胡哨的,始终不过是做dispatch的处理,真正的数据存储还是需要从store上面找
使用@reduxjs/toolkit
的configureStore
做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的异步数据,如果要做持久化存储还是其他原因需要设置数据,怎么办?
根据实际情况,我们可以做一个登陆持久化,比如第一次签名,第二次不签名,从其他应用获取非过期的签名数据,但是数据的代码不方便直接写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 suppressErrorNotification
和getData
跟onSuccess
是干吗的,除了这些属性,还有没有一些比较重要和使用的属性?
cache
这个属性是true的时候永久缓存,当是数字的时候代表缓存频率,比如10就是10秒内重复请求都会走缓存。
但是,突发情况,就算是缓存时间都需要重新数据数据怎么办?
cacheKey
这个数据是强制取消key,也就是cachekey变化的时候,不走缓存。还有一个是检查param参数的,key值叫requestKey.
由于缓存要么存内存要么缓硬盘,不管放哪里都是有限的空间,数据量太大的话可能会内存爆满导致卡顿,于是可以限制缓存数量
requestCapacity
这个就是限制缓存数据的。
除了缓存数量,有些情况需要强制清空,可以用clearRequestsCache
至于onSuccess
和getData
都是成功响应后的数据,不过参数和用途上有区别
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)}</>;
}
复制代码
–完–