帶你用 Vue 全家桶和 Node.js 完成一個聚合應用

yck發表於2019-03-04

平時會經常瀏覽一些網站充電,但是老是需要切換網站也很麻煩,所以就有了做這個小專案的想法。通過爬蟲抓取一些網站,然後整合在一個應用中。雖然是個簡單應用,但是五臟六腑俱全,適合 Vue 的新手學習。

專案地址

專案技術棧

  • Vue 全家桶
  • 語言:ES6
  • UI:這裡使用了Element-ui,畢竟小專案,不花時間在 UI 上
  • 後臺:express + superagent + cheerio

後端

使用 express 做介面,在請求介面時,同時使用 superagent 請求對應的地址抓取資料

app.get(`/api/zaoduke`, function (req, res, next) {
  // 請求介面附帶的引數
  let page = req.query.page
  superagent.get(`https://toutiao.io/subjects/11907?f=new&page=${page}`)
    .end(function (err, sres) {
      ...
    });
});複製程式碼

然後通過 cheerio 解析返回的網頁原始碼 (類似 jqury) 的寫法,獲得自己需要的資料,然後通過 express 回傳

var $ = cheerio.load(sres.text)
      var items = []
      var data = {
        items: items,
        hasMore: true
      }
      if ($(`.post`).length < 30) {
        data.hasMore = false
      }
      $(`.post`).each(function (idx, element) {
        var $element = $(element)
        // cheerio 沒有 innterText 的方法,所以通過 nodeType 去取只屬於這個元素的文字
        var $author = $element.find(`.meta`).contents().filter(function () {
          return this.nodeType === 3;
        });
        // 這裡取資料的方式和jqury是一樣的
        items.push({
          title: $element.find(`.title>a`).text().trim(),
          href: `https://toutiao.io` + $element.find(`.title>a`).attr(`href`),
          author: $author.text().trim()
        })
      })

      res.send(data)複製程式碼

前端

路由相關

在 Vue 中使用外掛必須呼叫 Vue.use(xxx)

路由懶載入,當然小專案不做非同步也完全沒問題。當專案大了,可以使用這個功能分割不同路由元件,這個可以做到訪問才載入路由。

const Lists = type => () => import(`../page/Lists.js`).then(m => m.default(type))複製程式碼

因為主體內容樣式是一樣的,所以我通過使用不同 type 的方式來複用程式碼。

let router = new Router({ 
  // 想使用 scrollBehavior 必須用這個 mode
  mode: `history`,
  // 切換路由時內容滑動到底部
  scrollBehavior: () => ({
    y: 0
  }),
  routes: [{
    path: `/`,
    redirect: `/zaoduke`
  },
  // 以下就是通過不同型別的 type 去複用程式碼
  {
    path: `/raywenderlich/:page(\d+)?`,
    component: Lists(`raywenderlich`)
  },
  {
    path: `/csstricks/:page(\d+)?`,
    component: Lists(`csstricks`)
  }
  ...
  ]
})複製程式碼

專案使用 Vuex 去管理資料,但是當手動重新整理瀏覽器的時候,因為 Vuex 是全域性變數,所以變數被銷燬,不能獲取正確的資料了。有種方式是將 state 存到 localStorage 中,在這裡我使用了還有種方法。Vue route 有個全域性鉤子 beforeEach 他會在進入路由前呼叫。

router.beforeEach((to, from, next) => {
  // 去獲取路由當前的頁碼,用於請求資料
  store.state.page = to.params.page || 1
  // 獲取 type
  store.state.type = to.path.match(/[a-zA-Z0-9]+/)[0]
  // 這裡必須呼叫 next,否則進不了路由
  next()
})複製程式碼

接下來匯出 router 即可,到這裡路由部分結束。

export default router複製程式碼

Vuex

使用 Vuex 還是要注意下場景,畢竟使用這個外掛還是很繁瑣的。在這個專案中其實不使用 Vuex 也是可以的,直接使用瀏覽器內建的方式去管理資料即可。因為程式碼中 Vuex 用的很少,就一筆略過了

PS: 在使用 Vuex 和 Vue route 要注意必須在 Vue 的例項中傳入。

new Vue({
  el: `#app`,
  router,
  store,
  render: h => h(App)
})複製程式碼

Vue

先看一下複用元件的程式碼

// 內容元件
import List from `./ray.vue`

export default function createListView(type) {
  return {
    // 通過 type 渲染,這個 type 需要在 List 中的 props 定義
    render(h) {
      return h(List, {
        props: {
          type
        }
      })
    }
  }
}複製程式碼

List 元件中包含了三個子元件,分別為頂部進度條,分頁元件,單個 li 元件。具體的 HTML 和 CSS 程式碼大家可以自己看一下,沒有任何難度。

在頁面中請求資料的程式碼我選擇放在 mounted 中,因為只有在這個鉤子及以後才可以訪問到 $refs 例項

async getData(page = 1) {
      // 已經在載入了,就返回
      if (this.isLoad) {
        return
      }
      this.isLoad = true
      // 呼叫進度條的 start 方法
      this.$refs.progress.start()
      // 這種非同步請求方法寫起來簡單
      let data = await getRaywenderlichData(this.type, page)
      // 給資料賦值
      this.list = data.items
      // 判斷下一頁還有沒有資料,沒有的話禁用下一頁按鈕
      if (!data.hasMore) {
        this.noMore = true
      } else {
        this.noMore = false
      }
      this.isLoad = false
      this.$refs.progress.finish()
    }複製程式碼

子元件給父元件通訊

// 當點選上一頁或下一頁時呼叫
routerPush() {
      // 改變路由
      this.$router.push(`/${this.type}/${this.pageIndex}`)
      // 傳送訊息
      this.$emit(`changePage`, this.pageIndex)
    }
// 然後在父元件使用
<pageBreak class="pageBreak" 
      :type="type" 
      :isLoad="isLoad"
      :noMore="noMore" 
      @changePage="changePage">複製程式碼

後記

大家有興趣的可以在這個 issus 中回覆覺得不錯的網站,我會新增進專案。

如果在專案中發現了有什麼不解或者發現了 bug,歡迎提交 PR 或者 issue,歡迎大神們多多指點小弟???

專案地址,如果喜歡這個專案,歡迎 Star!

相關文章