2017-11-13 15 views
4

私は基本的にリスト名を持つドロップダウンプロパティに応じてグリッドをレンダリングする以下のSharepoint Framework webpartを持っています。reactjsのライフサイクルについて混乱しました

import * as React from "react"; 
import * as ReactDom from "react-dom"; 
import { Version } from "@microsoft/sp-core-library"; 
import { 
    BaseClientSideWebPart, 
    IPropertyPaneConfiguration, 
    PropertyPaneTextField, 
    PropertyPaneDropdown, 
    IPropertyPaneDropdownOption, 
    IPropertyPaneField, 
    PropertyPaneLabel 
} from "@microsoft/sp-webpart-base"; 

import * as strings from "FactoryMethodWebPartStrings"; 
import FactoryMethod from "./components/FactoryMethod"; 
import { IFactoryMethodProps } from "./components/IFactoryMethodProps"; 
import { IFactoryMethodWebPartProps } from "./IFactoryMethodWebPartProps"; 
import * as lodash from "@microsoft/sp-lodash-subset"; 
import List from "./components/models/List"; 
import { Environment, EnvironmentType } from "@microsoft/sp-core-library"; 
import IDataProvider from "./components/dataproviders/IDataProvider"; 
import MockDataProvider from "./test/MockDataProvider"; 
import SharePointDataProvider from "./components/dataproviders/SharepointDataProvider"; 

export default class FactoryMethodWebPart extends BaseClientSideWebPart<IFactoryMethodWebPartProps> { 
    private _dropdownOptions: IPropertyPaneDropdownOption[]; 
    private _selectedList: List; 
    private _disableDropdown: boolean; 
    private _dataProvider: IDataProvider; 
    private _factorymethodContainerComponent: FactoryMethod; 

    protected onInit(): Promise<void> { 
    this.context.statusRenderer.displayLoadingIndicator(this.domElement, "Todo"); 

    /* 
    Create the appropriate data provider depending on where the web part is running. 
    The DEBUG flag will ensure the mock data provider is not bundled with the web part when you package the 
    solution for distribution, that is, using the --ship flag with the package-solution gulp command. 
    */ 
    if (DEBUG && Environment.type === EnvironmentType.Local) { 
     this._dataProvider = new MockDataProvider(); 
    } else { 
     this._dataProvider = new SharePointDataProvider(); 
     this._dataProvider.webPartContext = this.context; 
    } 

    this.openPropertyPane = this.openPropertyPane.bind(this); 

    /* 
    Get the list of tasks lists from the current site and populate the property pane dropdown field with the values. 
    */ 
    this.loadLists() 
     .then(() => { 
     /* 
     If a list is already selected, then we would have stored the list Id in the associated web part property. 
     So, check to see if we do have a selected list for the web part. If we do, then we set that as the selected list 
     in the property pane dropdown field. 
     */ 
     if (this.properties.spListIndex) { 
      this.setSelectedList(this.properties.spListIndex.toString()); 
      this.context.statusRenderer.clearLoadingIndicator(this.domElement); 
     } 
     }); 

    return super.onInit(); 
    } 

    // render method of the webpart, actually calls Component 
    public render(): void { 
    const element: React.ReactElement<IFactoryMethodProps > = React.createElement(
     FactoryMethod, 
     { 
     spHttpClient: this.context.spHttpClient, 
     siteUrl: this.context.pageContext.web.absoluteUrl, 
     listName: this._dataProvider.selectedList === undefined ? "GenericList" : this._dataProvider.selectedList.Title, 
     dataProvider: this._dataProvider, 
     configureStartCallback: this.openPropertyPane 
     } 
    ); 

    // reactDom.render(element, this.domElement); 
    this._factorymethodContainerComponent = <FactoryMethod>ReactDom.render(element, this.domElement); 

    } 

    // loads lists from the site and fill the dropdown. 
    private loadLists(): Promise<any> { 
    return this._dataProvider.getLists() 
     .then((lists: List[]) => { 
     // disable dropdown field if there are no results from the server. 
     this._disableDropdown = lists.length === 0; 
     if (lists.length !== 0) { 
      this._dropdownOptions = lists.map((list: List) => { 
      return { 
       key: list.Id, 
       text: list.Title 
      }; 
      }); 
     } 
     }); 
    } 

    protected get dataVersion(): Version { 
    return Version.parse("1.0"); 
    } 

    protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void { 
    /* 
    Check the property path to see which property pane feld changed. If the property path matches the dropdown, then we set that list 
    as the selected list for the web part. 
    */ 
    if (propertyPath === "spListIndex") { 
     this.setSelectedList(newValue); 
    } 

    /* 
    Finally, tell property pane to re-render the web part. 
    This is valid for reactive property pane. 
    */ 
    super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue); 
    } 

    // sets the selected list based on the selection from the dropdownlist 
    private setSelectedList(value: string): void { 
    const selectedIndex: number = lodash.findIndex(this._dropdownOptions, 
     (item: IPropertyPaneDropdownOption) => item.key === value 
    ); 

    const selectedDropDownOption: IPropertyPaneDropdownOption = this._dropdownOptions[selectedIndex]; 

    if (selectedDropDownOption) { 
     this._selectedList = { 
     Title: selectedDropDownOption.text, 
     Id: selectedDropDownOption.key.toString() 
     }; 

     this._dataProvider.selectedList = this._selectedList; 
    } 
    } 


    // we add fields dynamically to the property pane, in this case its only the list field which we will render 
    private getGroupFields(): IPropertyPaneField<any>[] { 
    const fields: IPropertyPaneField<any>[] = []; 

    // we add the options from the dropdownoptions variable that was populated during init to the dropdown here. 
    fields.push(PropertyPaneDropdown("spListIndex", { 
     label: "Select a list", 
     disabled: this._disableDropdown, 
     options: this._dropdownOptions 
    })); 

    /* 
    When we do not have any lists returned from the server, we disable the dropdown. If that is the case, 
    we also add a label field displaying the appropriate message. 
    */ 
    if (this._disableDropdown) { 
     fields.push(PropertyPaneLabel(null, { 
     text: "Could not find tasks lists in your site. Create one or more tasks list and then try using the web part." 
     })); 
    } 

    return fields; 
    } 

    private openPropertyPane(): void { 
    this.context.propertyPane.open(); 
    } 

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 
    return { 
     pages: [ 
     { 
      header: { 
      description: strings.PropertyPaneDescription 
      }, 
      groups: [ 
      { 
       groupName: strings.BasicGroupName, 
       /* 
       Instead of creating the fields here, we call a method that will return the set of property fields to render. 
       */ 
       groupFields: this.getGroupFields() 
      } 
      ] 
     } 
     ] 
    }; 
    } 
} 

私component.tsx

//#region Imports 
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, 
    buildColumns, 
    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"; 
import PropTypes from "prop-types"; 
//#endregion 


export default class FactoryMethod extends React.Component<IFactoryMethodProps, IFactoryMethodState> { 
    constructor(props: IFactoryMethodProps, state: any) { 
    super(props); 
    this.setInitialState(); 
    } 

    // lifecycle help here: https://staminaloops.github.io/undefinedisnotafunction/understanding-react/ 

    //#region Mouting events lifecycle 

    // the data returned from render is neither a string nor a DOM node. 
    // it's a lightweight description of what the DOM should look like. 
    // inspects this.state and this.props and create the markup. 
    // when your data changes, the render method is called again. 
    // react diff the return value from the previous call to render with 
    // the new one, and generate a minimal set of changes to be applied to the DOM. 
    public render(): React.ReactElement<IFactoryMethodProps> { 
    switch(this.props.listName) { 
     case "GenericList": 
      // tslint:disable-next-line:max-line-length 
      return <this.ListMarqueeSelection items={this.state.DetailsListItemState.items} columns={this.state.columns} />; 
     case "News": 
      // tslint:disable-next-line:max-line-length 
      return <this.ListMarqueeSelection items={this.state.DetailsNewsListItemState.items} columns={this.state.columns}/>; 
     case "Announcements": 
      // tslint:disable-next-line:max-line-length 
      return <this.ListMarqueeSelection items={this.state.DetailsAnnouncementListItemState.items} columns={this.state.columns}/>; 
     case "Directory": 
      // tslint:disable-next-line:max-line-length 
      return <this.ListMarqueeSelection items={this.state.DetailsDirectoryListItemState.items} columns={this.state.columns}/>; 
     default: 
      return null; 
    } 
    } 

    // invoked once, only on the client (not on the server), immediately AFTER the initial rendering occurs. 
    public componentDidMount(): void { 
    // you can access any refs to your children 
    // (e.g., to access the underlying DOM representation - ReactDOM.findDOMNode). 
    // the componentDidMount() method of child components is invoked before that of parent components. 
    // if you want to integrate with other JavaScript frameworks, 
    // set timers using setTimeout or setInterval, 
    // or send AJAX requests, perform those operations in this method. 
    this._configureWebPart = this._configureWebPart.bind(this); 
    this.readItemsAndSetStatus(""); 
    } 

