使用 Nuxt.js 搭建前端页面

滔哥 2021年02月27日 94次浏览

认识Nuxt.js,了解它的适用场景和优缺点;学会如何搭建一个基于Nuxt.js的项目,并了解它的基本用法。

知识点

  • 认识 Nuxt.js 是做什么的,了解为什么要用它
  • 如何创建 Nuxt.js 项目
  • Nuxt.js 的项目结构
  • Nuxt.js 基础路由

预备知识

  • npm
  • vue.js

Nuxt.js 十分简单易用。一个简单的项目只需将 nuxt 添加为依赖组件即可。为了快速入门,Nuxt.js 团队创建了脚手架工具create-nuxt-app
开始之前请先确认我们的环境,Nuxt.js 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。你可以使用 nvm 或 nvm-windows 在同一台电脑中管理多个 Node 版本。
环境确认无误之后,我们开始创建 nuxt 项目吧。

开始创建项目

第一步:首先执行一下命令安装npx:

npm install -g npx

第二步:创建一个 Nuxt.js 项目只需要以下命令即可:

npx create-nuxt-app <项目名>

假设我们的项目名称为site,那么创建项目命令如下:

npx create-nuxt-app site

创建过程中该工具会引导填写项目名称、描述、作者、选择包管理方式、UI 框架等等等

修改 Nuxt.js 端口

Nuxt.js 的默认端口为3000,由于实验楼只对外开放了8080端口,所以我们要想看到演示效果需要将端口号修改为8080,打开nuxt.config.js添加如下配置:

server: {
  port: 8080, // default: 3000
  host: '0.0.0.0' // default: localhost
}

上一步我们将Nuxt.js的端口修改成了8080,那么接下来我们在site目录中执行以下命令来启动服务:

npm run dev

Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。

假设 pages 的目录结构如下:

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

那么,Nuxt.js 自动生成的路由配置如下:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

自定义组件

在做页面开发的时候经常会遇到相同的功能会在多个页面中用到,在每个页面中都加上相同的代码显然是不明智的,这么做会有两个问题:

代码冗余重复
如果这块功能发生改变,那么所有页面都得修改,不便维护
所以这里就需要用到组件,将相同功能封装到同一个组件中,页面中只需要引用该组件即可,如果功能发生变动也只需要修改该组件。

接下来通过一个实例来了解组件如何使用,例如下面的页面中有顶部导航和底部页脚,这两部分是在所有页面都需要用到的,所以我们将他们封装成组件。

未使用组件前的pages/index.vue页面代码如下:

<template>
  <section>
    <nav class="navbar is-light" role="navigation" aria-label="main navigation">
      <div class="container">
        <div class="navbar-brand">
          <a class="navbar-item" href="https://mlog.club">
            <img src="https://i.loli.net/2019/10/11/aAiXVBCNknJeYGD.png" />
          </a>
        </div>

        <div id="navbarBasicExample" class="navbar-menu">
          <div class="navbar-start">
            <a class="navbar-item">Home</a>
            <a class="navbar-item">Documentation</a>
          </div>

          <div class="navbar-end">
            <div class="navbar-item">
              <div class="buttons">
                <a class="button is-primary"><strong>Sign up</strong></a>
                <a class="button is-light">Log in</a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </nav>
    <div class="container">
      <div class="notification">正文内容</div>
    </div>
    <footer class="footer">
      <div class="content has-text-centered">
        <p>
          Powered by <a href="https://mlog.club"><strong>bbs-go</strong></a>
        </p>
      </div>
    </footer>
  </section>
</template>

<script>
export default {
  components: {},
};
</script>

<style>
.container {
  min-height: 300px;
}
</style>

首先我们先将导航条封装成组件,在项目的components目录中新建文件MyNav.vue内容如下:

<template>
  <nav class="navbar is-light" role="navigation" aria-label="main navigation">
    <div class="container">
      <div class="navbar-brand">
        <a class="navbar-item" href="https://mlog.club">
          <img src="https://i.loli.net/2019/10/11/aAiXVBCNknJeYGD.png" />
        </a>
      </div>

      <div id="navbarBasicExample" class="navbar-menu">
        <div class="navbar-start">
          <a class="navbar-item">Home</a>
          <a class="navbar-item">Documentation</a>
        </div>

        <div class="navbar-end">
          <div class="navbar-item">
            <div class="buttons">
              <a class="button is-primary"><strong>Sign up</strong></a>
              <a class="button is-light">Log in</a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </nav>
</template>

<style></style>

这样我们就新建了一个组件,接下来再修改pages/index.vue文件,将组件引入进来并使用,修改后的pages/index.vue页面代码如下:

<template>
  <section>
    <my-nav />
    <div class="container">
      <div class="notification">正文内容</div>
    </div>
    <footer class="footer">
      <div class="content has-text-centered">
        <p>
          Powered by <a href="https://mlog.club"><strong>bbs-go</strong></a>
        </p>
      </div>
    </footer>
  </section>
</template>

<script>
import MyNav from '~/components/MyNav';

export default {
  components: {
    MyNav,
  },
};
</script>

<style>
.container {
  min-height: 300px;
}
</style>

然后刷新页面会发现我们看到的效果和使用组件之前看到的效果是一样的。总结一下组件的使用步骤,主要为以下三步:

新建组件
在页面中通过import引入组件,例如:import MyNav from '~/components/MyNav'
以 html 标签形式使用组件,例如:<my-nav />
以此类推我们也可以将页脚部分封装成组件。

Vue.js 的组件还有很多的用法,例如组件参数、时间监听等等,详情可以查看这里:https://cn.vuejs.org/v2/guide/components.html

使用 Nuxt.js 请求接口数据并渲染

使用 Go 语言提供接口

新建server/main.go文件,在该文件代码中我们启动 http 服务,并提供一个返回当前时间的接口。完整代码如下:

package main

import (
    "time"

    "github.com/iris-contrib/middleware/cors"
    "github.com/kataras/iris"
)

func main() {
    app := iris.New()

    // 跨域配置
    app.Use(cors.New(cors.Options{
        AllowedOrigins:   []string{"*"}, // allows everything, use that to change the hosts.
        AllowCredentials: true,
        MaxAge:           600,
        AllowedMethods:   []string{iris.MethodGet, iris.MethodPost, iris.MethodOptions, iris.MethodHead, iris.MethodDelete, iris.MethodPut},
        AllowedHeaders:   []string{"*"},
    }))
    app.AllowMethods(iris.MethodOptions)

    app.Get("/api/json", func(ctx iris.Context) {
        ctx.JSON(iris.Map{"curTime": time.Now()})
    })

    app.Run(iris.Addr(":8081"), iris.WithoutServerError(iris.ErrServerClosed))
}

然后执行go run main.go启动服务,端口为:8081,接口/api/json返回数据格式如下:

{
  "curTime": "2019-09-29T18:24:03.4427+08:00"
}

使用 Axios 请求接口数据

Nuxt.js 扩展了 Vue.js,增加了一个叫asyncData的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。asyncData方法会在页面每次加载之前调用,你可以利用asyncData方法来获取数据,Nuxt.js 会将asyncData返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

bbs-go 是由 Go 语言提供jsonapi,然后由 Nuxt.js 通过asyncData调用jsonapi进行页面渲染的,这里我们使用 Nuxt.js的 axios 模块。在上一个实验中我们创建Nuxt.js项目的时候选择了安装axios模块,所以这里只需要做简单的配置即可。

Axios 配置代理默认

实验楼的webide只对外开放 8080 端口,所以我们希望 Go 语言提供的所有接口都可以从 8080 端口访问,这里就可以使用axios的代理功能。axios代理的配置需要我们修改文件site/nuxt.config.js,修改如下:

  ...

  /*
  ** Axios module configuration
  ** See https://axios.nuxtjs.org/options
  */
  axios: {
    proxy: true
  },
  proxy: {
    '/api/': 'http://localhost:8081/' // 要求axios代理8081端口
  }

  ...

