用Vue3,React,Angular分别写一个简单的列表组件示例

73 阅读2分钟

用React来编写:

使用 React 来实现一个类似的滚动加载列表组件。我们将创建一个名为 ScrollableList 的组件。


import React, { useState, useEffect } from 'react';

import './ScrollableList.css';

const ScrollableList = ({ columns, fetchData }) => {

  const [data, setData] = useState([]);

  const [loading, setLoading] = useState(false);

  const [hasMore, setHasMore] = useState(true);

  const loadMoreData = async () => {

    if (loading || !hasMore) return;

    setLoading(true);

    try {

      const newData = await fetchData();

      setData(prevData => [...prevData, ...newData]);

      setHasMore(newData.length > 0);

    } catch (error) {

      console.error('Error fetching data:', error);

    } finally {

      setLoading(false);

    }

  };

  // Initial data load

  useEffect(() => {

    loadMoreData();

  }, []);

  const renderRow = (rowData, index) => {

    return (

      <div key={index} className="list-row">

        {columns.map((column, columnIndex) => (

          <div key={columnIndex} className="list-cell">

            {rowData[column]}

          </div>

        ))}

      </div>

    );

  };

  const handleScroll = (e) => {

    const { scrollTop, clientHeight, scrollHeight } = e.target;

    if (scrollHeight - scrollTop === clientHeight) {

      loadMoreData();

    }

  };

  return (

    <div className="scrollable-list" onScroll={handleScroll}>

      <div className="list-header">

        {columns.map((column, index) => (

          <div key={index} className="list-header-cell">

            {column}

          </div>

        ))}

      </div>

      <div className="list-body">

        {data.map((rowData, index) => renderRow(rowData, index))}

        {loading && <div className="loading">Loading...</div>}

        {!hasMore && <div className="end-message">No more items</div>}

      </div>

    </div>

  );

};

export default ScrollableList;

然后,创建一个名为 ScrollableList.css 的 CSS 文件来定义列表组件的样式:


.scrollable-list {

  width100%;

  max-height300px;

  overflow-y: auto;

}

.list-header {

  display: flex;

  border-bottom1px solid #ccc;

  font-weight: bold;

}

.list-header-cell {

  flex1;

  padding8px;

  text-align: center;

  border-right1px solid #ccc;

}

.list-body {

  display: flex;

  flex-direction: column;

}

.list-row {

  display: flex;

  border-bottom1px solid #eee;

}

.list-cell {

  flex1;

  padding8px;

  text-align: center;

  border-right1px solid #eee;

}

.loading {

  padding10px;

  text-align: center;

}

.end-message {

  padding10px;

  text-align: center;

  color#999;

}

现在,我们来使用这个 ScrollableList 组件。在父组件中,你可以传递 columns 和 fetchData 方法给 ScrollableList


import React, { useState } from 'react';

import ScrollableList from './ScrollableList';

const App = () => {

  const [page, setPage] = useState(1);

  const fetchData = async () => {

    const response = await fetch(`https://api.example.com/data?page=${page}`);

    const newData = await response.json();

    setPage(prevPage => prevPage + 1);

    return newData;

  };

  const columns = ['Name''Age''Location']; // Customize columns here

  return (

    <div className="App">

      <h1>Scrollable List</h1>

      <ScrollableList columns={columns} fetchData={fetchData} />

    </div>

  );

};

export default App;

在这个示例中,我们假设 fetchData 函数从服务器获取数据,并且服务器端根据 page 参数返回不同的数据。你可以根据实际需求来自定义 fetchData 函数和 columns

这个 ScrollableList 组件会在滚动到底部时加载更多数据,直到服务器返回的数据为空为止。同时,根据传入的 columns 参数,会自动根据列数来渲染列表。

用Angular来编写

使用 Angular 来实现一个类似的滚动加载列表组件。我们将创建一个名为 ScrollableListComponent 的组件。

首先,创建 scrollable-list.component.ts 文件:


import { ComponentInputOnInitOutputEventEmitterViewChildElementRef } from '@angular/core';

@Component({

  selector'app-scrollable-list',

  templateUrl'./scrollable-list.component.html',

  styleUrls: ['./scrollable-list.component.css']

})

export class ScrollableListComponent implements OnInit {

  @Input() columnsstring[] = [];

  @Output() fetchDataEventEmitter<any> = new EventEmitter<any>();

