3-3-nuxt.js实现 realworld-nuxtjs项目,同时学习部署在自己服务器上

=》测试 -bui9bi

NuxtJS

代码仓库地址:https://gitee.com/cloveryuan/realworld-nuxtjs

一、Nuxt.js是什么

  • 一个基于Vue.js生态的第三方开源服务端渲染应用框架
  • 它可以帮我们轻松的使用Vue.js技术栈构建同构应用
  • 官网:https://zh.nuxtjs.org/
  • Github仓库:https://github.com/nuxt/nuxt.js

二、Nuxt.js的使用方式

  • 初始化项目
  • 已有的Node.js服务端项目
    • 直接把Nuxt当做一个中间件集成到Node Web Server中
  • 现有的Vue.js项目
    • 非常熟悉Nuxt.js
    • 至少百分之10的代码改动

三、初始化Nuxt.js应用方式

官方文档:https://zh.nuxtjs.org/guide/installation

  • 方式一:使用create-nuxt-app
  • 方式二:手动创建

四、Nuxt.js路由

1. 基本路由

pages文件夹下的文件会自动生成路由

2. 路由导航
  • a标签

    • 它会刷新整个页面,不推荐使用
    • <a href="/">首页</a>
  • nuxt-link组件

    • https://router.vuejs.org/zh/api/#router-link-props
    • <router-link to="/">首页</router-link>
  • 编程式导航

    • https://router.vuejs.org/zh/guide/essentials/navigation.html

    • <button @click="onClick">首页</button>

      methods: {
             
        onClick () {
             
          this.$router.push('/')
        }
      }
      
3. 动态路由

user/_id.vue,动态路由参数文件名由下划线开头。

<template>
  <div>
    <h1>User page</h1>
    <p>{
  {$route.params.id}}</p>
  </div>
</template>

<script>
export default {
  name: 'UserPage'
}
</script>

<style scoped>

</style>
4. 嵌套路由

可以通过 vue-router 的子路由创建 Nuxt.js 应用的嵌套路由。创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。

Warning: 别忘了在父组件(.vue文件) 内增加 <nuxt-child/> 用于显示子视图内容。

在这里插入图片描述

4. 路由配置
  • 参考文档:https://zh.nuxtjs.org/api/configuration-router

  • 在项目根目录下创建nuxt.config.js

    /**
     * Nuxt.js 配置文件 nuxt.config.js
     */
    
     module.exports = {
         
       router: {
         
         base: '/abc',
         // routes就是路由配置表,是个数组,resolve是解析路由路径的
         extendRoutes(routes, resolve) {
         
          routes.push({
         
            name: 'custom',
            path: '*',
            component: resolve(__dirname, 'pages/404.vue')
          }),
          routes.push({
         
            name: 'hello',
            path: '/hello',
            component: resolve(__dirname, 'pages/about.vue')
          })
        }
       }
     }
    

五、Nuxt.js视图

在这里插入图片描述

1. 模板

你可以定制化 Nuxt.js 默认的应用模板。

定制化默认的 html 模板,只需要在 src 文件夹下(默认是应用根目录)创建一个 app.html 的文件。

默认模板为:

<!DOCTYPE html>
<html {
    {
     HTML_ATTRS }}>
  <head {
    {
     HEAD_ATTRS }}>
    {
  { HEAD }}
  </head>
  <body {
    {
     BODY_ATTRS }}>
    {
  { APP }}
  </body>
</html>

在这里插入图片描述

2. 结构

Nuxt.js 允许你扩展默认的布局,或在 layout 目录下创建自定义的布局。

可通过添加 layouts/default.vue 文件来扩展应用的默认布局。

提示: 别忘了在布局文件中添加 <nuxt/> 组件用于显示页面的主体内容。

默认布局的源码如下:

<template>
  <nuxt />
</template>

在这里插入图片描述

可以在组件中通过layout属性修改默认布局组件:

在这里插入图片描述

Index页面的布局组件变成了foo,但是about页面还是default,因为about页面没有修改其layout属性,所以默认的布局文件还是default

