如何重构三个异步加载和显示数据的组件合二为一?

How to refactor three components, which asynchronously load and display data into one?

我有以下 TypeScript 代码。我simplified/remove尽我所能。

interface DataPullingPageState
{
  loading: boolean;
  displayedEntries: string[];
};

export class EntriesPageOne extends React.Component<{}, DataPullingPageState>
{
  constructor(props: any)
  {
    super(props);

    this.state = { loading: false, displayedEntries: [] };
  }

  async componentDidMount()
  {
    this.setState({ loading: true });

    const entries = await api.loadAll();

    this.setState({ loading: false, displayedEntries: entries });
  }

  render()
  {
    if (this.state.loading)
    {
      return <div>loading</div>;
    }
    else if (this.state.displayedEntries.length === 0)
    {
      return <div>nothing found</div>;
    }
    else
    {
      return this.state.displayedEntries.map((entry, i) => <div key={i}>{entry}</div>);
    }
  }
}

export class EntriesPageTwo extends React.Component<{}, DataPullingPageState>
{
  constructor(props: any)
  {
    super(props);

    this.state = { loading: false, displayedEntries: [] };
  }

  async componentDidMount()
  {
    this.setState({ loading: true });

    const param = "my param";
    const entries = await api.loadByStringParam(param);

    this.setState({ loading: false, displayedEntries: entries });
  }

  render()
  {
    if (this.state.loading)
    {
      return <div>loading</div>;
    }
    else if (this.state.displayedEntries.length === 0)
    {
      return <div>nothing found</div>;
    }
    else
    {
      return this.state.displayedEntries.map((entry, i) => <div key={i}>{entry}</div>);
    }
  }
}

export class EntriesPageThree extends React.Component<{}, DataPullingPageState>
{
  constructor(props: any)
  {
    super(props);

    this.state = { loading: false, displayedEntries: [] };
  }

  async componentDidMount()
  {
    this.setState({ loading: true });

    const param = 123;
    const entries = await api.loadByNumberParam(param);

    this.setState({ loading: false, displayedEntries: entries });
  }

  render()
  {
    if (this.state.loading)
    {
      return <div>loading</div>;
    }
    else if (this.state.displayedEntries.length === 0)
    {
      return <div>nothing found</div>;
    }
    else
    {
      return this.state.displayedEntries.map((entry, i) => <div key={i}>{entry}</div>);
    }
  }
}

如您所见,三个不同的组件都显示相同但具有三种不同的加载方式。

我想知道我如何才能从这三个组件中只制作一个组件。我已经听说过 HoC,但不知道它们是否适合我的情况。

是的,你可以 HoC 让我们稍微简化一下你的代码:

HoC 方法

class EntriesPage extends React.Component {
  // you don't need state for loading
  render() {
    const { loading, entries } = this.props
  }
}
EntriesPage.defaultProps = { loading: true, entries: [] }

const withEntries = (apiCall) => (Page) => {
  return async (props) => {
     const entries = await apiCall()
     <Page {...props} loading={false} entries={entries} />
  }
}

现在你可以这样排第一页了

// PageOne
export default withEntries(api.loadAll)(EntriesPage)
// PageTwo
export default withEntries(() => api.loadByStringParam('param'))(EntriesPage)
// PageThree
export default withEntries(() => api.loadByNumberParam(123))(EntriesPage)

这将创建接受动态获取方法的 HoC,并将结果作为 prop 传递给最终组件。希望这有帮助

以参数为 prop 的 Hoc 方法

您甚至可以通过将其更改为类似这样的内容来将参数公开给最终组件

const withEntries = (apiCall) => (Page) => {
  return async (props) => {
     const { fetchParam, ...rest } = props
     const entries = await apiCall(fetchParam)
     <Page {...rest} loading={false} entries={entries} />
  }
}

// EntriesPageComposed.tsx
export default withEntries(api.loadByStringParam)(EntriesPage)

<EntriesPageComposed fetchParams={123} />

加载程序组件

或者你甚至可以在没有 HoC 的情况下使其完全隔离,并将所有内容作为 prop 传递并制作 "data loader" 组件,这是 React 应用程序中非常常见的模式,它将仅充当准备下一个 props 的加载器。

const ComposedComponent = async (props) => {
  const { fetchMethod, fetchParam, ...rest } = props
  const entries = await fetchMethod(fetchParam)

  return (
    <EntriesPage {...rest} loading={false} entries={entries} />
  )
}


<ComposedComponent fetchMethod={api.loadByStringParam} fetchParam={'param'} />

通过这种方式,您可以隔离初始实现,并且只需传递一个 prop 即可动态添加新的获取方法。