  dataany[] = [];

  loadingboolean = false;

  hasMoreboolean = true;

  @ViewChild('scrollContainer') scrollContainer!: ElementRef;

  constructor() { }

  ngOnInit(): void {

    this.loadMoreData();

  }

  loadMoreData() {

    if (this.loading || !this.hasMorereturn;

    this.loading = true;

    this.fetchData.emit().subscribe((newData: any[]) => {

      this.data = [...this.data, ...newData];

      this.hasMore = newData.length > 0;

      this.loading = false;

    });

  }

  onScroll() {

    const scrollContainer = this.scrollContainer.nativeElement;

    if (scrollContainer.scrollTop + scrollContainer.clientHeight === scrollContainer.scrollHeight) {

      this.loadMoreData();

    }

  }

}

接着,创建 scrollable-list.component.html 文件:


<div class="scrollable-list" (scroll)="onScroll()" #scrollContainer>

  <div class="list-header">

    <div *ngFor="let column of columns" class="list-header-cell">

      {{ column }}

    </div>

  </div>

  <div class="list-body">

    <div *ngFor="let item of data; let i = index" class="list-row">

      <div *ngFor="let column of columns; let j = index" class="list-cell">

        {{ item[column] }}

      </div>

    </div>

    <div *ngIf="loading" class="loading">Loading...</div>

    <div *ngIf="!hasMore" class="end-message">No more items</div>

  </div>

</div>

最后,创建 scrollable-list.component.css 文件:


.scrollable-list {

  width100%;

  max-height300px;

  overflow-y: auto;

}

.list-header {

  display: flex;

  border-bottom1px solid #ccc;

  font-weight: bold;

}

.list-header-cell {

  flex1;

  padding8px;

  text-align: center;

  border-right1px solid #ccc;

}

.list-body {

  display: flex;

  flex-direction: column;

}

.list-row {

  display: flex;

  border-bottom1px solid #eee;

}

.list-cell {

  flex1;

  padding8px;

  text-align: center;

  border-right1px solid #eee;

}

.loading {

  padding10px;

  text-align: center;

}

.end-message {

  padding10px;

  text-align: center;

  color#999;

}

现在我们可以在父组件中使用这个 ScrollableListComponent


import { Component } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';

@Component({

  selector'app-root',

  template`

    <h1>Scrollable List</h1>

    <app-scrollable-list [columns]="columns" (fetchData)="fetchData($event)"></app-scrollable-list>

  `,

  styleUrls: ['./app.component.css']

})

export class AppComponent {

  columnsstring[] = ['Name''Age''Location'];

  pagenumber = 1;

  constructor(private http: HttpClient) {}

  fetchData(): Observable<any[]> {

    const url = `https://api.example.com/data?page=${this.page}`;

    return this.http.get<any[]>(url);

  }

}

在这个示例中,我们假设 fetchData 方法从服务器获取数据,并且服务器端根据 page 参数返回不同的数据。你可以根据实际需求自定义 fetchData 方法和 columns

这个 ScrollableListComponent 组件会在滚动到底部时加载更多数据,直到服务器返回的数据为空为止。同时,根据传入的 columns 参数,会自动根据列数来渲染列表。

子组件:this.fetchData.emit().subscribe((newData: any[]) => {

  this.data = [...this.data, ...newData];

  this.hasMore = newData.length > 0;

  this.loading = false;

});

父组件: 
<app-scrollable-list [columns]="columns" (fetchData)="fetchData($event)"></app-scrollable-list>

fetchData(): Observable<any[]> {

    const url = `https://api.example.com/data?page=${this.page}`;

    return this.http.get<any[]>(url);

  }
    

在这段代码中,this.fetchData.emit() 是通过输出属性 fetchData 发射一个事件,将这个事件传递给了父组件。在父组件中,这个事件被绑定到了一个方法 fetchData() 上。所以实际上,this.fetchData.emit() 的作用是通知父组件调用 fetchData() 方法。

而在 subscribe((newData: any[]) => { ... }) 中,确实是在订阅了父组件中 fetchData() 方法返回的 Observable。在 fetchData() 方法中,发送了一个 HTTP GET 请求,获取了数据。当这个 HTTP 请求返回数据时,subscribe 中的回调函数会被执行,其中的 newData 就是从服务器返回的数据。

接着,在这个订阅回调函数中,代码做了以下几件事情:

- 将新获取的数据 newData 与已有的数据 this.data 合并,使用了展开运算符 [...this.data, ...newData],将它们连接成一个新的数组,这样就保留了之前已经加载的数据,并加上了新加载的数据。

- 根据新加载的数据 newData 的长度判断是否还有更多数据,如果 newData.length 大于 0,则说明还有数据可以加载,设置 this.hasMore 为 true,否则设置为 false

- 最后,将 loading 设置为 false,表示数据加载完成,不再处于加载状态。

用Vue3来编写

使用 Vue 3 来实现一个类似的滚动加载列表组件。我们将创建一个名为 ScrollableList 的组件。

首先,创建 ScrollableList.vue 文件:


<template>

