Nuxt.js 基础入门教程

原文连接javascript

Vue 开发一个单页面应用,相信不少前端工程师都已经学会了,可是单页面应用有一个致命的缺点,就是 SEO 极不友好。除非,vue 能在服务端渲染(ssr)并直接返回已经渲染好的页面,而并不是只是一个单纯的 <div id="app"></div>css

Nuxt.js 就是一个极简的 vue 版的 ssr 框架。基于它,咱们能够快速开发一个基于 vue 的 ssr 单页面应用。html

安装

Nuxt.js 官方提供了一个模板,可使用 vue-cli 直接安装。前端

$ vue init nuxt-community/starter-template <project-name>

目录结构

.
├── README.md
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── nuxt.config.js
├── package.json
├── pages
├── plugins
├── static
├── store
└── yarn.lock

其中:vue

  1. assets: 资源文件。放置须要通过 webpack 打包处理的资源文件,如 scss,图片,字体等。
  2. components: 组件。这里存放在页面中,能够复用的组件。
  3. layouts: 布局。页面都须要有一个布局,默认为 default。它规定了一个页面如何布局页面。全部页面都会加载在布局页面中的 <nuxt /> 标签中。若是须要在普通页面中使用下级路由,则须要在页面中添加 <nuxt-child />该目录名为Nuxt.js保留的,不可更改。
  4. middleware: 中间件。存放中间件。能够在页面中调用: middleware: 'middlewareName'
  5. pages: 页面。一个 vue 文件即为一个页面。index.vue 为根页面。java

    1. 若须要二级页面,则添加文件夹便可。
    2. 若是页面的名称相似于 _id.vue (以 _ 开头),则为动态路由页面,_ 后为匹配的变量(params)。
    3. 若变量是必须的,则在文件夹下创建空文件 index.vue。更多的配置请移步至 官网
  6. plugin: 插件。用于组织那些须要在 根vue.js应用 实例化以前须要运行的 Javascript 插件。须要注意的是,在任何 Vue 组件的生命周期内, 只有 beforeCreatecreated 这两个钩子方法会在 客户端和服务端均被调用。其余钩子方法仅在客户端被调用。
  7. static: 静态文件。放置不须要通过 webpack 打包的静态资源。如一些 js, css 库。
  8. store: 状态管理。具体使用请移步至 官网
  9. nuxt.config.js: nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。具体配置请移步至 官网

Nuxt 特有函数

首先,了解一下在 nuxt 的页面中独有的函数/变量:node

asyncData(context)

asyncData方法使得你可以在渲染组件以前异步获取数据。该方法在服务端中执行的,因此,请求数据时,不存在跨域问题。返回的数据将与 data() 返回的数据进行合并。因为asyncData方法是在组件 初始化 前被调用的,因此在方法内是没有办法经过 this 来引用组件的实例对象。webpack

context 变量的可用属性一览:ios

属性字段 类型 可用 描述
isClient Boolean 客户端 & 服务端 是否来自客户端渲染
isServer Boolean 客户端 & 服务端 是否来自服务端渲染
isDev Boolean 客户端 & 服务端 是不是开发(dev) 模式,在生产环境的数据缓存中用到
route vue-router 路由 客户端 & 服务端 vue-router 路由实例。
store vuex 数据流 客户端 & 服务端 Vuex.Store 实例。只有vuex 数据流存在相关配置时可用。
env Object 客户端 & 服务端 nuxt.config.js 中配置的环境变量, 见 环境变量 api
params Object 客户端 & 服务端 route.params 的别名
query Object 客户端 & 服务端 route.query 的别名
req http.Request 服务端 Node.js API 的 Request 对象。若是 nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用
res http.Response 服务端 Node.js API 的 Response 对象。若是 nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用
redirect Function 客户端 & 服务端 用这个方法重定向用户请求到另外一个路由。状态码在服务端被使用,默认 302。redirect([status,] path [, query])
error Function 客户端 & 服务端 用这个方法展现错误页:error(params)params 参数应该包含 statusCodemessage 字段。

fetch(context)

fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法相似,不一样的是它不会设置组件的数据。为了让获取过程能够异步,你须要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再渲染组件。git

fetch 会在组件每次加载前被调用(在服务端或切换至目标路由以前)。

head

Nuxt.js 使用了 vue-meta 更新应用的 头部标签(Head)html 属性

用于更新 头部信息。如 title,descripe 等。head 方法里可经过 this 关键字来获取组件的数据。

layout

指定该页面使用哪一个布局文件。默认值为 default

middleware

须要执行的中间件,如鉴权的 auth等。

transition

指定页面切换时的动画效果。支持传入 String, Object, Function。具体配置请移步至 官网

validate

Nuxt.js 可让你在动态路由对应的页面组件中配置一个校验方法用于校验动态路由参数的有效性。

返回 true 说明路由有效,则进入路由页面。返回不是 true 则显示 404 页面。

Begin Coding

前置工做

API

在这里,咱们使用 CNode API 进行开发 Demo.

axios

请求数据,咱们使用 Nuxt 官方提供的 @nuxtjs/axios 安装后,在 nuxt.config.js 中加上:

export default {
  ...
  modules: [
    '@nuxtjs/axios'
  ],
  axios: {
    baseURL: 'https://cnodejs.org/api/v1',
    // or other axios configs.
  }
  ...
}

就能够在页面中经过 this.$axios.$get 来获取数据,不须要在每一个页面都单独引入 axios.

scss

须要先安装 sass-loader 和 node-sass

$    yarn add sass-loader node-sass --dev