六、Nuxt.js异步数据

1. asyncData方法

Nuxt.js 扩展了 Vue.js,增加了一个叫 asyncData 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。

  • https://zh.nuxtjs.org/guide/async-data
  • 基本用法
    • 它会将asyncData返回的数据融合组件data方法返回数据一并给组件
    • 调用时机:服务端渲染期间和客户端路由更新之前(保证了服务端和客户端都要运行处理数据)
  • 注意事项
    • 只能在页面组件中使用,非页面组件中不会调用asyncData方法,如果子组件中需要数据,可以通过props方式传递数据
    • 没有this,因为它是在组件初始化之前被调用的

当你想用的动态页面内容有利于SEO或者是提升首屏渲染速度的时候,就在asyncData中发送请求数据。如果是非异步数据或者普通数据,则正常的初始化到data中即可。

Pages/index.vue

<template>
  <div>
    <h1>Hello {
  { title }}!</h1>
    <Foo />
    <nuxt-link to="/about">about</nuxt-link>
  </div>
</template>

<script>
import axios from 'axios'
import Foo from '@/components/Foo'

export default {
  name: 'HomePage',
  components: {
    Foo
  },
  async asyncData () {
    // 如果验证asyncData是在服务端执行的?可以通过log输出在了服务端控制台,得出这个方法是在服务端执行的。Nuxtjs为了方便调试,把服务端控制台输出数据也打印在了客户端控制台,但是为了区分,在客户端控制台用“Nuxt SSR”包裹起来了
    console.log('asyncData')
    const res = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'// 这里的请求地址要写完整,因为在服务端渲染期间,也要来请求数据,不写完整的话服务端渲染就会走到80端口,如果只是客户端渲染,就会以3000端口为基准来请求根目录下的data.json,服务端渲染就默认走到80了
    })
    // 返回的数据会与data中的数据混合
    return res.data
  },
  data () {
    return {
      foo: 'bar'
    }
  }
}
</script>

<style scoped>

</style>

pages/components/Foo.vue

<template>
  <div>
    <h1>Foo</h1>
    此处会报错,因为这是非页面组件,asyncData方法不会执行,所以foo是未定义。
    <h3>{
  {foo}}</h3>
  </div>
</template>

<script>
export default {
  name: 'FooPage',
  asyncData () {
    return {
      foo: 'bar'
    }
  }
}
</script>

<style scoped>

</style>

static这个文件夹可以直接作为根路径访问

2. 上下文对象

pages/article/_id.vue

<template>
  <div>
    <h1>article Page </h1>
    <nuxt-link to="/">首页</nuxt-link>
    <h3>title: {
  {post.title}}</h3>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'ArticlePage',
  async asyncData (context) {
    // asyncData的参数为上下文对象,我们无法在这个方法里使用this,所以无法通过this.$router.params.id拿到路由参数,但是可以通过context.params.id获取参数
    console.log(context) 
    const { data: {posts} } = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'
    })
    const id = parseInt(context.params.id, 10)
    return {
      post: posts.find(item => item.id === id),
    }
  }
}
</script>

Components/Foo.vue

<template>
  <div>
    <h1>Foo</h1>
    <ul>
      <li v-for="item in posts" :key="item.id">
        <nuxt-link :to="'/article/'+item.id">{
  {item.title}}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'FooPage',
  props: ["posts"]
}
</script>

<style scoped>

</style>

pages/index.vue

<template>
  <div>
    <h1>Hello {
  { title }}!</h1>
    <Foo :posts="posts" />
    <nuxt-link to="/about">about</nuxt-link>
  </div>
</template>

<script>
import axios from 'axios'
import Foo from '@/components/Foo'

