Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/435.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript React Details列表始终为空_Javascript_Reactjs_Typescript_Promise - Fatal编程技术网

Javascript React Details列表始终为空

Javascript React Details列表始终为空,javascript,reactjs,typescript,promise,Javascript,Reactjs,Typescript,Promise,我正在尝试使用以下react office ui组件: 因此,我有一个带有该组件的Web部件,但我有两个问题: 第一次加载Web部件时,列表没有被选中,所以它应该打开属性页,但它没有打开,这就是为什么我不得不做一个小把戏:我一点都不喜欢 第二个问题是,当用户选择从中呈现项目的另一个列表时,readItemsAndSetStatus没有被调用,因此状态没有得到更新 网页部件代码如下: import * as React from "react"; import * as ReactDom fro

我正在尝试使用以下react office ui组件:

因此,我有一个带有该组件的Web部件,但我有两个问题:

第一次加载Web部件时,列表没有被选中,所以它应该打开属性页,但它没有打开,这就是为什么我不得不做一个小把戏:我一点都不喜欢 第二个问题是,当用户选择从中呈现项目的另一个列表时,readItemsAndSetStatus没有被调用,因此状态没有得到更新

网页部件代码如下:

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 filld 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()
            }
          ]
        }
      ]
    };
  }
}
组件Web部件代码,为简洁起见,请使用ommited代码

//#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,
  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";
//#endregion

export default class FactoryMethod extends React.Component<IFactoryMethodProps, IFactoryMethodState> {
  private listItemEntityTypeName: string = undefined;
  private _selection: Selection;

  constructor(props: IFactoryMethodProps, state: any) {
    super(props);
    this.setInitialState();
    this._configureWebPart = this._configureWebPart.bind(this);
  }

  public componentWillReceiveProps(nextProps: IFactoryMethodProps): void {
    this.listItemEntityTypeName = undefined;
    this.setInitialState();
  }

  public componentDidMount(): void {
    this.readItemsAndSetStatus();
  }

  public setInitialState(): void {
    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:[]
      },
    };
  }

  private _configureWebPart(): void {
    this.props.configureStartCallback();
  }

  // reusable inline component
  public ListMarqueeSelection = (itemState: {columns: 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>
  )

  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.DetailsListItemState.columns} />;
          case "News":
            // tslint:disable-next-line:max-line-length
            return <this.ListMarqueeSelection items={this.state.DetailsNewsListItemState.items} columns={this.state.DetailsNewsListItemState.columns}/>;
          case "Announcements":
            // tslint:disable-next-line:max-line-length
            return <this.ListMarqueeSelection items={this.state.DetailsAnnouncementListItemState.items} columns={this.state.DetailsAnnouncementListItemState.columns}/>;
          case "Directory":
            // tslint:disable-next-line:max-line-length
            return <this.ListMarqueeSelection items={this.state.DetailsDirectoryListItemState.items} columns={this.state.DetailsDirectoryListItemState.columns}/>;
          default:
            return null;
      }
  }

  // read items using factory method pattern and sets state accordingly
  private readItemsAndSetStatus(): void {

    this.setState({
      status: "Loading all items..."
    });

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

  }

  private listNotConfigured(props: IFactoryMethodProps): boolean {
    return props.listName === undefined ||
      props.listName === null ||
      props.listName.length === 0;
  }
}
readitemsandsetstatus显然只在开始时执行一次,而不是在源代码更改时执行

更新1:

感谢第一个回答的人,基于他的回答,我去研究了生命周期事件,发现了这篇不错的帖子:

基于这一点和你的回答,我更新了我的代码如下:

//#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> {
  private _selection: Selection;

  constructor(props: IFactoryMethodProps, state: any) {
    super(props);
  }

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

  //#region Mouting events lifecycle
  // the object returned by this method sets the initial value of this.state
  getInitialState(): {}   {
    return {
        type: "GenericList",
        status: this.listNotConfigured(this.props)
          ? "Please configure list in Web Part properties"
          : "Ready",
        columns: [],
        DetailsListItemState:{
          items:[]
        },
        DetailsNewsListItemState:{
          items:[]
        },
        DetailsDirectoryListItemState:{
          items:[]
        },
        DetailsAnnouncementListItemState:{
          items:[]
        },
      };
  }

  // the object returned by this method sets the initial value of this.props
  // if a complex object is returned, it is shared among all component instances
  getDefaultProps(): {}  {
    return {

    };
  }

  // invoked once BEFORE first render
  componentWillMount(nextProps: IFactoryMethodProps): void {
    // calling setState here does not cause a re-render

    this.readItemsAndSetStatus(nextProps);
  }

  // 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(nextProps: IFactoryMethodProps): React.ReactElement<IFactoryMethodProps> {
    this.readItemsAndSetStatus(nextProps);
    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(nextProps: IFactoryMethodProps): 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);

    // calling read items does not make any sense here, so I called in the will Mount, is that correct?
    // this.readItemsAndSetStatus(nextProps);
  }

  //#endregion

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

  // determines if the render method should run in the subsequent step
  // dalled BEFORE a second render
  // not called for the initial render
  shouldComponentUpdate(nextProps: IFactoryMethodProps, nextState: IFactoryMethodProps): boolean {
    // if you want the render method to execute in the next step
    // return true, else return false
      return true;
  }

  // called IMMEDIATELY BEFORE a second render
  componentWillUpdate(nextProps: IFactoryMethodProps, nextState: IFactoryMethodProps): void {
    // you cannot use this.setState() in this method
  }

  // called IMMEDIATELY AFTER a second render
  componentDidUpdate(prevProps: IFactoryMethodProps, prevState: IFactoryMethodProps): void {
    // nothing here yet
  }

  //#endregion

  // called IMMEDIATELY before a component is unmounted from the DOM, No region here, its only one method for that lifecycle
  componentWillUnmount(): void {
    // nothing here yet
  }

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

  // reusable inline component
  private ListMarqueeSelection = (itemState: {columns: 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>
  )

  // read items using factory method pattern and sets state accordingly
  private readItemsAndSetStatus(props: IFactoryMethodProps): void {

    this.setState({
      status: "Loading all items..."
    });

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

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

  //#endregion
}
那么,现在,它是否更有意义呢?

1>您用来打开属性窗格的回调正在FactoryMethod组件的构造函数中调用。这不是一个好的做法,因为构造函数不应该有任何副作用。相反,在componentDidMount中调用此回调,这是一种只调用一次的生命周期方法,非常适合任何需要在初始加载组件后只运行一次的代码。有关此方法的更多信息,请参阅

2> 函数readitemandsetstatus只执行一次,因为您在componentDidMount中调用它,这是一种生命周期方法,当组件第一次加载到页面上时,它只运行一次

public componentDidMount(): void {
    this.readItemsAndSetStatus();
  }
在componentWillReceiveProps中,您正在调用setInitialState,它将在您的组件每次收到任何新的props时重置您的状态。有关组件的更多信息将从

这将清除通过在componentDidMount方法中调用readitemandsetchanges所做的所有更改。这是你想要的吗?如果没有,那么您可能应该在这里调用readitemandsetstatus函数,以便根据通过nextProps传入的新道具更新状态

由于您将从componentDidMount和componentWillReceiveProps调用相同的函数readitemandsetstatus,因此应该将要在函数中使用的props作为参数传递

private readItemsAndSetStatus(props): void {
...
}
这将允许您从ComponentDidMount传递This.props,从componentWillReceiveProps传递nextProps,并在函数中相应地使用它们

希望这能解决你的问题

更新1:首先,您作为参考分享的链接,指的是React的一个非常旧的版本。我建议您浏览和其他较新的资源,例如,本视频,以明确您的概念。然后,您可以重新编写代码并修复您看到的任何问题

可以在代码中进行以下更改:

在构造函数中设置初始状态。这就是它的目的。此外,任何函数绑定都应该放在这里。 不要连接您不打算使用的生命周期方法。像shouldComponentUpdate这样的方法用于优化渲染,并且只有在您有正当理由的情况下才应该使用。否则,它们可能会降低性能。与往常一样,在使用方法之前,请检查方法上的。 在componentDidMount而不是componentWillMount中执行任何资源获取或回调操作,以便您的组件在进行更改之前已完全加载到DOM中。
我是否需要从render方法调用readItemsAndSetStatus?您不应该进行任何api调用或具有render方法副作用的调用。根据文档,呈现函数应该是纯的,这意味着它不修改组件状态,每次调用时返回相同的结果,并且不直接与浏览器交互。如果需要与浏览器交互,请使用componentDidMount或其他生命周期方法执行工作。保持渲染纯净使组件更容易思考。-你能看看更新1并告诉我你的想法吗?谢谢
public componentWillReceiveProps(nextProps: IFactoryMethodProps): void {
    this.listItemEntityTypeName = undefined;
    this.setInitialState();
  }
private readItemsAndSetStatus(props): void {
...
}