若是须要在项目中全局使用某个 scss 文件(如 mixins, vars 等),须要借助 sass-resources-loader : yarn add sass-resources-loader —dev, 还须要在 nuxt.config.js 的 build 配置中调整导出的 loader 配置:

export default {
  ...
  build: {
    extend(config, { isDev, isClient }) {
      const sassResourcesLoader = {  
        loader: 'sass-resources-loader',  
        options: {  
          resources: [
            // 填写须要全局注入 scss 的文件。引入后,全部页面均有效。
            'assets/styles/mixins.scss'  
          ]
        }  
      }
      // 修改 scss sass 引用的 loader。
      config.module.rules.forEach((rule) => {  
        if (rule.test.toString() === '/\\.vue$/') {  
          rule.options.loaders.sass.push(sassResourcesLoader)  
          rule.options.loaders.scss.push(sassResourcesLoader)  
        }  
        if (['/\\.sass$/', '/\\.scss$/'].indexOf(rule.test.toString()) !== -1) {  
          rule.use.push(sassResourcesLoader)  
        }  
      })  
    }
  }
  ...
}

首页

首页通常只须要简单的获取首页数据并渲染便可。

主要 代码:

asyncData({app, query}) {
  console.log(query)
  // 根据不用的标签获取不一样的数据,最后返回话题列表。
  return app.$axios.$get(`topics?tab=${query.tab || ''}`).then(res => {
    // console.log(res)
    // console.log(JSON.parse(res))
    return {list: res.data}
  })
}

当进入首页时,该函数会被执行, nuxt 会等到获取数据后再和组件的 data 合并,进而渲染数据。在模板中,能够直接使用 list 变量获取数据。

<div class="card fluid topic" v-for="topic in list" :key="topic.id" >
  <div class="section">
    <h3><nuxt-link :to="{name: 'topic-id', params: {id: topic.id}}" class="topic-title">{{topic.title}}</nuxt-link></h3>
    <p class="topic-info">
      <mark v-if="topic.top" class="tertiary">精华</mark>
      <mark v-else>{{tabsObj[topic.tab]}}</mark>
      <span class="avatar">
        <img :src="topic.author.avatar_url" alt="">
      </span>
      <span class="username">
        {{topic.author.loginname}}
      </span>
    </p>
  </div>
</div>

在这里说起一下, <nuxt-link /><a /> 的区别是: nuxt-link 走的是 vue-router 的路由,即网页已为单页面,而且浏览器不会重定向。而 a 标签走的是 window.location.href,每一次点击 a 标签后的页面,都会进行一次服务端渲染,和普通的 PHP 混合开发没有太大的区别。

在这里使用了 nuxt-link 是由于 CNode 的 API 不存在跨域问题,所以能够做为一个单页面应用,体验更好。

由于列表页数据类型有多种,该页面可能会被复用,因此当路由对象发生变化时,须要从新获取数据,这时能够监听路由的变化以作出响应:

watch: {
  '$route': function() {
    console.log('$route has changed.')
    this.getData()
  }
}

配置 seo 优化(这里只是单纯的复制罢了,demo 使用,侵删):

head() {
  return {
    title: '首页' + (this.$route.query.tab ? `- ${this.tabsObj[this.$route.query.tab]}` : ''),
    meta: [{
      hid: 'description',
      name: 'description',
      content: 'CNode:Node.js专业中文社区'
    }]
  }
}

话题详情

一样的,使用 asyncData 函数进行获取数据,再渲染页面。

asyncData({app, params}) {
  console.log(params)
  return app.$axios.$get('topic/' + params.id).then(res => {
    // let data = res.data instanceof String ? JSON.parse(res.data) : res.data
    let data = res.data
    // console.log(res)
    // let div = document.createElement('div')
    // div.innerHTML = res.data.data.content
    // res.data.summary = div.innerText.substr(0, 120)
    data.summary = data.content.replace(/<[^>]+>/g,"").substr(0, 120).replace(/\s+/g, '')
    return {detail: data}
  }).catch(err => {
    console.log('axios.get failed.')
    console.error(err)
  })
}

在这里,踩过坑。想使用 div 的 innerText 来过滤掉正文中的 HTML 标签,可是,若是用户是直接进入这个页面的时候,执行 asyncData 时,document 对象是不存在的,从而会报错。也就是说,asyncData 在服务端执行时,是没有 documentwindow 对象的,请你们注意一下。

做为一个社区,seo 尤其重要,假若每一个页面都须要写一大堆的 head 对象,就会显得尤为的繁琐。因此能够借助 nuxt 的 plugin 机制,将其封装成一个函数,并注入到每个页面当中:

// plugins/global.js
import Vue from 'vue'

Vue.mixin({
  methods: {
    // 必传 标题,描述。其余的 meta 标签经过 payload 注入,其中,每一个 meta 的 hid 须要是惟一的。
    $seo(title, content, payload = []) {
      return {
        title,
        meta: [{
          hid: 'description',
          name: 'description',
          content
        }].concat(payload)
      }
    }
  }
})

在 nuxt.config.js 中加上:

export default {
  plugins: [
    '~plugins/global.js'
  ]
}

这样,只须要在页面的 head 的函数中,返回该函数便可:

head() {
    return this.$seo(this.detail.title, this.detail.summary)
}

详情页 seo

可见,详情页已经成功的设置了部分 seo 的标签。

以上是 Nuxt 的一些基础配置及应用。

我再去研究一下, fetch 和 store 的结合,将该 demo 继续完善。

Demo 线上地址
GitHub 地址

相关文章
相关标签/搜索