代理功能的应用场景有很多,例如我的界面渲染需要从多个服务去拉去数据:

  axios: {
    proxy: true
  },

  proxy: {
    '/api/': 'http://api.example.com',
    '/api2/': 'http://api.another-website.com'
  }

Axios 请求接口

接下来我们改造页面,新增asyncData方法,在该方法中调用http://localhost:8081/json接口获取数据然后渲染到页面,改造后页面代码如下:

<template>
  <section>
    <my-nav />
    <div class="container">
      <div class="notification">当前时间:{{ curTime }}</div>
    </div>
    <my-footer />
  </section>
</template>

<script>
import MyNav from '~/components/MyNav';
import MyFooter from '~/components/MyFooter';

export default {
  components: {
    MyNav,
    MyFooter,
  },
  async asyncData({ $axios }) {
    const resp = await $axios.get('/api/json');
    return {
      curTime: resp.data.curTime,
    };
  },
};
</script>

<style>
.container {
  min-height: 300px;
}
</style>

动态路由

Nuxt.js路径结构是 restful 风格的,我们可以根据页面文件路径来定义带参数的路由。路由参数需要将文件夹或文件名称以下划线开头,即表示为动态参数。例如:
| 路由 | 目录结构 |
| ------------------------- | ------------------------- |
| /user/123 | /pages/user/_id.vue |
| /user/123/info | /pages/user/_id/info.vue |

在页面中我们可以通过Nuxt.js context中的params对象来获取路由参数。下面我们通过一个实例来学习和验证效果:

新增/pages/user/_id.vue文件,文件内容如下:

<template>
  <section>
    <my-nav />
    <div class="container">
      <div class="notification">当前数据编号:{{ curId }}</div>
    </div>
    <my-footer />
  </section>
</template>

<script>
import MyNav from '~/components/MyNav';
import MyFooter from '~/components/MyFooter';

export default {
  components: {
    MyNav,
    MyFooter,
  },
  async asyncData({ params }) {
    return {
      curId: params.id,
    };
  },
};
</script>

<style>
.container {
  min-height: 300px;
}
</style>

然后启动服务,并访问服务,路径为:/user/123,可以看到页面效果如下,页面中输出的当前数据编号:123即是从我们的路由参数中获取的。

页面布局

同一个网站,一般情况下顶部导航和底部等在所有页面中都是一样的,为避免重复代码, Nuxt.js 提供了布局功能,默认布局为default,我们也可以自定义布局文件,下面我们打开项目的layouts,去新建一个自定义布局:bbs。在layouts文件夹下新建文件bbs.vue,内容如下:

<template>
  <div>
    <div class="header">
      我是顶部
    </div>

    <nuxt />

    <div class="footer">
      我是底部
    </div>
  </div>
</template>

<script>
  export default {};
</script>

<style scoped>
  .header,
  .footer {
    font-size: 24px;
    color: red;
  }
</style>

上面我们定义了一个名为bbs的布局,下面我们将首页修改为该布局,编辑页面文件pages/index.vue,在代码中加上一行:layout: 'bbs',代码如下:

export default {
  layout: 'bbs',
  components: {
    Logo,
  },
};

做完以上,当前目录结构

.
├── server
│   ├── go.mod
│   ├── go.sum
│   └── main.go
└── site
    ├── README.md
    ├── assets
    │   └── README.md
    ├── components
    │   ├── Logo.vue
    │   ├── MyFooter.vue
    │   ├── MyNav.vue
    │   └── README.md
    ├── jsconfig.json
    ├── layouts
    │   ├── README.md
    │   └── default.vue
    ├── middleware
    │   └── README.md
    ├── nuxt.config.js
    ├── package-lock.json
    ├── package.json
    ├── pages
    │   ├── README.md
    │   ├── index.vue
    │   └── user
    │       └── _id.vue
    ├── plugins
    │   └── README.md
    ├── static
    │   ├── README.md
    │   └── favicon.ico
    └── store
        └── README.md