export default {
  name: 'HomePage',
  components: {
    Foo
  },
  async asyncData () {
    // 如果验证asyncData是在服务端执行的?可以通过log输出在了服务端控制台,得出这个方法是在服务端执行的。Nuxtjs为了方便调试,把服务端控制台输出数据也打印在了客户端控制台,但是为了区分,在客户端控制台用“Nuxt SSR”包裹起来了
    console.log('asyncData')
    const res = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'// 这里的请求地址要写完整,因为在服务端渲染期间,也要来请求数据,不写完整的话服务端渲染就会走到80端口,如果只是客户端渲染,就会以3000端口为基准来请求根目录下的data.json,服务端渲染就默认走到80了
    })
    // 返回的数据会与data中的数据混合
    return res.data
  },
  data () {
    return {
      foo: 'bar'
    }
  }
}
</script>

<style scoped>

</style>

NuxtJS综合案例

一、案例介绍

1. 案例介绍

案例名称:RealWorld

这是一个开源的学习项目,目的就是帮助开发者快速学习新技能。

GitHub仓库:https://github.com/gothinkster/realworld

在线实例:https://demo.realworld.io/

2. 案例相关资源
3. 学习前提
  • Vue.js使用经验
  • Nuxt.js基础
  • Node.js、webpack相关使用经验
4. 学习收获
  • 掌握使用Nuxt.js开发同构渲染应用

  • 增强Vue.js实践能力

  • 掌握同构渲染应用中常见的功能处理

    • 用户状态管理
    • 页面访问权限处理
    • SEO优化
  • 掌握同构渲染应用的发布与部署

二、项目初始化

1. 创建项目
  • mkdir realworld-nuxtjs
  • yarn init -y
  • yarn add nuxt
  • 配置启动脚本
  • 创建pages目录,配置初始页面
2. 导入样式资源

Real world的仓库里提供了样式文件:https://github.com/gothinkster/realworld-starter-kit/blob/master/FRONTEND_INSTRUCTIONS.md

在这里插入图片描述

<!-- Import Ionicon icons & Google Fonts our Bootstrap theme relies on -->
    <link href="//code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css">
    <link href="//fonts.googleapis.com/css?family=Titillium+Web:700|Source+Serif+Pro:400,700|Merriweather+Sans:400,700|Source+Sans+Pro:400,300,600,700,300italic,400italic,600italic,700italic" rel="stylesheet" type="text/css">
    <!-- Import the custom Bootstrap 4 theme from our hosted CDN -->
    <link rel="stylesheet" href="//demo.productionready.io/main.css">

将这三个link放到我们项目中的app.html模板文件中,这个app.html要新建,默认模板就是Nuxt官网上的导航里的视图中的代码:

<!DOCTYPE html>
<html {
    {
     HTML_ATTRS }}>
  <head {
    {
     HEAD_ATTRS }}>
    {
  { HEAD }}
  </head>
  <body {
    {
     BODY_ATTRS }}>
    {
  { APP }}
  </body>
</html>

把三个link放到head标签里,由于ionicons的CDN地址在国外,打开速度较慢,又包含字体文件,无法直接下载到本地,所以我们去一个国内CDN网站上找到它来使用。

国内CDN网站:https://www.jsdelivr.com/

在这里插入图片描述

搜索ionicons,选择我们需要的版本的css的min版本,复制CDN链接,替换到link中

在这里插入图片描述

第二个link的CDN国内支持访问,就不用本地化了。

第三个link的CDN也是在国外,需要本地化,然而它不含字体文件,所以可以直接另存到本地,我们另存到了static/index.css

最终app.html就是这样:

<!DOCTYPE html>
<html {
    {
     HTML_ATTRS }}>
  <head {
    {
     HEAD_ATTRS }}>
    {
  { HEAD }}
    <!-- Import Ionicon icons & Google Fonts our Bootstrap theme relies on -->
    <link href="https://cdn.jsdelivr.net/npm/ionicons@2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css">
    <link href="//fonts.googleapis.com/css?family=Titillium+Web:700|Source+Serif+Pro:400,700|Merriweather+Sans:400,700|Source+Sans+Pro:400,300,600,700,300italic,400italic,600italic,700italic" rel="stylesheet" type="text/css">
    <!-- Import the custom Bootstrap 4 theme from our hosted CDN -->
    <link rel="stylesheet" href="/index.css">
  </head>
  <body {
    {
     BODY_ATTRS }}>
    {
  { APP }}
  </body>
