如何重构三个组件,它们异步地将数据加载和显示为一个组件?

时间:2019-01-24 18:17:34

标签: javascript reactjs typescript abstraction

我有以下TypeScript代码。我已尽可能简化/删除了。

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,但不知道它们是否适合我的情况。

1 个答案:

答案 0 :(得分:0)

是的,您可以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传递给最终组件。希望这会有所帮助

以param为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传递并制作“数据加载器”组件,这在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来动态添加新的获取方法。