跳转到主文档

从 BFF 获取数据

上一小节中,我们创建了【 函数写法 】的 BFF,这一小节中,我们将用该模式为联系人列表实现一个数据接口。

我们首先把不需要的样板文件清理掉,把 index.ts 重命名为 contacts.ts,执行以下命令:

rm -r api/info api/_app.ts
mv api/index.ts api/contacts.ts

此时 API 路由将变为 /api/contacts

我们使用 faker 来 mock 需要的数据,首先安装依赖:

pnpm add faker

api/contacts.ts 内容改为:

import { name, internet } from 'faker';

export const get = async () => {
const mockData = new Array(20).fill(0).map(() => {
const firstName = name.firstName();
return {
name: firstName,
avatar: `https://avatars.dicebear.com/api/identicon/${firstName}.svg`,
email: internet.email(),
};
});

return { code: 200, data: mockData };
};

数据同样可以从远端接口获取,此处仅为了演示。

执行 pnpm run dev,访问 http://localhost:8080/api/contacts

browser-result

接下来我们把 src/contacts/App.tsx 里硬编码的 mockData 改成从 BFF 加载。

我们将业务逻辑迁移到组件中,App.tsx 只保留简单的路由部分,执行:

mkdir -p src/contacts/components/AllContacts/
mkdir -p src/contacts/components/ArchivedContacts/
touch src/contacts/components/AllContacts/index.tsx
touch src/contacts/components/ArchivedContacts/index.tsx

ArchivedContacts/index.tsx 的内容:

import { List } from 'antd';
import Item from '../Item';

const getAvatar = (users: Array<{ name: string; email: string }>) =>
users.map(user => ({
...user,
avatar: `https://avatars.dicebear.com/v2/identicon/${user.name}.svg`,
}));

const mockArchivedData = getAvatar([
{ name: 'Thomas', email: 'w.kccip@bllmfbgv.dm' },
{ name: 'Chow', email: 'f.lfqljnlk@ywoefljhc.af' },
]);

const ArchivedContacts = () => (
<List
dataSource={mockArchivedData}
renderItem={info => <Item key={info.name} info={info} />}
/>
);

export default ArchivedContacts;

AllContacts/index.tsx 的内容:

import { useState, useEffect } from 'react';
import { List } from 'antd';
import { get as contacts } from '@api/contacts';
import Item from '../Item';

const AllContacts = () => {
const [list, setList] = useState(
[] as Array<{ name: string; email: string; avatar: string }>,
);
const loadMockData = async () => {
const { data } = await contacts();
setList(data);
};
useEffect(() => {
if (!list.length) {
loadMockData();
}
});
return (
(list.length && (
<List
dataSource={list}
renderItem={info => <Item key={info.name} info={info} />}
/>
)) || (
<div className="p-4 items-center border-gray-200 border-b border-t custom-text-gray">
Pending...
</div>
)
);
};

export default AllContacts;

在 Modern.js 中,可以通过调用函数(前后端一体化)的方式,直接调用 BFF 接口,调用时无需关心域名、路径等。

在使用 pnpm run new 创建 BFF 时,Modern.js 已经默认在 tsconig.json 中注入了别名。这也是之前提到的,生成器在项目创建之后并不会被抛弃,仍旧可以在开发过程中不断为项目提供新的内容。

接下来修改 src/contacts/App.tsx,把 List 组件的调用代码替换成上面的 AllContactsArchivedContacts

<Route path="/" exact={true}>
<Helmet>
<title>All</title>
</Helmet>
<AllContacts />
</Route>
<Route path="/archives" exact={true}>
<Helmet>
<title>Archives</title>
</Helmet>
<ArchivedContacts />
</Route>

删除 App.tsx 里的 mock 数据和 List 组件,把 Item 组件替换成 ContactList

import { useState } from 'react';
import { Radio, RadioChangeEvent } from 'antd';
import { Route, Switch, useHistory } from '@modern-js/runtime/router';
import { Helmet } from '@modern-js/runtime/head';
import 'tailwindcss/base.css';
import 'tailwindcss/components.css';
import 'tailwindcss/utilities.css';
import './styles/utils.css';
import AllContacts from './components/AllContacts';
import ArchivedContacts from './components/ArchivedContacts';

执行 pnpm run dev,再打开页面http://localhost:8000/contacts,可以看到页面加载后,会先初始化联系人数据(显示 pending),之后每次切换到 All 列表,也会重新请求联系人数据:

browser-result


本小节的代码可以在这里查看