</html>
3. 布局组件
  • 重写路由表

nuxt.config.js

module.exports = {
   
  router: {
   
    // 自定义路由表规则
    extendRoutes(routes, resolve) {
   
      // 清除Nuxt.js基于pages目录生成的路由表规则
      routes.splice(0)

      routes.push(...[
        {
   
          path: '/',
          component: resolve(__dirname, 'pages/layout'),
          children: [
            {
   
              path: '', // 默认子路由
              component: resolve(__dirname, 'pages/Home')
            }
          ]
        }
      ])
    }
  }
}

Layout/index.vue

在这里插入图片描述

将模板代码中的导航和页脚代码放到layout/index.vue里面,导航和页脚之间放子路由组件<nuxt-child/>

路由表中的layout组件的默认子组件是HomePage,html部分的代码来自于模板代码中的home部分。代码如下

home/index.vue

在这里插入图片描述

然后重启项目,访问项目的根路径,就是Layout组件

在这里插入图片描述

4. 导入登录注册页面

将仓库中的登录/注册模板代码拷贝到pages/login/index.vue中,登录和注册共用一个页面,通过计算属性来判断当前是登录还是注册页面,进而进行不同的文字渲染。

在这里插入图片描述

但是配置两个不同的路由,在nuxt.config.js中的layout路由的children数组中再添加两个子路由:

{
   
  path: '/login',
  name: 'login',
  component: resolve(__dirname, 'pages/login')
},
{
   
  path: '/register',
  name: 'register',
  component: resolve(__dirname, 'pages/login')
}

页面如下:

在这里插入图片描述
在这里插入图片描述

5. 导入剩余页面

个人简介profile、设置settings、文章新增修改页editor、文章详情页article

在这里插入图片描述

6. 处理顶部导航链接

a标签替换成nuxt-link标签,href属性替换成to属性

7. 处理导航链接的高亮

给nuxt.config.js的router对象增加linkActiveClass: 'active'属性

然后删掉导航链接中的Home链接写死的active类,再增加exact属性,表示只有精确匹配到Home的路径时才高亮。

<nuxt-link class="nav-link" exact to="/">Home</nuxt-link>

8. 封装请求状态

安装axios: yarn add axios

封装请求:utils/request.js

/**
 * 基于axios封装的请求模块
 */

 import axios from 'axios'

 const request = axios.create({
   
   baseURL: 'https://conduit.productionready.io'
 })

// 请求拦截器

// 响应拦截器

 export default request

三、登录注册

1. 封装登录方法

api/user.js

import request from '@/utils/request'

// 用户登录
export const login = data => {
   
  return request({
   
    method: 'POST',
    url: '/api/users/login',
    data
  })
}

// 用户注册
export const register = data => {
   
  return request({
   
    method: 'POST',
    url: '/api/users',
    data
  })
}

pages/login/index.vue

import {
    login } from '@/api/user'
export default {
   
  name: 'LoginPage',
  computed: {
   
    isLogin () {
   
      return this.$route.name === 'login'
    }
  },
  data () {
   
    return {
   
      user: {
   
        email: '',
        password: ''
      }
    }
  },
  methods: {
   
    async onSubmit () {
   
      // 提交表单,请求登录
      const {
    data } = await login({
   
        user: this.user
      })
      console.log('data', data)
      // TODO 保存用户的登录状态

      // 跳转到首页
      this.$router.push('/')
    }
  }
}
2. 错误处理

data中存放errors: {} // 错误信息

改写onSubmit方法(try-catch捕获错误):

async onSubmit () {
   
  try {
   
    // 提交表单,请求登录
    const {
    data } = await login({
   
      user: this.user
    })
    console.log('data', data)
    // TODO 保存用户的登录状态

    // 跳转到首页
    this.$router.push('/')
  }
  catch (err) {
   
    console.log('请求失败', err)
    console.dir(err)
    this.errors = err.response.data.errors
  }
}

