我有以下代码,但首先是解释。 我正在使用名为详细信息列表的REACTJS Office UI组件: https://developer.microsoft.com/en-us/fabric#/components/detailslist
我希望我的应用程序能够从任何类型的Sharepoint列表中呈现信息,而不管源有哪些列。为此,我试图实现这样的工厂方法设计模式:
export interface IListItem {
[key: string]: any;
id: string;
title: string;
modified: Date;
created: Date;
modifiedby: string;
createdby: string;
}
import {IListItem} from './IListItem';
export interface IAnnouncementListItem extends IListItem {
announcementBody: string;
expiryDate: Date;
}
import {IListItem} from './IListItem';
export interface IDirectoryListItem extends IListItem {
firstName: string;
lastName: string;
mobileNumber: string;
internalNumber: string;
}
import {IListItem} from './IListItem';
export interface INewsListItem extends IListItem {
newsheader: string;
newsbody: string;
expiryDate: Date;
}
import { IListItem } from './models/IListItem';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
export interface IFactory{
getItems(requester: SPHttpClient, siteUrl: string, listName: string): IListItem[];
}
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import { IWebPartContext } from '@microsoft/sp-webpart-base';
import { IListItem} from './models/IListItem';
import { IFactory } from './IFactory';
import { INewsListItem } from './models/INewsListItem';
import { IDirectoryListItem } from './models/IDirectoryListItem';
import { IAnnouncementListItem } from './models/IAnnouncementListItem';
export class ListItemFactory implements IFactory{
getItems(requester: SPHttpClient, siteUrl: string, listName: string): IListItem[] {
switch(listName) {
case 'List':
let items: IListItem[];
requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => {
return response.json();
})
.then((response: { value: IListItem[] }): void => {
items= response.value;
});
return items;
case 'News':
let newsitems: INewsListItem[];
requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: SPHttpClientResponse): Promise<{ value: INewsListItem[] }> => {
return response.json();
})
.then((response: { value: INewsListItem[] }): void => {
newsitems= response.value;
});
return newsitems;
case 'Announcements':
let announcementitems: IAnnouncementListItem[];
requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IAnnouncementListItem[] }> => {
return response.json();
})
.then((response: { value: IAnnouncementListItem[] }): void => {
announcementitems= response.value;
});
return announcementitems;
case 'Directory':
let directoryitems: IDirectoryListItem[];
requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IDirectoryListItem[] }> => {
return response.json();
})
.then((response: { value: IDirectoryListItem[] }): void => {
items= response.value;
});
return directoryitems;
default:
return null;
}
}
}
/* public getItems(requester: SPHttpClient, siteUrl: string, listName: string): IListItem[] {
let items: IListItem[];
requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => {
return response.json();
})
.then((response: { value: IListItem[] }): void => {
items= response.value;
});
return items;
} */
}
州级:
import { IListItem } from './models/IListItem';
import { INewsListItem } from './models/INewsListItem';
import { IDirectoryListItem } from './models/IDirectoryListItem';
import { IAnnouncementListItem } from './models/IAnnouncementListItem';
import {
IColumn
} from 'office-ui-fabric-react/lib/DetailsList';
export interface IFactoryMethodState{
type: string;
status: string;
DetailsListItemState: IDetailsListItemState;
DetailsNewsListItemState: IDetailsNewsListItemState;
DetailsDirectoryListItemState : IDetailsDirectoryListItemState;
DetailsAnnouncementListItemState : IDetailsAnnouncementListItemState;
}
export interface IDetailsListItemState {
columns: IColumn[];
items: IListItem[];
}
export interface IDetailsNewsListItemState {
columns: IColumn[];
items: INewsListItem[];
}
export interface IDetailsDirectoryListItemState {
columns: IColumn[];
items: IDirectoryListItem[];
}
export interface IDetailsAnnouncementListItemState {
columns: IColumn[];
items: IAnnouncementListItem[];
}
如您所见,我有不同类型的结果,所以我封装了该信息IfactoryMethodState
现在在组件中我使用它:
import * as React from 'react';
import styles from './FactoryMethod.module.scss';
import { IFactoryMethodProps } from './IFactoryMethodProps';
import {
IDetailsListItemState,
IDetailsNewsListItemState,
IDetailsDirectoryListItemState,
IDetailsAnnouncementListItemState,
IFactoryMethodState
} from './IFactoryMethodState';
import { IListItem } from './models/IListItem';
import { IAnnouncementListItem } from './models/IAnnouncementListItem';
import { INewsListItem } from './models/INewsListItem';
import { IDirectoryListItem } from './models/IDirectoryListItem';
import { escape } from '@microsoft/sp-lodash-subset';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import { ListItemFactory} from './ListItemFactory';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import {
DetailsList,
DetailsListLayoutMode,
Selection,
IColumn
} from 'office-ui-fabric-react/lib/DetailsList';
import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection';
import { autobind } from 'office-ui-fabric-react/lib/Utilities';
let _columns = [
{
key: 'column1',
name: 'Name',
fieldName: 'name',
minWidth: 100,
maxWidth: 200,
isResizable: true
},
{
key: 'column2',
name: 'Value',
fieldName: 'value',
minWidth: 100,
maxWidth: 200,
isResizable: true
},
];
export default class FactoryMethod extends React.Component<any, IFactoryMethodState> {
private listItemEntityTypeName: string = undefined;
private _selection: Selection;
constructor(props: IFactoryMethodProps, state: any) {
super(props);
//Initialize state
this.state = {
type: 'ListItem',
status: this.listNotConfigured(this.props) ? 'Please configure list in Web Part properties' : 'Ready',
DetailsListItemState:{
columns:[],
items:[]
},
DetailsNewsListItemState:{
columns:[],
items:[]
},
DetailsDirectoryListItemState:{
columns:[],
items:[]
},
DetailsAnnouncementListItemState:{
columns:[],
items:[]
},
};
}
public componentWillReceiveProps(nextProps: IFactoryMethodProps): void {
this.listItemEntityTypeName = undefined;
//Initialize state
this.state = {
type: 'ListItem',
status: this.listNotConfigured(this.props) ? 'Please configure list in Web Part properties' : 'Ready',
DetailsListItemState:{
columns:[],
items:[]
},
DetailsNewsListItemState:{
columns:[],
items:[]
},
DetailsDirectoryListItemState:{
columns:[],
items:[]
},
DetailsAnnouncementListItemState:{
columns:[],
items:[]
},
};
}
public render(): React.ReactElement<IFactoryMethodProps> {
let { type,
status,
DetailsListItemState,
DetailsNewsListItemState,
DetailsDirectoryListItemState,
DetailsAnnouncementListItemState } = this.state;
switch(this.props.listName)
{
case "List":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsListItemState.items }
columns={ DetailsListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
case "Announcements":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsAnnouncementListItemState.items }
columns={ DetailsAnnouncementListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
case "News":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsNewsListItemState.items }
columns={ DetailsNewsListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
case "Directory":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsDirectoryListItemState.items }
columns={ DetailsDirectoryListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
default :
break;
}
}
private readItems(): void {
this.setState({
status: 'Loading all items...'
});
let factory = new ListItemFactory();
//Here its where we actually use the pattern to make our coding easier.
switch(this.props.listName)
{
case "List":
let listItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName);
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsListItemState : {
items: listItems,
columns: [
]
}
});
break;
case "Announcements":
let announcementlistItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName) as IAnnouncementListItem[];
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsAnnouncementListItemState : {
items: announcementlistItems,
columns: []
}
});
break;
case "News":
let newsListItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName) as INewsListItem[];
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsNewsListItemState : {
items: newsListItems,
columns: []
}
});
break;
case "Directory":
let directoryListItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName) as IDirectoryListItem[];
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsDirectoryListItemState : {
items: directoryListItems,
columns: []
}
});
break;
default :
break;
}
}
private _getSelectionDetails(): string {
let selectionCount = this._selection.getSelectedCount();
switch (selectionCount) {
case 0:
return 'No items selected';
case 1:
return '1 item selected: ' + (this._selection.getSelection()[0] as any).name;
default:
return `${selectionCount} items selected`;
}
}
private listNotConfigured(props: IFactoryMethodProps): boolean {
return props.listName === undefined ||
props.listName === null ||
props.listName.length === 0;
}
}
我不喜欢这段代码:
switch语句,是否可以重写它并使其缩短,可能没有SWITCH?
国家实施,我对此并不十分确信。
更新1: 在做出改变之后,我发现了一些不正确的事情:
错误1:
public ListMarqueeSelection =(itemState:{fixedColumns:number,columns:{} [],items:{} []})=&gt; ( compact = {true}&gt; );
输入&#39; {items:{} [];列: {}[]; setKey:&#34; set&#34 ;; layoutMode: DetailsListLayoutMode.fixedColumns;塞莱...&#39;不能分配给类型 &#39; IntrinsicAttributes&amp; IntrinsicClassAttributes&amp; Readonly&lt; {children?:ReactNode; }&GT; ...&#39 ;.输入&#39; {items:{} []; 列: {}[]; setKey:&#34; set&#34 ;; layoutMode: DetailsListLayoutMode.fixedColumns;塞莱...&#39;不能分配给类型 &#39;只读&#39 ;. 财产类型&#39;列&#39;是不相容的。 输入&#39; {} []&#39;不能分配给类型&#39; IColumn []&#39;。 [ts]输入&#39; {items:{} [];列: {}[]; setKey:&#34; set&#34 ;; layoutMode: DetailsListLayoutMode.fixedColumns;塞莱...&#39;不能分配给类型 &#39; IntrinsicAttributes&amp; IntrinsicClassAttributes&amp; Readonly&lt; {children?:ReactNode; }&GT; ...&#39 ;.输入&#39; {items:{} []; 列: {}[]; setKey:&#34; set&#34 ;; layoutMode: DetailsListLayoutMode.fixedColumns;塞莱...&#39;不能分配给类型 &#39;只读&#39 ;. 财产类型&#39;列&#39;是不相容的。 输入&#39; {} []&#39;不能分配给类型&#39; IColumn []&#39;。 输入&#39; {}&#39;不能分配给类型&#39; IColumn&#39;。 物业&#39;关键&#39;在&#39; {}&#39;中缺少。 (参数)itemState:{ fixedColumns:number; 列: {}[]; items:{} []; }
错误2:
public render() {
let { type,
status,
DetailsListItemState,
DetailsNewsListItemState,
DetailsDirectoryListItemState,
DetailsAnnouncementListItemState } = this.state;
switch(this.props.listName)
{
case "List":
return <ListMarqueeSelection itemState={this.state.DetailsListItemState}/>;
case "News":
return <ListMarqueeSelection itemState={this.state.DetailsNewsListItemState}/>;
case "Announcements":
return <ListMarqueeSelection itemState={this.state.DetailsAnnouncementListItemState}/>;
case "Directory":
return <ListMarqueeSelection itemState={this.state.DetailsDirectoryListItemState}/>;
default:
return undefined;
}
}
找不到名称&#39; ListMarqueeSelection&#39;。你的意思是实例吗? member&#39; this.ListMarqueeSelection&#39;?
答案 0 :(得分:1)
你是对的,它太长了,但那是因为它的因素很少。幸运的是,它可以以简单的方式进行改进
首先,在setState
的所有案例中,*ListItemState
的所有来电都是相同的,但所使用的readItems
除外。我们应该提取一个函数并调用它,删除重复。
首先从方法readItems
开始
private readItems() {
this.setState({
status: 'Loading all items...'
});
let factory = new ListItemFactory();
//Here its where we actually use the pattern to make our coding easier.
switch(this.props.listName)
{
case "List":
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsListItemState : {
items: announcementlistItems,
columns: []
}
});
break;
break;
case "Announcements":
let announcementlistItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName) as IAnnouncementListItem[];
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsAnnouncementListItemState : {
items: announcementlistItems,
columns: []
}
});
break;
case "News":
let newsListItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName) as INewsListItem[];
this.setState({
status: `Successfully loaded ${listItems.length} items`,
DetailsNewsListItemState : {
items: newsListItems,
columns: []
}
});
break;
case "Directory":
let directoryListItems = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName) as IDirectoryListItem[];
this.setState<keyof {}>({
status: `Successfully loaded ${listItems.length} items`,
DetailsDirectoryListItemState : {
items: directoryListItems,
columns: []
}
});
break;
default :
break;
}
}
所以让我们提取一个共享方法
setStateWithList() {
const factory = new ListItemFactory();
const items = factory.getItems(this.props.spHttpClient,
this.props.siteUrl, this.props.listName);
const keyPart = this.props.listName === 'Items' ? '' : this.props.listName;
this.setState({
status: `Successfully loaded ${items.length} items`,
['Details' + keyPart + 'ListItemState' as keyof IFactoryMethodProps] : {
items,
columns: [
]
}
});
}
这导致我们
private readItems() {
this.setState({
status: 'Loading all items...'
});
//Here its where we actually use the pattern to make our coding easier.
if (this.props.listName) {
this.setStateWithList();
}
}
这显然是多余的,所以我们可以用
替换它 private readItems() {
this.setState({
status: 'Loading all items...'
});
if (this.props.listName) {
this.setStateWithList();
}
}
现在,转到render
我们拥有什么
public render() {
let { type,
status,
DetailsListItemState,
DetailsNewsListItemState,
DetailsDirectoryListItemState,
DetailsAnnouncementListItemState } = this.state;
switch(this.props.listName)
{
case "List":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsListItemState.items }
columns={ DetailsListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
case "Announcements":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsAnnouncementListItemState.items }
columns={ DetailsAnnouncementListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
case "News":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsNewsListItemState.items }
columns={ DetailsNewsListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
case "Directory":
return (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ DetailsDirectoryListItemState.items }
columns={ DetailsDirectoryListItemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }
/>
</MarqueeSelection>
</div>
);
default :
break;
}
}
我们将应用与之前相同的方法并应用参数化
ListMarqueeSelection(itemState: {fixedColumns: number, colums: IColumn[], items: IListItem[] }) => (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ itemState.items }
columns={ itemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }>
</DetailsList
</MarqueeSelection>
</div>
);
引导我们
render() {
switch(this.props.listName)
{
case "List":
return <this.ListMarqueeSelection
itemState={this.state.DetailsListItemState}/>;
case "News":
return <this.ListMarqueeSelection
itemState={this.state.DetailsNewsListItemState}/>;
case "Announcements":
return (<this.ListMarqueeSelection
itemState={this.state.DetailsAnnouncementsListItemState}/>;
case "Directory":
return <this.ListMarqueeSelection
itemState={this.state.DetailsDirectoryListItemState}/>;
default:
return undefined;
}
}
最后,让我们通过提取共享方法来清除constructor
和componentWillReceiveProps
之间的重复逻辑。
Out State
组件最终看起来像
export default class FactoryMethod extends React.Component<any, IFactoryMethodState> {
listItemEntityTypeName?: string;
_selection: Selection;
constructor(props: IFactoryMethodProps, state: any) {
super(props);
setInitialState();
}
componentWillReceiveProps(nextProps: IFactoryMethodProps) {
this.listItemEntityTypeName = undefined;
setInitialState();
}
render() {
switch(this.props.listName)
{
case "List":
return <this.ListMarqueeSelection
itemState={this.state.DetailsListItemState}/>;
case "News":
return <this.ListMarqueeSelection
itemState={this.state.DetailsNewsListItemState}/>;
case "Announcements":
return (<this.ListMarqueeSelection
itemState={this.state.DetailsAnnouncementsListItemState}/>;
case "Directory":
return <this.ListMarqueeSelection
itemState={this.state.DetailsDirectoryListItemState}/>;
default:
return undefined;
}
}
readItems() {
this.setState({
status: 'Loading all items...'
});
if (this.props.listName) {
this.setStateWithList();
}
}
setInitialState() {
this.state = {
type: 'ListItem',
status: this.listNotConfigured(this.props)
? 'Please configure list in Web Part properties'
: 'Ready',
DetailsListItemState:{
columns:[],
items:[]
},
DetailsNewsListItemState:{
columns:[],
items:[]
},
DetailsDirectoryListItemState:{
columns:[],
items:[]
},
DetailsAnnouncementListItemState:{
columns:[],
items:[]
},
};
}
ListMarqueeSelection = (itemState: {fixedColumns: number, colums: {}[], items: {}[] }) => (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ itemState.items }
columns={ itemState.columns }
setKey='set'
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }>
</DetailsList
</MarqueeSelection>
</div>
);
setStateWithList() {
const factory = new ListItemFactory();
const items = factory.getItems(this.props.spHttpClient,
this.props.siteUrl, this.props.listName);
const keyPart = this.props.listName === 'Items' ? '' : this.props.listName;
// the explicit specification of the type argument `keyof {}` is bad and
// it should not be required.
this.setState<keyof {}>({
status: `Successfully loaded ${items.length} items`,
['Details' + keyPart + 'ListItemState'] : {
items,
columns: [
]
}
});
}
_getSelectionDetails() {
const selectionCount = this._selection.getSelectedCount();
switch (selectionCount) {
case 0:
return 'No items selected';
case 1:
return `1 item selected: ${this._selection.getSelection()[0]).name}`;
default:
return `${selectionCount} items selected`;
}
}
listNotConfigured(props: IFactoryMethodProps) {
return !props.listNane || props.listName.length === 0;
}
}
当然,我们可以用另一种机制取代开关并改进更多,但这是一项重大改进。
我们可以看到问题不是转换,而是重复并丢失了临时子类型的机会