    //#endregion 

    //#region Props changes lifecycle events (after a property changes from parent component) 
    public componentWillReceiveProps(nextProps: IFactoryMethodProps): void { 
    if(nextProps.listName !== this.props.listName) { 
     this.readItemsAndSetStatus(nextProps.listName); 
    } 
    } 

    //#endregion 

    //#region private methods 
    private _configureWebPart(): void { 
    this.props.configureStartCallback(); 
    } 

    public setInitialState(): void { 
    this.state = { 
     type: "ListItem", 
     status: this.listNotConfigured(this.props) 
     ? "Please configure list in Web Part properties" 
     : "Ready", 
     columns:[], 
     DetailsListItemState:{ 
     items:[] 
     }, 
     DetailsNewsListItemState:{ 
     items:[] 
     }, 
     DetailsDirectoryListItemState:{ 
     items:[] 
     }, 
     DetailsAnnouncementListItemState:{ 
     items:[] 
     }, 
    }; 
    } 

    // reusable inline component 
    private ListMarqueeSelection = (itemState: {columns: IColumn[], items: IListItem[] }) => (
     <div> 
      <DetailsList 
      items={ itemState.items } 
      columns={ itemState.columns } 
      setKey="set" 
      layoutMode={ DetailsListLayoutMode.fixedColumns } 
      selectionPreservedOnEmptyClick={ true } 
      compact={ true }> 
      </DetailsList> 
     </div> 
) 

    // read items using factory method pattern and sets state accordingly 
    private readItemsAndSetStatus(nextListName: string): void { 
    this.setState({ 
     status: "Loading all items..." 
    }); 

    const factory: ListItemFactory = new ListItemFactory(); 
    factory.getItems(this.props.spHttpClient, this.props.siteUrl, nextListName) 
    .then((items: any[]) => { 
     const keyPart: string = this.props.listName === "GenericList" ? "" : nextListName; 
     // 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: buildColumns(items) 
     }); 
    }); 
    } 

    private listNotConfigured(props: IFactoryMethodProps): boolean { 
    return props.listName === undefined || 
     props.listName === null || 
     props.listName.length === 0; 
    } 

    //#endregion 
} 

