案例:nuxt-sample-demo

SSR
案例

这里已经将下面所有的完整案例全部上传到了gitHub中,nuxt-sample-demo案例网址 (opens new window),根据不同的分支感受不同的案例。

# 代码分支说明

  1. 在刚才初始化二的项目的中git init
  2. 创建文件.gitignore,将node_modules.nuxt写进去

.nuxt是在启动编译的时候自动生成的代码,里面包含了客户端代码和服务端渲染的代码。

  1. 将当前文件提交git commit -m '初始化nuxt.js项目'
  2. 创建一个新的分支git checkout -b 02-router,如果之后运行案例,分别使用不用的分支即可。

# Nuxt.js路由

# 基础路由

查看官网-路由 (opens new window)

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

  1. 创建pages里面创建一个user文件夹,里面创建两个文件index.vueuser.vue,里面填点简单的东西

  2. 现在目录中的路由结构就是下面的,所有的index文件对应的是当前文件夹下的根路径,访问http://localhost:3000/userhttp://localhost:3000/user/user可以找到对应的页面

image

可以看到打包的时候.nuxt文件中会生成一个router.js文件,里面确实引用了vue-router,生成了路,这个文件不要手动去改。

image

  1. 将这个代码commit提交git commit -m '02基础路由'

# 路由导航

这里的用法去哪里看?去Vue-Router的文档看。

  • a标签(会刷新整个页面,不推荐使用)
  • nuxt-link组件
  • 编程式导航(js跳转路由)
  1. 回到master分支并且创建一个新的分支03-路由导航
  2. about.vue里面写几种导航方式
<template>
  <div>
    <h1>About page</h1>
    <!-- 第一种:a 链接,刷新页面,走服务端渲染 -->
    <h2>a 链接</h2>
    <a href="/">首页</a>
    <!-- 第二种:router-link 导航链接组件 -->
    <h2>router-link</h2>
    <router-link to="/">router-link跳转首页</router-link>
    <!-- nuxt-link 和 router-link的用法一毛一样 -->
    <nuxt-link to="/">nuxt-link跳转首页</nuxt-link>
    <!-- 第三种:编程式导航 -->
    <h2>编程式导航</h2>
    <button @click="toHome">首页</button>
  </div>
</template>

<script>
export default {
  name: 'AboutPage',
  methods: {
    toHome () {
      // 和Vue-Router的用法一毛一样 
      this.$router.push('/')
    }
  }  
}
</script>

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  1. 提交当前分支git commit -m '03-路由导航'

# 动态路由

Vue-Router是通过自定义:id之类的定义动态路由,而nuxt直接在文件中识别前面是下划线开头的即为动态路由。

  1. 从master分支重新创建一个分支git checkout -b '04-动态路由'
  2. pages文件夹中创建user文件夹,里面创建_id.vue

image

  1. _id.vue文件里面写
<template>
  <div>
    <!--获取到路由中的id动态参数-->
    <h1>User page {{ $route.params.id }}</h1>
  </div>
</template>

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

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 在浏览器中可以看到,user后面可以不跟参数,可以跟随意的参数

image

  1. 这里还可以限制传的动态数据的格式或者值,如果不符合要求,就返回错误页面
export default {
  name: 'UserPage',
  validate({ params }) {
    // 必须是number类型
    return /^\d+$/.test(params.id)
  }
}
1
2
3
4
5
6
7
  1. 将这个分支提交git comnmit -m '04-动态路由'

# 嵌套路由

创建子路由,需要添加一个vue文件,里面要增加来显示子视图内容,同时还要创建一个同名目录来存放子视图组件。

  1. 从master创建一个分支git checkout -b 05-嵌套路由
  2. pages目录下创建文件users.vue
<template>
  <div>
    <h1>Users</h1>
    <!-- 子路由出口 -->
    <nuxt-child />
  </div>
</template>

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

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 创建同名目录users,并且创建index.vue,这个是默认子路由
<template>
  <div>
    <h1>Users 默认子路由</h1>
  </div>
</template>

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

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 访问页面http://localhost:3000/users/,可以看到子路由的内容在父路由上

image

  1. 在users文件夹下创建user.vue文件,并写下面的内容
<template>
  <div>
    <h1>Users - user</h1>
  </div>
</template>

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

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13