遍历错误信息:

<ul class="error-messages">
  <template v-for="(messages, field) in errors">
    <li v-for="(message, index) in messages" :key="index">
      {
  { field }} {
  { messages}}
    </li>
  </template>
</ul>
3. 注册
data () {
   
  return {
   
    user: {
   
      username: '',
      email: '',
      password: ''
    },
    errors: {
   } // 错误信息
  }
},
methods: {
   
    async onSubmit () {
   
      try {
   
        // 提交表单,请求登录
        const {
    data } = this.isLogin ? await login({
   
          user: this.user
        }): await register({
   
          user: this.user
        })
        console.log('data', data)
        // TODO 保存用户的登录状态

        // 跳转到首页
        this.$router.push('/')
      }
      catch (err) {
   
        console.log('请求失败', err)
        console.dir(err)
        this.errors = err.response.data.errors
      }
    }
}
4. 解析存储登录状态实现流程

https://zh.nuxtjs.org/examples/auth-external-jwt

https://codesandbox.io/s/github/nuxt/nuxt.js/tree/dev/examples/auth-jwt?from-embed=&file=/store/index.js

5. 将登录状态存储到容器中

store/index.js

// 为了防止在服务端渲染期间,运行的都是同一个实例,防止数据冲突,务必要把state定义成一个函数,返回数据对象
export const state = () => {
   
  return {
   
    user: null
  }
}

export const mutations = {
   
  setUser (state, data) {
   
    state.user = data
  }
}

export const actions = {
   

}

登录成功时保存用户状态到容器中,login/index.vue

// 保存用户的登录状态
this.$store.commit('setUser', data.user)
6. 登录状态持久化

将数据存到store中,只是在内存里,页面一刷新就没了。所以我们应该想办法将数据进行持久化。以前的做法是存到本地存储里,而现在在服务端也要渲染,所以不可以存在本地存储,否则服务端获取不到。正确的做法是存在cookie中,cookie可以随着http请求发送到服务端。

所以在login/index.vue页面中容器保存完登录状态后,还要将数据存储到Cookie中

// 仅在客户端加载js-cookie
const Cookie = process.client ? require('js-cookie'): undefined

// ...

// 保存用户的登录状态
this.$store.commit('setUser', data.user)

// 为了防止刷新页面数据丢失,数据需要持久化
Cookie.set('user', data.user)

在Store/index.js的actions中增加nuxtServerInit方法,nuxtServerInit是一个特殊的action方法,这个方法会在服务端渲染期间自动调用,作用是初始化容器数据,传递数据给客户端使用

export const actions = {
   
  nuxtServerInit ({
    commit }, {
    req }) {
   
    let user = null
    // 如果请求头中有个Cookie
    if (req.headers.cookie) {
   
      // 使用Cookieparser把cookie字符串转化为JavaScript对象
      const parsed = cookieparser.parse(req.headers.cookie)
      try {
   
        user = JSON.parse(parsed.user)
      } catch (err) {
   
        // No valid cookie found
      }
    }
    commit('setUser', user)
  }
}
7. 处理导航栏链接展示状态

Layout/index.vue页面中,增加计算属性user,通过user判断用户是否是登录状态:

import {
    mapState } from 'vuex'

export default {
   
  name: 'LayoutIndex',
  computed: {
   
    ...mapState(['user'])
  }
}

然后将导航栏上的用户名称那个li调到登录注册的li前面去,将最后两个li套在template里,将另3个li套在另外一个template里,如果用户登录了,则显示第一个template,如果未登录则显示后面两个登录注册所在的template

在这里插入图片描述

8. 处理页面访问权限 – 中间件