と私の工場オブジェクト

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 { 
    private _listItems: IListItem[]; 
    public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<IListItem[]> { 
     if(listName === ""){ 
      listName = "GenericList"; 
     } 
     switch(listName) { 
      case "GenericList": 
       let items: IListItem[]; 
       // tslint:disable-next-line:max-line-length 
       return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`, 
       SPHttpClient.configurations.v1, 
       { 
        headers: { 
         "Accept": "application/json;odata=nometadata", 
         "odata-version": "" 
        } 
       }) 
       .then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => { 
        return response.json(); 
       }) 
       .then((json: { value: IListItem[] }) => { 
        console.log(JSON.stringify(json.value)); 
        return items=json.value.map((v,i)=>(
         { 
          //key: v.id, 
          id: v.Id, 
          title: v.Title, 
          created: v.Created, 
          createdby: v.Author.Title, 
          modified: v.Modified, 
          modifiedby: v.Editor.Title       
         } 
        )); 
       }); 
      case "News": 
       let newsitems: INewsListItem[]; 
       // tslint:disable-next-line:max-line-length 
       return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`, 
       SPHttpClient.configurations.v1, 
       { 
        headers: { 
         "Accept": "application/json;odata=nometadata", 
         "odata-version": "" 
        } 
       }) 
       .then((response: SPHttpClientResponse): Promise<{ value: INewsListItem[] }> => { 
        return response.json(); 
       }) 
       .then((json: { value: INewsListItem[] }) => { 
        return items=json.value.map((v,i)=>(
         { 
          id: v.Id, 
          title: v.Title, 
          created: v.Created, 
          createdby: v.Author.Title, 
          modified: v.Modified, 
          modifiedby: v.Editor.Title, 
          newsheader: v.newsheader, 
          newsbody: v.newsbody, 
          expiryDate: v.expiryDate 
         } 
        )); 
       }); 
      case "Announcements": 
       let announcementitems: IAnnouncementListItem[]; 
       return 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((json: { value: IAnnouncementListItem[] }) => { 
        return items=json.value.map((v,i)=>(
         { 
          id: v.Id, 
          title: v.Title, 
          created: v.Created, 
          createdby: v.Author.Title, 
          modified: v.Modified, 
          modifiedby: v.Editor.Title, 
          announcementBody: v.announcementBody, 
          expiryDate: v.expiryDate 
         } 
        )); 
       }); 
      case "Directory": 
       let directoryitems: IDirectoryListItem[]; 
       return 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((json: { value: IDirectoryListItem[] }) => { 
        return items=json.value.map((v,i)=>(
         { 
          id: v.Id, 
          title: v.Title, 
          created: v.Created, 
          createdby: v.Author.Title, 
          modified: v.Modified, 
          modifiedby: v.Editor.Title, 
          firstName: v.firstName, 
          lastName: v.lastName, 
          mobileNumber: v.mobileNumber, 
          internalNumber: v.internalNumber 
         } 
        )); 
       }); 
      default: 
      // tslint:disable-next-line:max-line-length 
       return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`, 
       SPHttpClient.configurations.v1, 
       { 
        headers: { 
         "Accept": "application/json;odata=nometadata", 
         "odata-version": "" 
        } 
       }) 
       .then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => { 
        return response.json(); 
       }) 
       .then((json: { value: IListItem[] }) => { 
        console.log(JSON.stringify(json.value)); 
        return items=json.value.map((v,i)=>(
         { 
          //key: v.id, 
          id: v.Id, 
          title: v.Title, 
          created: v.Created, 
          createdby: v.Author.Title, 
          modified: v.Modified, 
          modifiedby: v.Editor.Title       
         } 
        )); 
       }); 
      } 
     } 
} 

問題は、それがレンダリング初めて、リスト名が定義されていないということなので、からデータを読み込み、 GenericListと私は目的の動作であるレンダリングされたグリッドを見ることができます

しかし、私は別のリストを選択するたびに、空リストは、getlistitemsパラメータのパラメータとして空文字列であり、通知、ニュースなどは受け付けません。

ここでは何が欠けていますか?

+7

は、ドキュメントは、ライフサイクルfunctions.Checkに関するすべての混乱をクリアするための良い源ですリアクトこのうちhttps://reactjs.org/ docs/react-component.html –

+0

これらの呼び出しが非同期であるため、setState呼び出しでのみ使用するreadItemsAndSetStatus関数を再作成することを検討してください。 – snatera

+0

私は理解していません、どうしますか? –

答えて

1

注:私はSharePointフレームワークを使用しません。

SharePointフレームワークWebPartとReactとの統合に問題があると思います。 Reactアプリはそのプロパティの変更を受け取ることがないため、ドロップダウンの後のReactアプリには変更が表示されません。

1)手動FactoryMethodWebPart.render()を呼び出すときsetSelectedList()時のドロップダウンの変更:

は、2つのオプションがあります。私はSharePointフレームワークを使用しないので、これがベストプラクティスかどうかはわかりません。私はReactアプリケーションを再構築し、再レンダリングをトリガーするのではなくDOMに再度挿入することを知っています。

2)すでにビルドされたReactアプリに新しい小道具を渡すことができる高次コンポーネントを使用します。この解決策はthis articleに基づいています。アイデアは、コンポーネントの周りにラッパーを返す関数を構築することです。この関数はレンダーするコンポーネントとsubscribe関数の両方を受け入れ、新しい関数をコンポーネントに渡すために使用できる関数を受け入れます。 Componentが作成されると、subscribe()メソッドが呼び出され、Componentが渡されます。

HOC:

const connect = (Component, subscribe) => class extends React.Component { 
    constructor(props) { 
    super(props); 

    const rerender = props => this.setState(props); 
    subscribe(rerender); 
    } 

    render() { 
    const props = { ...this.props, ...this.state }; 
    return <Component {...props} />; 
    } 
} 

ユースケース:

export default class FactoryMethodWebPart extends BaseClientSideWebPart<IFactoryMethodWebPartProps> { 
    // other private fields 
    private _factoryMethodComponent; 
    private _setFactoryMethodProps; 

    protected onInit(): Promise<void> { 
    // ... 
    this._factoryMethodComponent = connect(FactoryMethod, rerender => this._setFactoryMethodProps = rerender); 
    } 

    public render(): void { 
    const element = React.createElement(
     this._factoryMethodComponent, // use the connected component instead of FactoryMethod 
     { ... } 
    ] ); 

    this._factorymethodContainerComponent = ReactDom.render(element, this.domElement); 
    } 

    private setSelectedList(value: string): void { 
    // ... 

    this._setFactoryMethodProps({ listName: selectedDropDownOption ? selectedDropDownOption.text : 'GenericList' }); 
    } 
} 
+0

<! - language:lang-tsx - >を使用してコードをフォーマットすることができます – Kunukn

関連する問題