6.访问http://localhost:3000/users/user就可以看到父路由里面嵌套了user子路由

image

  1. 把内容进行提交git commit -m '05-嵌套路由'

# 路由配置

配置项 说明 类型 默认值
base 设置网站根路径 String '/'
extendRouters 扩展路由 Function -
  1. 创建一个新的分支 git checkout -b 06-路由配置
  2. 在根目录下创建一个文件nuxt.config.js
  3. 里面用CommonJS的语法导出配置对象,路由在router的对象中
module.exports = {
  router: {
    ... 
  }
}
1
2
3
4
5

# bash

  1. 设置base为/abc,访问根目录就变成了http://localhost:3000/abc/

# extendRoutes

  1. 设置一个可以访问about.vue文件的路由地址
module.exports = {
  router: {
    base: '/abc',
    // routes: 路由配置表,也是一个数组,可以动态push路由
    // resolve:解析路由组件路径
    extendRoutes(routes, resolve) {
      routes.push({
        // 设置之后访问的路由
        path: '/hello',
        // 设置个名称
        name: 'hello',
        // 设置对应的组件
        component: resolve(__dirname, 'pages/about.vue')
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这样,访问http://localhost:3000/abc/hello也可以访问到about.vue

image

其他的有需求看文档,这里不再多说。

# 视图

查看官网-视图 (opens new window)

image

# 视图-模板Document

Nuxt.js可以定制应用模板,现在默认的模板不写就是

<!--app.html-->
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>
1
2
3
4
5
6
7
8
9
10

要是想要自定义修改,在根目录下创建app.html,就在这个基础上修改即可,里面的,就是所有页面渲染之后的出口

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    <!-- 渲染的内容最终会注入到这里 -->
    <h1>app.html</h1>
    {{ APP }}
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11

如果看不到可以重启服务,可以看到加了h1标签,页面上也有了内容

image

# 视图-结构Layout

可以在html和页面组件之间再加一层,布局组件,可以扩展默认布局,layouts/default.vue这个是所有页面组件的父路由

  1. 创建一个分支git checkout -b 08-布局组件
  2. 在根目录下创建layouts/default.vue
<template>
  <div>
    <h1>Layouts/default.vue</h1>
    <!-- 页面出口,类似子路由,必须有 -->
    <nuxt />
  </div>
</template>

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

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 重启服务,可以看到页面上会有内容出现,这个比较适合项目中有相同的头部和侧边栏等布局

image

  1. 这个相当于index.vue页面有一个layout配置,这个是默认的。
<script>
export default {
  name: 'IndexPage',
  layout: 'default'
}
</script>
1
2
3
4
5
6
  1. 如果想要自定义的话,需要在layouts里面再添加一个user.vue,然后这里的layout设置为user
<!--layouts/user.vue-->
<template>
  <div>
    <h1>Layouts/user.vue</h1>
    <!-- 页面出口,类似子路由 -->
    <nuxt />
  </div>
</template>

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

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--pages/index.vue-->
<script>
export default {
  name: 'IndexPage',
  layout: 'user'
}
</script>
1
2
3
4
5
6
7

可以看到首页变成了

image

而这个时候about没有设置layout,所以它的父组件还是default.vue

image

# Nuxt.js异步数据

如何在服务端渲染动态页面?

具体参考官方文档 async-data (opens new window)

  1. 创建static/data.json,将数据填进去
{
  "posts": [
    {
      "id": 1,
      "title":"title1",
      "body":"hellohellohellohello"
    },
    {
      "id": 2,
      "title":"title2",
      "body":"hellohellohellohello"
    },
    {
      "id": 3,
      "title":"title3",
      "body":"hellohellohellohello"
    },
    {
      "id": 4,
      "title":"title4",
      "body":"hellohellohellohello"
    },
    {
      "id": 5,
      "title":"title5",
      "body":"hellohellohellohello"
    }
  ],
  "title": "我是标题"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  1. 访问http://localhost:3000/data.json可以看到对应数据。

static目录中的资源是可以直接被访问的,nuxt在web服务中公开的,允许公开访问不经过打包的。

  1. 先安装npm i axios --save,然后在index.vue中修改
<template>
  <div>
  <!--这里使用模板-->
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.title}}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'IndexPage',
  // 这里使用异步数据
  async asyncData () {
    console.log('asyncData')
    const res = await axios({
      method: 'GET',
      // 这里为啥不直接写data.json,是因为服务端地址默认是http://localhost:8080,所以这里需要写全名,不然请求的不是3000端口的数据
      url: 'http://localhost:3000/data.json'
    })
    // 这里将数据return,res.data里面就是返回的数据,这里会和下面的data进行融合
    return res.data
  },
  data () {
    return {
      foo: 'bar'
    }
  }
}
</script>

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

可以看到页面已经渲染上去了。

image

# 那么这个asyncData是在哪一步执行的呢?

  1. 我们上面在asyncData的函数一开始输出了一个console,可以看到这个是在服务端执行的。

image

Nuxt为了我们调试方便,在控制台也进行了输出,与客户端输出内容进行了区分。也可以看出来这个是在服务端渲染执行的。

image

  1. 如果有路由导航,在路由导航跳转之前也会执行的。

把index.vue和about.vue都设置上跳到对方的路由

<!--index.vue-->
<nuxt-link to="/about">About</nuxt-link>
<!--about.vue-->
<nuxt-link to="/">Index</nuxt-link>
1
2
3
4

从about.vue跳转到index.vue的时候,控制台也出现了asyncData,这个还是客户端执行的,并不是服务端执行的。

image

  1. 最后将代码提交git commit -m '09-异步数据'

# 总结

  • 功能:让我们可以在设置组件的数据之前能异步获取或处理数据
  • 基本用法
    • 他会将asyncData返回的数据融合组件data方法返回数据一并给组件
    • 调用时机:服务端渲染期间和客户端路由更新之前
  • 注意事项
    • 只能在页面组件中使用(只能在pages里面使用,components里面不能使用asyncData,可以通过组件间传值使用,这个也是服务端渲染的一部分)
    • 没有this,因为他是组件初始化之前被调用的(无论是客户端渲染还是服务端渲染,都在这个之前调用,在asyncData中打印this为undefined)

# 上下文对象

  1. 创建一个分支git checkout -b '10-上下文对象'

  2. 创建内容模板

    2.1. 创建static文件夹将之前的data.json放进去

    2.2. 创建components/subPage.vue,然后修改

    <template>
      <div>
        <h1>subPage</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: 'subPage',
      props:['posts']
    }
    </script>
    
    <style></style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    2.3. pages里面创建目录是article,有一个_id.vue文件

    <template>
      <div>
        <h1>article page</h1>
      </div>
    </template>
    
    <script>
    export default {
      name: 'ArticlePage'
    }
    </script>
    
    <style></style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    2.4. 在index.vue中引用subPage组件

    <template>
      <div>
        <h1>{{ title }}</h1>
        <sub-page :posts="posts"></sub-page>
      </div>
    </template>
    
    <script>
    import axios from 'axios'
    import SubPage from '../components/subPage.vue'
    export default {
      name: 'IndexPage',
      components: {
        SubPage
      },
      async asyncData () {
        const res = await axios({
          method: 'GET',
          url: 'http://localhost:3000/data.json'
        })
        return res.data
      }
    }
    </script>
    
    <style></style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    2.5. 现在的样式就是点击按钮就可以跳转到具体的内容页面上去,里面要显示对应的内容

    image

  3. 内容模板创建完毕之后,进入_id.vue页面接收消息

<script>
import axios from 'axios'
export default {
  name: 'ArticlePage',
  async asyncData (context) {
    const res = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'
    })
    // 下面不能使用this,所以不能通过这种方式获取路径
    // console.log(this.$route.params.id)
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这个里面不能用this,所以asyncData使用context接收上下文,下面打印一下上下文看看里面有什么?

image

  1. 里面我们找到了params里面有id,route的params里面也有id,这两个是一样的,可以直接使用,下面对页面进行修改
<template>
  <div>
    <!--这里展示数据-->
    <h1>{{ article.title }}</h1>
    <p>{{ article.body }}</p>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'ArticlePage',
  async asyncData (context) {
    const { data } = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'
    })
    // 通过上下文拿到id值
    const id = Number.parseInt(context.params.id)
    // 返回article就是每一个posts的元素
    return {
      article: data.posts.find(item => item.id === id)
    }
  }
}
</script>

<style></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  1. 这个时候演示就可以看到,内容发生了变化

image

案例完成。

更新时间: 2022-01-29 23:25