weexteam / article

This repos is a third party collection, and is not developed nor maintained by Apache Weex.
1.22k stars 141 forks source link

一个简单的长列表开发教程 #111

Open yundongbot opened 7 years ago

yundongbot commented 7 years ago

一个简单的长列表开发教程

长列表展示是一个常见需求,下面我们开发一个列表页面展示 Github star 数最高的 JavaScript 项目并支持常见的上拉加载、回到顶部等功能。

我们先来看一下我们要实现的效果:

mobile_preview

预备篇: Github API

获取 Github 数据需要使用 Github API,本例中,我们使用到 search repositories API。我们根据 q=language:javascript&sort=stars&order=desc 这几个参数能够取到一段 JSON 数据,内容是按 star 数排序的 JavaScript 项目。由于数据太大,Github 支持分页返回数据,只需带上 page 参数即可。

数据非常丰富,但此处我们仅需要三个字段:

第1步:编写基本代码

我们要在每个 <cell> 中分别展示 repo 的全称和 star 数。基本代码如下:

<template>
  <div class="wrapper">
    <list class="list">
      <cell class="row" repeat="item in items" id="item-{{$index}}">
        <div>
          <text class="item">Repo name: {{item.full_name}}</text>
        </div>
        <div>
          <text class="item">Repo star: {{item.stargazers_count}}</text>
        </div>
      </cell>
    </list>
  </div>
</template>

