预期目标
我自己随便写了一些mock data,格式是json:
[
{"Name": "Tom", "Team": "Tom & Jerry", "Sex": "male"},
{"Name": "Lei Li", "Team": "English Learning", "Sex": "male"},
{"Name": "Meimei Han", "Team": "English Learning", "Sex": "female"},
{"Name": "Jerry", "Team": "Tom & Jerry", "Sex": "male"}
]
为了保证后续代码的严谨性,我这里先将组别乱序,防止到时候Grouping功能没有生效,但效果还是一样。
我们的目标是做一个有简单交互界面,界面主要就两个组件:
- 右侧复选框:选择性别,以展示选中内容
- 左侧内容区域:以Team分组,分别显示人名
我简单用Mockplus绘制了一下原型图,就是这样一个简单的效果。右边我计划使用简单的文字形式呈现,因为Fluent UI的DetailsList要写的代码还是不少的。
编写组件
这里说明的一下,因为Mobx的原因,我初步的教程是使用Class Component进行讲解,后续有时间的话,我会添加最新的Function Component写法。
我们已经有了原型图了,怎么样设计一个编写代码的结构呢?我强烈建议大家以后可以研读一下官网的《React哲学》,这篇文章很好地展示了React的一个开发流程,尤其是如何确定state的说明,虽然我们这边将会用MobX代替state。
可以看到我将这个页面分为三个模块,红色区域为整个大组件将会包含两个子组件。那么很显然,我将会主要编写三个组件,红色区域的整体控件Canvas_Class.tsx
,绿色的信息控件DetailsInfo_Class.tsx
,蓝色的多选框CheckBox_Class.tsx
。
首先在src
文件夹下创建文件夹Component
,然后创建这三个文件。
准备工作
Fluent UI的一些组件(比如CheckBox)会依赖于它自身的图标库,因此我们需要先初始化一下图标,官方文档在这。
我们修改一下index.tsx
,添加initializeIcons
,这个方法建议在最顶级的那一层添加,所以我才放在index.tsx
。同时,它需要放在render前面。
import { Canvas } from './Components/Canvas_Class';
import React from 'react';
import ReactDOM from 'react-dom';
import { initializeIcons } from '@fluentui/react/lib/Icons';
initializeIcons();
ReactDOM.render(
<React.StrictMode>
<Canvas/>
</React.StrictMode>,
document.getElementById('root')
);
这里Canvas会报错,不过别担心,后面就会写。
整体控件Canvas
Fluent UI也有自己的栅格系统。这里我们使用一下栅格系统简单布局。
但是栅格系统是依赖于Fabric Core的,我们需要添加一下相关环境,配置文档在这。
打开public文件夹下的index.html
,添加一行
<link
rel="stylesheet"
href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/11.0.0/css/fabric.min.css"
/>
然后在body标签上添加一下类
<body class="ms-Fabric" dir="ltr">
</body>
当然,也可以选择使用npm添加,具体操作就看上面的官方文档即可。
然后我们简单使用栅格系统来划一下区域,根据屏幕大小,可以使组件自动调整位置。当然记得在class前添加一下export,不然没法在外部使用这个组件。
import * as React from 'react';
import {CheckBoxSex} from './CheckBox_Class';
import {DetailsInfo} from './DetailsInfo_Class';
export class Canvas extends React.Component {
public render() {
return (
<div className="ms-Grid" dir="ltr">
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm6 ms-md4 ms-lg3">
<DetailsInfo />
</div>
<div className="ms-Grid-col ms-sm6 ms-md8 ms-lg9">
<CheckBoxSex />
</div>
</div>
</div>
);
}
}
复选框CheckBox
我们进入Fluent UI官网搜索自己想要的控件,这里选择了Checkbox。
巨硬还是很贴心地给了源代码(我等CV工程师福音),点击右上角Show Code
就可以查看,或者点击Export to CodePen
就可以在线编辑了。上面提到过Class Component和Function Component,官方例程就是一个很好的Function Component,我这边将其简单改写成Class Component。
import * as React from 'react';
import { Checkbox, Stack } from '@fluentui/react';
export class CheckBoxSex extends React.Component {
// Used to add spacing between checkboxes
private stackTokens = { childrenGap: 10 };
public render() {
return (
<Stack tokens={this.stackTokens}>
<Checkbox label="female" defaultChecked onChange={this._onChange} />
<Checkbox label="male" defaultChecked onChange={this._onChange} />
</Stack>
);
}
private _onChange(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) {
console.log(`The option has been changed to ${isChecked}.`);
}
}
信息显示区域DetailsInfo
这里重新看一下数据:
[
{"Name": "Tom", "Team": "Tom & Jerry", "Sex": "male"},
{"Name": "Lei Li", "Team": "English Learning", "Sex": "male"},
{"Name": "Meimei Han", "Team": "English Learning", "Sex": "female"},
{"Name": "Jerry", "Team": "Tom & Jerry", "Sex": "male"}
]
一个人有三个属性,TypeScript中可以接口来表示这个对象。我们再在src
文件夹下创建一个Model
文件夹,添加一个PageModel.ts
文件。添加一个接口
export interface PersonInfo {
Name: string,
Team: string,
Sex: string
}
然后可以将刚刚的静态数据先放到组件里面,动态获取的话下章再说。
然后,因为我们希望将所有人按组分类,所以再添加一个接口用于分组
export interface TeamInfo {
TeamName: string,
Persons: PersonInfo[]
}
简单写了个Group的方法
import * as React from 'react';
import { PersonInfo, TeamInfo } from '../Model/PageModel';
import { Stack, Text } from '@fluentui/react';
export class DetailsInfo extends React.Component {
private infoArray: PersonInfo[] = [
{"Name": "Tom", "Team": "Tom & Jerry", "Sex": "male"},
{"Name": "Lei Li", "Team": "English Learning", "Sex": "male"},
{"Name": "Meimei Han", "Team": "English Learning", "Sex": "female"},
{"Name": "Jerry", "Team": "Tom & Jerry", "Sex": "male"}
];
private getTeamInfoArr(infoArray: PersonInfo[]): TeamInfo[] {
let teamInfoArray: TeamInfo[] = new Array<TeamInfo>();
let teamInfoMap: Map<String, PersonInfo[]> = new Map();
infoArray.forEach((personInfo) => {
if (!teamInfoMap.has(personInfo.Team)) {
teamInfoMap.set(personInfo.Team, new Array<PersonInfo>());
}
teamInfoMap.get(personInfo.Team)?.push(personInfo);
});
// console.log(teamInfoMap);
teamInfoMap.forEach((personArr, teamName) => {
teamInfoArray.push({
TeamName: teamName.toString(),
Persons: personArr
});
});
// console.log(teamInfoArray);
return teamInfoArray;
}
public render() {
let teams = this.getTeamInfoArr(this.infoArray);
return (
<Stack>
{
teams.map((teamInfo) => (
<Stack>
<Text variant={"xLarge"}>{teamInfo.TeamName}</Text>
{
teamInfo.Persons.map((personInfo) => (
<Stack>
{personInfo.Name + '-' + personInfo.Sex}
</Stack>
))
}
</Stack>
))
}
</Stack>
);
}
}
最后效果
执行
npm start
# or
yarn start
最后界面如上图所示。