【问题标题】:How to re-render React Component when promise resolves? | How to block render until data loads?当 promise 解决时如何重新渲染 React 组件? |如何在数据加载之前阻止渲染?
【发布时间】:2020-04-22 06:31:40
【问题描述】:

我尝试更新指向azure-devops-ui/Filter 的功能组件。我正在使用返回异步响应的azure-devops-extension-sdk,以便使用此组件:
https://developer.microsoft.com/en-us/azure-devops/components/filter Azure DevOps 上的 WorkItem 内

我已经分别使用 this.state/componentDidMountuseState/useEffect 编写了基于类和函数组件的代码。我关注了this SO post

但是,我只能重新渲染状态。状态更新时,类和功能组件中的 UI 组件都不会更新。

我的代码有两个版本,它们都在等待响应并成功更新状态。但是,两者都不会等待 UI 的呈现。

通用组件:

import {
    IWorkItemChangedArgs,
    IWorkItemFieldChangedArgs,
    IWorkItemFormService,
    IWorkItemLoadedArgs,
    WorkItemTrackingServiceIds,
  } from "azure-devops-extension-api/WorkItemTracking";

import * as React from "react";
import * as SDK from "azure-devops-extension-sdk";

import { Header } from "azure-devops-ui/Header";
import { Page } from "azure-devops-ui/Page";