  <div class="scrollable-list" @scroll="onScroll" ref="scrollContainer">

    <div class="list-header">

      <div v-for="column in columns" :key="column" class="list-header-cell">

        {{ column }}

      </div>

    </div>

    <div class="list-body">

      <div v-for="(item, index) in data" :key="index" class="list-row">

        <div v-for="column in columns" :key="column" class="list-cell">

          {{ item[column] }}

        </div>

      </div>

      <div v-if="loading" class="loading">Loading...</div>

      <div v-if="!hasMore" class="end-message">No more items</div>

    </div>

  </div>

</template>

<script>

import { ref, onMounted } from 'vue';

export default {

  props: {

    columns: {

      type: Array,

      required: true

    },

    fetchData: {

      type: Function,

      required: true

    }

  },

  setup(props) {

    const data = ref([]);

    const loading = ref(false);

    const hasMore = ref(true);

    const scrollContainer = ref(null);

    const loadMoreData = async () => {

      if (loading.value || !hasMore.value) return;

      loading.value = true;

      try {

        const newData = await props.fetchData();

        data.value = [...data.value, ...newData];

        hasMore.value = newData.length > 0;

      } catch (error) {

        console.error('Error fetching data:', error);

      } finally {

        loading.value = false;

      }

    };

    const onScroll = () => {

      const container = scrollContainer.value;

      if (container.scrollTop + container.clientHeight === container.scrollHeight) {

        loadMoreData();

      }

    };

    onMounted(() => {

      loadMoreData();

    });

    return {

      data,

      loading,

      hasMore,

      onScroll,

      scrollContainer

    };

  }

};

</script>

<style>

.scrollable-list {

  width: 100%;

  max-height: 300px;

  overflow-y: auto;

}

.list-header {

  display: flex;

  border-bottom: 1px solid #ccc;

  font-weight: bold;

}

.list-header-cell {

  flex: 1;

  padding: 8px;

  text-align: center;

  border-right: 1px solid #ccc;

}

.list-body {

  display: flex;

  flex-direction: column;

}

.list-row {

  display: flex;

  border-bottom: 1px solid #eee;

}

.list-cell {

  flex: 1;

  padding: 8px;

  text-align: center;

  border-right: 1px solid #eee;

}

.loading {

  padding: 10px;

  text-align: center;

}

.end-message {

  padding: 10px;

  text-align: center;

  color: #999;

}

</style>

现在我们可以在父组件中使用这个 ScrollableList 组件:


<template>

  <div class="app">

    <h1>Scrollable List</h1>

    <ScrollableList :columns="columns" :fetch-data="fetchData" />

  </div>

</template>

<script>

import { ref } from 'vue';

import ScrollableList from './components/ScrollableList.vue';

export default {

  components: {

    ScrollableList

  },

  setup() {

    const columns = ref(['Name', 'Age', 'Location']);

    const page = ref(1);

    const fetchData = async () => {

      const response = await fetch(`https://api.example.com/data?page=${page.value}`);

      const newData = await response.json();

      page.value += 1;

      return newData;

    };

    return {

      columns,

      fetchData

    };

  }

};

</script>

<style>

.app {

  display: flex;

  flex-direction: column;

  align-items: center;

}

</style>

在这个示例中,我们假设 fetchData 方法从服务器获取数据,并且服务器端根据 page 参数返回不同的数据。你可以根据实际需求自定义 fetchData 方法和 columns

这个 ScrollableList 组件会在滚动到底部时加载更多数据,直到服务器返回的数据为空为止。同时,根据传入的 columns 参数,会自动根据列数来渲染列表。