<style>
.wrapper {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.list{
  flex: 1;
}
.row {
  padding: 20;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
</style>

<script>
module.exports = {
  data: {
    items:[]
  },
  methods: {}
}
</script>

这里有一点需要注意,对于内嵌的 <list>,必须有一个可计算的高度,你可以显示的指定高度,也可以使用 positionflex 来限制高度。由于我们的列表是全屏显示,我们使用 position 将列表设置为全屏展示。

第2步:请求数据

此时,items 为空数组,无任何数据,我们需要发起异步请求获取数据,并将其填充到 items 数组中。

这里,我们会使用到 stream 模块,此模块为我们提供了常用的网络请求 API,我们会用到 fetch。具体信息请查阅 fetch

我们在全局下引入 stream 模块,在 created 生命周期内发起异步请求获取第一页数据。此处,我们选择在 created 生命周期,原因在于在 created 生命周期内,页面还未开始渲染,我们能够更早发起请求。

我们将发起请求封装为一个方法 renderData(),以便重用。

<template>
  <div class="wrapper">
    <list class="list">
      <cell class="row" repeat="item in items" id="item-{{$index}}">
        <div>
          <text class="item">Repo name: {{item.full_name}}</text>
        </div>
        <div>
          <text class="item">Repo star: {{item.stargazers_count}}</text>
        </div>
      </cell>
    </list>
  </div>
</template>

<style>
.wrapper {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.list{
  flex: 1;
}
.row {
  padding: 20;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
</style>

<script>
var stream = require('@weex-module/stream') || {}

var SEARCH_URL = 'https://api.github.com/search/repositories?q=language:javascript&sort=stars&order=desc'
module.exports = {
  data: {
    page: 1,
    items:[]
  },
  created: function () {
    var url = SEARCH_URL + '&page=' + this.page

    this.renderData(url)

    this.page++
  },
  methods: {
    renderData: function (url) {
      var self = this

      stream.fetch({
        method: 'GET',
        url: url,
        type:'json'
      }, function(res) {
        try {
          var results = res.data.items || []

          if (Array.isArray(results)) {
            for(var i = 0; i < results.length; i++) {
              self.items.push(results[i])
            }
          }
        } catch(e) {}
      },function(res){

      })
    }
  }
}
</script>

第3步:顶部吸附

我们为页面提供一个吸附在顶部的标题,以便提示页面内容:

<cell> 前插入 <header> 标签并添加样式。

<template>
  <div class="wrapper">
    <list class="list">
      <header class="header">
        <text class="title">Search Results</text>
      </header>
      <cell class="row" repeat="item in items" id="item-{{$index}}">
        <div>
          <text class="item">Repo name: {{item.full_name}}</text>
        </div>
        <div>
          <text class="item">Repo star: {{item.stargazers_count}}</text>
        </div>
      </cell>
    </list>
  </div>
</template>

<style>
.header {
  padding: 25;
  background-color: #efefef;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
.title {
  text-align: center;
}
.wrapper {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.list{
  flex: 1;
}
.row {
  padding: 20;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
</style>

<script>
var stream = require('@weex-module/stream') || {}

var SEARCH_URL = 'https://api.github.com/search/repositories?q=language:javascript&sort=stars&order=desc'
module.exports = {
  data: {
    page: 1,
    items:[]
  },
  created: function () {
    var url = SEARCH_URL + '&page=' + this.page

    this.renderData(url)

    this.page++
  },
  methods: {
    renderData: function (url) {
      var self = this

      stream.fetch({
        method: 'GET',
        url: url,
        type:'json'
      }, function(res) {
        try {
          var results = res.data.items || []

          if (Array.isArray(results)) {
            for(var i = 0; i < results.length; i++) {
              self.items.push(results[i])
            }
          }
        } catch(e) {}
      },function(res){

      })
    }
  }
}
</script>

第4步:上拉加载

我们添加上拉加载功能以便用户查看更多 repo:

  1. <cell> 后插入 <loading> 组件;
  2. <loading> 组件添加 loading 事件;
  3. 此事件会调用 loadingData() 方法,获取下一页数据;我们在上拉时显示 <loading>,加载出数据后将其隐藏;
  4. 我们将列表限制为 15 页,如果页数大于 15,将文案改为“NO MORE”。
<template>
  <div class="wrapper">
    <list class="list">
      <header class="header">
        <text class="title">Search Results</text>
      </header>
      <cell class="row" repeat="item in items" id="item-{{$index}}">
        <div>
          <text class="item">Repo name: {{item.full_name}}</text>
        </div>
        <div>
          <text class="item">Repo star: {{item.stargazers_count}}</text>
        </div>
      </cell>
      <loading onloading="loadingData" style="width: 750; padding: 30;" display="{{loadingDisplay}}">
        <text class="text">{{loadingText}}</text>
      </loading>
    </list>
  </div>
</template>

<style>
.header {
  padding: 25;
  background-color: #efefef;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
.title {
  text-align: center;
}
.text {
  text-align: center;
}
.wrapper {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.list{
  flex: 1;
}
.row {
  padding: 20;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
</style>

<script>
var stream = require('@weex-module/stream') || {}

var SEARCH_URL = 'https://api.github.com/search/repositories?q=language:javascript&sort=stars&order=desc'
module.exports = {
  data: {
    isLoaded: true,
    page: 1,
    loadingDisplay: 'hide',
    loadingText: 'Loading...',
    items:[]
  },
  created: function () {
    var url = SEARCH_URL + '&page=' + this.page

    this.renderData(url)

    this.page++
  },
  methods: {
    renderData: function (url) {
      var self = this

      stream.fetch({
        method: 'GET',
        url: url,
        type:'json'
      }, function(res) {
        self.loadingDisplay = 'hide'

        try {
          var results = res.data.items || []

          if (Array.isArray(results)) {
            for(var i = 0; i < results.length; i++) {
              self.items.push(results[i])
            }
          }
        } catch(e) {}
      },function(res){

      })
    },
    loadingData: function (e) {
      var url = SEARCH_URL + '&page=' + this.page
      var self = this

      if (self.isLoaded === false) return 

      self.loadingDisplay = 'show'

      if (self.page <=10 ) {
        self.renderData(url)
        self.page++
      } else {
        self.loadingDisplay = 'hide'
        self.loadingText = 'NO MORE!'
      }
    }
  }
}
</script>

第5步:回到顶部

点击回到顶部按钮将会滚动到第一个 <cell>,这一步,我们会用到以下几个 API:

我们按如下步骤编写代码:

  1. <list> 组件后插入按钮,将其固定在右下角;
  2. 为按钮添加 click 事件;
  3. 点击时,通过 goToTop() 方法回到顶部。
<template>
  <div class="wrapper">
    <list class="list">
      <header class="header">
        <text class="title">Search Results</text>
      </header>
      <cell class="row" repeat="item in items" id="item-{{$index}}">
        <div>
          <text class="item">Repo name: {{item.full_name}}</text>
        </div>
        <div>
          <text class="item">Repo star: {{item.stargazers_count}}</text>
        </div>
      </cell>
      <loading onloading="loadingData" style="width: 750; padding: 30;" display="{{loadingDisplay}}">
        <text class="text">{{loadingText}}</text>
      </loading>
    </list>
    <div class="up" onclick="goToTop">
      <img class="img" src="https://img.alicdn.com/tps/TB1ZVOEOpXXXXcQaXXXXXXXXXXX-200-200.png"></img>
    </div>
  </div>
</template>

<style>
.header {
  padding: 25;
  background-color: #efefef;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
.title {
  text-align: center;
}
.text {
  text-align: center;
}
.wrapper {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.list{
  flex: 1;
}
.row {
  padding: 20;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
.up {
  width: 70;
  height: 70;
  position: fixed;
  right: 20;
  bottom: 20;
}
.img {
  width: 70;
  height: 70;
}
</style>

<script>
var dom = require('@weex-module/dom') || {}
var stream = require('@weex-module/stream') || {}

var SEARCH_URL = 'https://api.github.com/search/repositories?q=language:javascript&sort=stars&order=desc'
module.exports = {
  data: {
    isLoaded: true,
    page: 1,
    loadingDisplay: 'hide',
    loadingText: 'Loading...',
    items:[]
  },
  created: function () {
    var url = SEARCH_URL + '&page=' + this.page

    this.renderData(url)

    this.page++
  },
  methods: {
    renderData: function (url) {
      var self = this

      stream.fetch({
        method: 'GET',
        url: url,
        type:'json'
      }, function(res) {
        self.loadingDisplay = 'hide'

        try {
          var results = res.data.items || []

          if (Array.isArray(results)) {
            for(var i = 0; i < results.length; i++) {
              self.items.push(results[i])
            }
          }
        } catch(e) {}
      },function(res){

      })
    },
    loadingData: function (e) {
      var url = SEARCH_URL + '&page=' + this.page
      var self = this

      if (self.isLoaded === false) return

      self.loadingDisplay = 'show'

      if (self.page <=10 ) {
        self.renderData(url)
        self.page++
      } else {
        self.loadingDisplay = 'hide'
        self.loadingText = 'NO MORE!'
      }
    },
    goToTop: function (e) {
      dom.scrollToElement(this.$el('item-0'), {
        offset: -100
      })
    }
  }
}
</script>

第6步:下拉刷新

为兼容所有版本,我们在 <header> 组件后插入 <refresh> 组件,并添加 refresh 事件调用 renderData() 重新加载数据。这里和使用 <loading> 类似。

当用户下拉刷新时,我们弹出一个 toast 提醒用户页面正在刷新数据。这里用到了 modal module 的 toast 方法。详情参考 modal module

<template>
  <div class="wrapper">
    <list class="list">
      <header class="header">
        <text class="title">Search Results</text>
      </header>
      <refresh style="width: 750; padding: 30;" onrefresh="refreshData" display="{{refreshDisplay}}">
        <text class="text"> ↓ Pull to refresh </text>
        <loading-indicator class="indicator"></loading-indicator>
      </refresh>
      <cell class="row" repeat="item in items" id="item-{{$index}}">
        <div>
          <text class="item">Repo name: {{item.full_name}}</text>
        </div>
        <div>
          <text class="item">Repo star: {{item.stargazers_count}}</text>
        </div>
      </cell>
      <loading onloading="loadingData" style="width: 750; padding: 30;" display="{{loadingDisplay}}">
        <text class="text">{{loadingText}}</text>
      </loading>
    </list>
    <div class="up" onclick="goToTop">
      <img class="img" src="https://img.alicdn.com/tps/TB1ZVOEOpXXXXcQaXXXXXXXXXXX-200-200.png"></img>
    </div>
  </div>
</template>

<style>
.header {
  padding: 25;
  background-color: #efefef;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
.title {
  text-align: center;
}
.text {
  text-align: center;
}
.wrapper {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.list{
  flex: 1;
}
.row {
  padding: 20;
  border-bottom-color: #eeeeee;
  border-bottom-width: 2;
  border-bottom-style: solid;
}
.up {
  width: 70;
  height: 70;
  position: fixed;
  right: 20;
  bottom: 20;
}
.img {
  width: 70;
  height: 70;
}
</style>

<script>
var dom = require('@weex-module/dom') || {}
var modal = require('@weex-module/modal') || {}
var stream = require('@weex-module/stream') || {}

var SEARCH_URL = 'https://api.github.com/search/repositories?q=language:javascript&sort=stars&order=desc'
module.exports = {
  data: {
    isLoaded: true,
    page: 1,
    loadingDisplay: 'hide',
    refreshDisplay: 'hide',
    loadingText: 'Loading...',
    items:[]
  },
  created: function () {
    var url = SEARCH_URL + '&page=' + this.page

    this.renderData(url)

    this.page++
  },
  methods: {
    renderData: function (url) {
      var self = this

      stream.fetch({
        method: 'GET',
        url: url,
        type:'json'
      }, function(res) {
        self.refreshDisplay = 'hide'
        self.loadingDisplay = 'hide'

        try {
          var results = res.data.items || []

          if (Array.isArray(results)) {
            for(var i = 0; i < results.length; i++) {
              self.items.push(results[i])
            }
          }
        } catch(e) {}
      },function(res){

      })
    },
    loadingData: function (e) {
      var url = SEARCH_URL + '&page=' + this.page
      var self = this

      if (self.isLoaded === false) return 

      self.loadingDisplay = 'show'

      if (self.page <=10 ) {
        self.renderData(url)
        self.page++
      } else {
        self.loadingDisplay = 'hide'
        self.loadingText = 'NO MORE!'
      }
    },
    goToTop: function (e) {
      dom.scrollToElement(this.$el('item-0'), {
        offset: -100
      })
    },
    refreshData: function (e) {
      var url = SEARCH_URL + '&page=1'

      if (this.isLoaded === false) return 

      this.refreshDisplay = 'show'

      modal.toast({
        'message': 'Refreshing...', 
        'duration': 1
      })

      this.items = []
      this.page = 1
      this.renderData(url)

      this.refreshDisplay = 'hide'
    }
  }
}
</script>

体验一下

Genng commented 7 years ago

使用android集成的方式,提示没有这个方法 fetch.. 是怎么回事呀。 直接weex xxx.we 然后将js文件复制到项目里的...

yundongbot commented 7 years ago

@Genng 看一下 sdk 里是否集成了 stream 模块

Genng commented 7 years ago

@DoranYun
image sdk里面有的,是不是安卓集成的时候,还需要初始化什么,或者we的语法不对

yundongbot commented 7 years ago

语法你可以参考我上面的例子。

gitshuang commented 7 years ago

厉害

Louis2014 commented 7 years ago

求git下载地址