import { Filter, getKeywordFilterItem } from "azure-devops-ui/Filter";
import { IListBoxItem } from "azure-devops-ui/ListBox";
import { AggregateItemProvider } from "azure-devops-ui/Utilities/AggregateItemProvider";
import {
    Filter as FilterStore,
    FILTER_CHANGE_EVENT,
    IFilterState
} from "azure-devops-ui/Utilities/Filter";
import { GroupedItemProvider } from "azure-devops-ui//Utilities/GroupedItemProvider";
import { groupedItems, groups, statusItems } from "./data";
        export class WorkItemComponent extends React.Component<{} & ExtendedProps, any> {

    private provider: AggregateItemProvider<IListBoxItem>;

    private filterStore = new FilterStore();

    private textoStore = "second";

    private groupedProvider = new GroupedItemProvider([], [], true);
    private filterItems = [
        getKeywordFilterItem(this.filterStore),
        { name: "Status", id: "status", items: statusItems, filterItemKey: "status" },
        {
            name: "Group Items",
            id: "groupItems",
            items: this.groupedProvider,
            filterItemKey: "groupItems"
        }
    ];

    constructor(props: {}) {
        super(props);
        this.provider = new AggregateItemProvider<IListBoxItem>();
        this.groupedProvider.push(...groupedItems);
        this.groupedProvider.pushGroups(...groups);
        this.provider.push(statusItems);
        this.provider.push(this.groupedProvider);


        if(this.props.pdata==="first")
        this.filterStore = new FilterStore(
          {defaultState: {    groupItems: {        value: [this.props.pdata,this.textoStore]    }}} 
          );
          else
          this.filterStore = new FilterStore(
            {defaultState: {    groupItems: {        value: [this.textoStore,this.props.pdata,]    }}} 
            );


          this.filterStore.subscribe(this.onFilterChanged, FILTER_CHANGE_EVENT);


        this.state = {
            //currentState: ""
            currentState: JSON.stringify(this.filterStore.getState(), null, 4)
        };

    }

    public render():JSX.Element {

        return (
            <Page className="sample-hub flex-grow">
                <Header title="Filter" />
                <div className="page-content">
                <Filter 
                    filterStore={this.filterStore}
                    filterItems={this.filterItems}
                    items={this.provider}
                />
                    <div style={{ marginTop: "16px" }} className="monospaced-text">
                        <span>Current state:</span>
                        <span>{this.state.currentState}</span>
                        <span>{this.props.pdata}</span>
                    </div>
                </div>
            </Page>
        );
    }

    private onFilterChanged = (changedState: IFilterState) => {
        this.setState({
            currentState: JSON.stringify(this.filterStore.getState(), null, 4)
        });
        this.onFilterChangedExtended(JSON.stringify(this.filterStore.getState(), null, 4))
    };

    private async onFilterChangedExtended(estadoActual: string) {
        const workItemFormService = await SDK.getService<IWorkItemFormService>(
          WorkItemTrackingServiceIds.WorkItemFormService
        );

        workItemFormService.setFieldValue(SDK.getConfiguration().witInputs.FieldName, estadoActual);

      }

Functional 组件中第一个具有 useState 和 useEffect 的 Caller:

        import { WorkItemComponent } from "./WorkItemComponent";

  const WorkItemFilterAsync: React.FC = props => {

    let respuestaAsync="";

    const [data, setData] = React.useState<string>('');
    React.useEffect(() => {
    const fetchdata = async() =>{
      const result = await fngetFieldName()

         setData(result);

    }      
      // Execute the created function directly
      fetchdata();

    }, []);


    async function fngetFieldName(): Promise<string> {
      const workItemFormService = await SDK.getService<IWorkItemFormService>(
        WorkItemTrackingServiceIds.WorkItemFormService
      );

      const respuesta = workItemFormService.getFieldValue(SDK.getConfiguration().witInputs.FieldName);
      respuestaAsync = (await respuesta).toString();
      return JSON.stringify(respuesta);
    }
  return <WorkItemComponent pdata={data}/>
}
export default WorkItemFilterAsync;

第二个调用者在一个类上有componentDidMount:

import {
    IWorkItemChangedArgs,
    IWorkItemFieldChangedArgs,
    IWorkItemFormService,
    IWorkItemLoadedArgs,
    WorkItemTrackingServiceIds,
  } from "azure-devops-extension-api/WorkItemTracking";


import * as React from "react";
import { showRootComponent } from "../../Common";
import * as SDK from "azure-devops-extension-sdk";

import { WorkItemComponent } from "./WorkItemComponent";

//const WorkItemComponent = React.lazy (() => import('./WorkItemComponent'));


class WorkItemFilter extends React.Component{
    state = {
        externalData: false,
      };

      public componentDidMount() {
          this.onLoadExtended();
    }

    render():JSX.Element {

            return(

                <div className="page-content">
                    {this.state.externalData ? <WorkItemComponent pdata="first"/> : <WorkItemComponent pdata="third"/>}
                </div>

            );
        }


        private async onLoadExtended() {
            const workItemFormService = await SDK.getService<IWorkItemFormService>(
              WorkItemTrackingServiceIds.WorkItemFormService
            );

            let varaux = workItemFormService.getFieldValue(SDK.getConfiguration().witInputs.FieldName);

            if ((await varaux).toString()!=="")
            {

                this.setState({
                    externalData: true,
                });

            }

          }


}


  showRootComponent(<WorkItemFilter />);


这是父组件:

export function showRootComponent(component: React.ReactElement<any>) {
    ReactDOM.render(component, document.getElementById("root"));
}

Azure Dev Ops Extension (azure-devops-extension.json) 的配置:

{
    "contributions": [
        {
            "id": "work-item-filter",
            "type": "ms.vss-work-web.work-item-form-control",
            "description": "Custom Filter",
            "targets": [
                "ms.vss-work-web.work-item-form"
            ],
            "properties": {
                "name": "BHD Filter",
                "uri": "dist/WorkItemFilter/WorkItemFilter.html",
                "height": 600,
                "inputs": [
                    {
                        "id":"FieldName",
                        "name": "Select the field for this control.",
                        "type": "WorkItemField",
                        "properties": {
                            "workItemFieldTypes": ["String", "PlainText", "HTML"]
                        },
                        "validation": {
                            "dataType": "String",
                            "isRequired": true
                        }

我也尝试将ReactDOM.render 放在浏览器事件中,但我不能这样做,因为 UI 扩展需要一个字段来保存数据:

【问题讨论】:

  • “但我只能重新渲染状态,而不是 UI 组件,既不在类也不在功能组件中”是什么意思。在 React 中,每当 state 或 props 更新时,都会调用 render 函数。它将被重新渲染。在功能组件中,组件将在状态或道具更新时更新。状态来自useState 钩子。我不太了解 TypeScript,但我认为您想使用 useState 挂钩存储来自 Promise 的数据。获取useEffect 中的数据并从useState 挂钩中设置状态。它会自动重新渲染。
  • 亲爱的@technogeek1995:虽然我把&lt;Filter filterStore={this.state.filterStore} ;仅在&lt;span&gt;{this.state.currentState}&lt;/span&gt; 上更改状态、工作或更新,而不是在 中。仍然使用 Button 和 onClick 事件,我更改了 state.filterStore 但过滤器从不更新此值或自动检查任何值,它就像构造函数上的图像一样工作。
  • 亲爱的@technogeek1995:就像 ReactDOM.render 在这两种情况下都不会等待异步函数 workItemFormService.getFieldValue 的响应,即使是 Lazy 和 Suspense
  • 我没有使用 Lazy 和 Suspense 的经验。但是,渲染函数是非阻塞的。它永远不会等待承诺的价值。它只会使用道具或状态更改重新渲染。因此,当您获取数据时,您需要设置状态,以便组件将使用新状态重新渲染并更新值。然后你会看到值不仅随着 onClick 更新,而且当数据被 Promise 接收时更新。如果这有助于更好地解释它,我可以将其形式化为一个简单的香草 JS 答案?
  • 是的,亲爱的@technogeek1995

标签: javascript node.js reactjs typescript azure


【解决方案1】:

使用新的 React Hooks 编写功能性反应组件很简单。在下面的示例中,我使用useStateuseEffectuseState 钩子与基于类的 React 组件中的 this.state/this.setState 同义。 useEffect 钩子类似于componentDidMount+componentDidUpdate。它也可以是componentDidUnmount

代码的执行方式是从上到下。因为它是函数式的,所以它会运行一次并以useState 的参数中设置的默认状态进行渲染。它不会阻止从 useEffect 函数中的 API 获取数据。因此,您需要能够在没有数据的情况下处理加载。任何时候props.apiConfigprops.id 发生变化,组件都会重新渲染,所有useEffect 都会重新渲染。 如果props.apiConfigprops.id 在第一次运行后发生变化,它只会调用useEffect 唯一讨厌的部分是useEffect 不能是async 函数,所以你必须调用函数getDataWrapper 不使用await。当API接收到数据后,会将数据存储在state中,这会触发组件的重新渲染。

总结:

  1. 使用默认状态渲染一次
    • 呼叫useEffect,后者呼叫getDataWrapper
    • 返回初始值在useState中的组件
  2. 一旦API在useEffect/getDataWrapper函数中接收到数据,通过setState设置状态并将isLoading设置为false
  3. 使用setState 现在包含的更新值重新渲染组件
    • 避免使用useEffect 控制路径,因为useEffect 的第二个参数中的值没有改变。 (例如:props.apiConfig & props.id)。
import React, { useState, useEffect } from 'react';
import { getDataFromAPI } from './api';

const MyComponent = (props) => {

  const [state, useState] = useState({
    isLoading: true,
    data: {}
  });

  useEffect(() => {
    const getDataWrapper = async () => {
      const response = await getDataFromAPI(apiConfig, props.id);
      setState({
        isLoading: false,
        data: response
      });
    });

    getDataWrapper();
  }, [props.apiConfig, props.id]);


  if(state.isLoading) { return <div>Data is loading from API...</div>

  return (
    <div>
      <h1>Hello, World!</h1>
      <pre>{JSON.stringify(state.data, null, 2)}</pre>
    </div>
  );
};

export default MyComponent;

【讨论】:

  • 谢谢,我再次测试,但它适用于简单的 HTML 标签,例如:{this.state.currentState} {this.props.pdata} 但不适用于:azure-devops-ui/Filter
  • azure-devops-ui/Filter 不起作用是什么意思?您尝试通过 JSX 输出的变量中是否没有数据?
  • 我也尝试过 this.state.filterStore &lt;Filter filterStore={this.state.filterStore} filterItems={this.filterItems} items={this.provider} /&gt; 但仅在简单的 HTML 标签上更新状态是更新的,但不是 UI,只是在过滤器上。
  • 您从 API 接收的数据是否与 Azure Dev Ops 过滤器的 props 格式匹配?显然,既然您要让它在普通 HTML 字段中正确更新,那么您将数据传递到 Filter 组件的方式或 Filter 组件本身有什么问题吗?您是否验证过您传入的内容与过滤器组件的documentation 匹配?
  • 更新 WIT 时,我已将项目预先保存在字段中,以便在 WIT 加载期间使用异步功能进行预检查:workItemFormService.getFieldValue(SDK.getConfiguration().witInputs.FieldName); 预检查过滤器 UI 上的元素使用构造函数,(我也尝试过 onClick 操作):this.filterStore = new FilterStore( {defaultState: { groupItems: { value: [this.props.pdata,this.textoStore] }}} ); 我也尝试过使用this.state = { FilterStoreAux: FilterStore }; 使用:this.state.FilterStoreAux`
猜你喜欢
  • 1970-01-01
  • 2019-11-20
  • 2016-06-09
  • 2021-04-19
  • 2016-06-19
  • 1970-01-01
  • 2020-03-18
  • 2020-09-29
相关资源
最近更新 更多