Skip to content

Context(asyncDataContext)

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

asyncDataContext 的存在是为了满足服务端渲染的同时服务客户端。

并且本项目在 React函数组件 原有的基础上扩展了 asyncData 方法seo 方法asyncDataContext是其中必不可少的环节。

asyncDataContext 类型

ts
import { ParsedUrl } from 'query-string';
import { IRouteMeta } from './router';

export interface IAsyncDataContext<T> {
  paresUrl: ParsedUrl; // 当前页面路由经过query-string处理后的pareUrl
  meta: IRouteMeta; // 当前页面对应的meta
  app: any; // 当前应用 asyncData 的所有数据
  page: T; // 当前页面的 asyncData 数据
}

1
2
3
4
5
6
7
8
9
10

asyncDataContext 提供的方法

ts
/**
 * 返回 AsyncData集的所有数据与当前页面的AsyncData数据、meta、paresUrl
 * @returns IAsyncDataContext: {app, page}
 */
export const useAsyncDataContext = <T>(): IAsyncDataContext<T> => {
  return useContext<IAsyncDataContext<T>>(AsyncDataContext);
};

/**
 * 返回 当前页面ParesUrl
 * @returns any IAsyncDataContext.app
 */
export const useAsyncDataParesUrlContext = () => {
  return useContext(AsyncDataContext).paresUrl;
};

/**
 * 返回 当前页面meta
 * @returns any IAsyncDataContext.app
 */
export const useAsyncDataMetaContext = () => {
  return useContext(AsyncDataContext).meta;
};

/**
 * 返回 AsyncData集的所有数据
 * @returns any IAsyncDataContext.app
 */
export const useAsyncDataAppContext = () => {
  return useContext(AsyncDataContext).app;
};

/**
 * 返回 当前页面的AsyncData数据
 * @returns T IAsyncDataContext.page
 */
export const useAsyncDataPageContext = <T>(): T => {
  return useContext<IAsyncDataContext<T>>(AsyncDataContext).page;
};

// AsyncDataUpateContext
export const AsyncDataUpateContext = createContext<
  (val: IAsyncDataContext<any>) => void
>(() => {});

/**
 * 返回 AsyncData集 的修改函数 - 慎用
 * @returns (val: IAsyncDataContext<any>) => void
 */
export const useAsyncDataUpateContext = () => {
  return useContext(AsyncDataUpateContext);
};

/**
 * 返回 AsyncData当前页面数据的修改函数
 * @returns (val: T) => void
 */
export const useAsyncDataUpatePageContext = <T>() => {
  const location = useLocation();
  const asyncData = useAsyncDataContext<T>();
  const upateAsyncData = useContext(AsyncDataUpateContext);

  return (val: T) => {
    const app = [...asyncData.app];
    const appLen = app.length;
    if (appLen > 0) {
      app.splice(appLen - 1, 1, val);
    } else {
      app.push(val);
    }
    upateAsyncData({
      ...asyncData,
      paresUrl: {
        url: location.pathname,
        query: querystring.parse(location.search),
      },
      app,
      page: val,
    });
  };
};

/**
 * 返回 发送请求当前页面数据 dispatch
 * @param cb (config: IAsyncDataProps) => Promise<T> 回调函数传入当前页面page.asyncData()
 * @returns  [loading: boolean, dispatch: () => Promise<void>, location:Location, parsedUrl: querystring.ParsedUrl]
 */
export const useEffectDispatchAsyncDataPage = <T>(
  cb: (config: IAsyncDataProps) => Promise<T | undefined>
): [boolean, () => Promise<void>, Location, querystring.ParsedUrl] => {
  const location = useLocation();
  const parsedUrl = useAsyncDataParesUrlContext();
  const upateAsyncDataPage = useAsyncDataUpatePageContext<T>();
  const [loading, setLoading] = useState<boolean>(false);

  const dispatch = async () => {
    setLoading(true);
    try {
      const asyncDataConfig: IAsyncDataProps = {
        paresUrl: {
          url: location.pathname,
          query: querystring.parse(location.search),
        },
      };
      const data = await cb(asyncDataConfig);
      data && upateAsyncDataPage(data);
    } catch (error: any) {
      console.log(error);
    }
    setLoading(false);
  };

  return [loading, dispatch, location, parsedUrl];
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

asyncDataContext 使用

项目默认嵌入并且结合 asyncData 方法,进行传递。

以下为样列:

tsx
import { memo, useEffect, useMemo } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { Spin } from 'antd';
import {
  useAsyncDataPageContext,
  useEffectDispatchAsyncDataPage,
} from '@/store/asyncDataContext';

import PaginationBase from '@/components/Pagination/Base';

import { ResponseData } from '@/utils/request';
import { queryList } from './service';
import { ResponseDataType, TableListItem, ITableData } from './data';

import styles from './index.module.less';

import { IServerPage } from '@/@types/server';

const About: IServerPage<ITableData> = () => {
  const [searchParams] = useSearchParams();
  // 获取当前页码
  const page = useMemo(
    () => Number(searchParams.get('page') || 1),
    [searchParams]
  );

  // 取出数据
  const asyncDataStore = useAsyncDataPageContext<ITableData>();
  const list = useMemo<TableListItem[]>(
    () => asyncDataStore.list || [],
    [asyncDataStore]
  );
  const total = useMemo<number>(
    () => asyncDataStore?.pagination?.total || 0,
    [asyncDataStore]
  );
  const current = useMemo<number>(
    () => asyncDataStore?.pagination?.current || 1,
    [asyncDataStore]
  );

  // 生成请求数据 dispatch
  const [loading, dispatch, location, parsedUrl] =
    useEffectDispatchAsyncDataPage<ITableData>(async config => {
      return About.asyncData && (await About.asyncData(config));
    });

  // 客户端 - 请求数据
  useEffect(() => {
    const parsedUrlQueryPage = Number(parsedUrl.query.page || 1);
    if (parsedUrl.url === location.pathname && parsedUrlQueryPage !== page) {
      dispatch();
    }
  }, [page]);

  return (
    <div className="about">
      <h1>This is an about page</h1>
      <Spin spinning={loading}>
        <div className={styles.box}>
          <ul>
            {list.map(item => (
              <li key={item.id}>
                <Link to={`/detail?id=${item.id}`}>{item.title}</Link>
                <span>{item.addtime}</span>
              </li>
            ))}
          </ul>
        </div>
        <div>
          <PaginationBase
            total={total}
            currentPage={current}
            pageUrl="/about?page={page}"
          />
        </div>
      </Spin>
    </div>
  );
};

About.asyncData = async ({ paresUrl }) => {
  const current = Number(paresUrl.query.page || 1);
  const response: ResponseData<ResponseDataType> = await queryList({
    current,
  });
  const data = response.data || { list: [], total: 0 };
  return {
    list: data.list || [],
    pagination: {
      total: data.total || 0,
      current,
      pageSize: 10,
    },
  };
};

export default memo(About);


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

Released under the MIT License.