[https://zh.nuxtjs.org/guide/routing#%E4%B8%AD%E9%97%B4%E4%BB%B6](https://zh.nuxtjs.org/guide/routing#%E4%B8%AD%E9%97%B4%E4%BB%B6)

中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。

每一个中间件应放置在 middleware/ 目录。文件名的名称将成为中间件名称 (middleware/auth.js将成为 auth 中间件)。然后给要保护的页面增加middleware属性,值为中间件的文件名

中间件执行流程顺序:

  1. nuxt.config.js
  2. 匹配布局
  3. 匹配页面

定义两个中间件,

middleware/authenticated.js

export default function ({
    store, redirect }) {
   
  // 如果用户未登录,则跳转到登录页
  if (!store.state.user) {
   
    return redirect('/login')
  }
}

middleware/notAuthenticated.js

export default function ({
    store, redirect }) {
   
  // 如果用户已登录,则跳转到首页
  if (store.state.user) {
   
    return redirect('/')
  }
}

然后给settings/index.vueprofile/index.vueeditor/index页面增加属性middleware值为authenticated

export default {
   
  name: 'Settings',
  middleware: 'authenticated'
}

login/index.vue页面增加属性middleware值为notAuthenticated

export default {
   
  name: 'LoginPage',
  middleware: 'notAuthenticated',
  // ...
}

四、首页

首页展示我的关注的文章和公共文章,所有文章可以选择标签,还可以分页。

1. 展示公共文章列表

Api/article.js

import request from '@/utils/request'

// 获取公共的文章列表
export const getArticles = params => {
   
  return request({
   
    method: 'GET',
    url: '/api/articles',
    params
  })
}

为了更好地优化SEO,将数据渲染放到服务端进行,数据初始化代码写到asyncData ()函数中,循环渲染文章信息。

home/index.vue

<div
  class="article-preview"
  v-for="article in articles"
  :key="article.slug"
>
  <div class="article-meta">
    <nuxt-link
      :to="{
        name: 'profile',
        params: {
          username: article.author.username
        }
      }"
      ><img :src="article.author.image"
    /></nuxt-link>
    <div class="info">
      <nuxt-link
        :to="{
          name: 'profile',
          params: {
            username: article.author.username
          }
        }"
        class="author"
      >{
  {article.author.username}}</nuxt-link>
      
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
为什么要学习服务端渲染 nuxt.js ? 现在我们的项目大多数都是SPA(单页面应用),在实际开发过程中单页面应用比之前的模板渲染要好很多,首先单页面应用是前后端分离,架构清晰,前端负责交互逻辑,后端负责数据,前后端单独开发,独立测试。但是,SPA不利于SEO(搜索引擎优化)。让搜索引擎更为信任该网站,通过提升排名获得更多网站流量,对于某些类型的网站是非常有必要的。目前大部分的Vue项目本质是 SPA 应用,React、Angular也都是SPA应用。SPA应用广泛用于对SEO要求不高的场景中。在我们开发的过程中,我们有 SEO 的需求,我们需要搜索引擎更多地抓取到我们的项目内容,此时我们需要SSR。SSR保证用户尽快看到基本的内容,也使得用户体验性更好。 Nuxt.js 是一个 Node 程序,基于vue.js开发的一套服务端渲染的框架,必须使用 Node 环境。我们对 Nuxt.js 应用的访问,实际上是在访问这个 Node.js 程序的路由,程序输出首屏渲染内容 + 用以重新渲染的 SPA 的脚本代码,而路由是由 Nuxt.js 约定好的 pages 文件夹生成的,开发只需要遵循一定的约定,直接使用vue.js开发我们项目也是非常轻松的。 课程案例 (1) HOME PAGE (2) Jokes Page  (3)About Page  课程概述 在本课程中,大喵将使用 nuxt.js + bootstrapVue + json-server 开发实战性质一个入门级项目,带着大家来体验服务端渲染(SSR )项目构建的过程;介绍 nuxt.js项目目录的结构,每个文件夹和文件的基本概念和作用,以及nuxt.config.js 配置文件的基本介绍;页面公共结构处理,路由页面跳转配置处理;axios 接口请求;带着大家来熟悉及掌握 bootstrapVue UI组件库的使用;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值