当前位置:   article > 正文

从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二九║ Nuxt实战:异步实现数据双端渲染..._{ name: 'page', mode: 'out-in' },

{ name: 'page', mode: 'out-in' },

回顾

哈喽大家好!又是元气满满的周~~~二哈哈,不知道大家中秋节过的如何,马上又是国庆节了,博主我将通过三天的时间,给大家把项目二的数据添上(这里强调下,填充数据不是最重要的,最重要的是要配合着让大家明白 nuxt.js 是如何一步步实现服务端渲染的),虽说是基于 Nuxt 的,但是数据源还是我们的老数据,就是 .net core api 的数据,和上一个项目中使用的是一样的,这里要说下,有的小伙伴说想要我的数据,这里暂时说抱歉,因为里边有一些我的私人的记录,还有一些网站密码啥的,我自己懒得一条一条的删除了,这里就不放出来了,多多包涵,不过接口地址可以随便使用http://123.206.33.109:8081,有源代码和数据库表结构,相信大家还是可以搞定滴,因为是三天,所以今天我们就会把首页给处理出来,这里有俩个点,第一,为什么是三天呢,因为博主三天后要放假了哈哈( 这里要给大家说下心得,坚持学习和坚持写博客是完全不同的时间量,每天我光写博客的时间最少三个小时,加上工作的九个小时,每天我至少需要12个小时,所以如果想快速学习,建议还是要好好的写博客 ),第二,数据获取和之前的 vue 有点儿差别,虽然都是基于 axios ,但是 nuxt 框架做了一定的封装,而且还是异步的,ASync/Await ,大家看我今天的标题也能看的出来,至于为什么会是异步的呢,先留个神秘,大家看完今天的讲解应该就知道是为什么了~

书接上文,上周咱们说到了nuxt 的运行原理《二八║ Nuxt 基础:面向源码研究Nuxt.js》,不知道大家是否看了呢,主要通过源码的分析,来重点说明 Nuxt 是如何实现服务端渲染的,个人感觉写的比较羞涩难懂,我也是在慢慢的润色,尽量修改成通俗易懂的给大家展示,写成人话。这里我要说句题外话,大家有时间的话,还是应该把后端的注意力拿出来一点儿点儿放到前端了,以前我也是一个老后端,一直想着各种持久化ORM哪个更帅,各种框架哪个性价比更高,但是一直也技术平平,反而忽略了这两年的前端发展,经过这一个月的学习,我发现前端技术竟然发展如此之快,竟超出我的想象,有点儿追赶挑战后端的意思了,多语言化的发展,更有助于一个程序员的发展( 这个欢迎来喷,只会一种语言的话,嗯~ 会有局限性┑( ̄Д  ̄)┍ ),哈哈这个扯的有点儿远了。

对于昨天的文章,总结来说,nuxt 的核心就是在 vue.js 的基础上,封装了双端渲染模式(服务端和客户端),结合页面html片段缓存,来实现 SSR ,最终解决首屏快速渲染和 SEO 的问题,核心就是在如何实现双端渲染上,大家之前的教程中,对 Vue 的 SPA 很熟练了,这三天咱们就慢慢的研究下,如何实现双端渲染的,这个也就是我讲 nuxt 的核心 —— 渲染。

零、今天要完成蓝色的部分

13652172-8e8f0a4bee437db5.png
image

一、重点温习框架中的几个部分文件 —— 铺垫

1、nuxt.config.js 文件

项目的核心文件,作为一个配置文件,对全局配置起到一个十分重要的作用,像我们的 webconfig 一样

  1. //这些配置在你的项目里可能不一定都有,但是我都会提到
  2. module.exports = {
  3. cache: {},
  4. css: [ // 加载一个 node.js 模块 // 'hover.css/css/hover-min.css', // // 同样加载一个 node.js 模块,不过我们定义所需的预处理器 // { src: 'bulma', lang: 'sass' }, // // 项目中的 CSS 文件 // '~assets/css/main.css', // // 项目中的 Sass 文件 // { src: '~assets/css/main.scss', lang: 'scss' } // 指定 scss 而非 sass
  5. ], // 默认 true
  6. dev: process.env.NODE_ENV !== 'production',//不是生产环境 // 创建环境变量
  7. env: {}, // 配置 Nuxt.js 应用生成静态站点的具体方式。
  8. genetate: {
  9. dir: '',
  10. minify: '',
  11. routes: [],
  12. }, /* * vue-meta
  13. * Headers of the page */ head: {
  14. title: '老张的哲学',
  15. meta: [
  16. { charset: 'utf-8' },
  17. { name: 'viewport', content: 'width=device-width, initial-scale=1' },
  18. { hid: 'description', name: 'description', content: 'Nuxt.js project' }
  19. ],
  20. link: [
  21. { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
  22. ]
  23. }, /* ** Customize the progress bar color */ loading: { color: '#3B8070' }, /* ** Build configuration */ build: { /* ** Run ESLint on save */ extend (config, { isDev, isClient }) { if (isDev && isClient) {
  24. config.module.rules.push({
  25. enforce: 'pre',
  26. test: /\.(js|vue)$/,
  27. loader: 'eslint-loader',
  28. exclude: /(node_modules)/ })
  29. }
  30. }
  31. },
  32. performance: {
  33. gzip: false,
  34. prefetch: true }, // 引入 Vue.use 的插件
  35. plugins: [], // 默认当前路径
  36. rootDir: process.cwd(),
  37. router: { base: '',
  38. mode: 'history',
  39. linkActiveClass: 'nuxt-link-active',
  40. scrollBehavior: (to, from, savedPosition) => { // savedPosition 只有在 popstate 导航(如按浏览器的返回按钮)时可以获取。
  41. if (savedPosition) { return savedPosition
  42. } else {
  43. let position = {} // 目标页面子组件少于两个
  44. if (to.matched.length < 2) { // 滚动至页面顶部
  45. position = { x: 0, y: 0 }
  46. } else if (to.matched.some((r) => r.components.default.options.scrollToTop)) { // 如果目标页面子组件中存在配置了scrollToTop为true
  47. position = { x: 0, y: 0 }
  48. } // 如果目标页面的url有锚点, 则滚动至锚点所在的位置
  49. if (to.hash) {
  50. position = { selector: to.hash }
  51. } return position
  52. }
  53. }, // default
  54. middleware: 'user-agent', // 扩展路由
  55. extendRoutes: () => {}, // 默认同 rootDir
  56. srcDir: this.rootDir,
  57. transition: {
  58. name: 'page',
  59. mode: 'out-in' },
  60. watchers: {
  61. chokidar: {}, // 文件监控
  62. webpack: {
  63. aggregateTimeout: 300,
  64. poll: 1000 }
  65. }
  66. }
  67. }

2. 视图模板

默认的 html 模版: 应用根目录下的 app.html 文件, 如果你没有找到这个文件, 则采用默认的模版,当然你也可以自己新增,配置,

这个更像是我们之前的 index.html 页面,只不过把 <div id='app'></div> 挂载,变成了填充的方式。

  1. <!DOCTYPE html>
  2. <html {{ HTML_ATTRS }}>
  3. <head> {{ HEAD }} </head>
  4. <body {{ BODY_ATTRS }}> {{ APP }} </body>
  5. </html>

3. layouts 布局目录

13652172-5e27528dc168c27a.png
image

可以修改该目录下的 default.vue 来修改默认布局 , 这个就是类似于我们之前的 app.vue 页面,

  1. <template>
  2. <div class="layout-default">
  3. <cl-header></cl-header>
  4. <nuxt class="layout-main"/>
  5. <cl-footer></cl-footer>
  6. <div class="layout-bg"></div>
  7. </div>
  8. </template>

其中 <nuxt/> 是必需的,页面的主体内容会显示在这里 (类似于根节点的 <router-view/>)

此外还可以在目录下 error.vue 作为错误页面,具体的写法可以参考官方文档

4. pages 页面 路由

路由, 约定大于配置, 支持动态, 嵌套, 动态嵌套路由, 过渡效果和中间件,通过文件夹目录名称, 组件名称, 生成路由配置,默认的 transitionName 为 page, 可在 assets 中添加全局的过渡效果,

在匹配页面之前执行;

页面组件实际上是 Vue 组件,只不过 Nuxt.js 为这些组件添加了一些特殊的配置项(对应 Nuxt.js 提供的功能特性)以便你能快速开发通用应用。

  1. <template>
  2. <h1 class="red">Hello {{ name }}!</h1>
  3. </template>
  4. <script> export default {
  5. asyncData (context) { // called every time before loading the component
  6. return { name: 'World' }
  7. },
  8. fetch () { // The fetch method is used to fill the store before rendering the page
  9. },
  10. head () { // Set Meta Tags for this Page
  11. }, // and more functionality to discover
  12. ...
  13. } </script>
  14. <style> .red {
  15. color: red;
  16. } </style>

nuxt.config.js --> 执行middleware --> 匹配布局 --> 匹配页面

13652172-48a36e86357120b4.png
image

用于存放页面级别的组件,nuxt 会根据该目录下的页面结构生成路由

比如上图中的页面结构,会生成这样的路由配置:

  1. const _7b01ffaa = () => import('..\\pages\\post\\index.vue' /* webpackChunkName: "pages_post_index" */).then(m => m.default || m) const _2b7fe492 = () => import('..\\pages\\post\\_id.vue' /* webpackChunkName: "pages_post__id" */).then(m => m.default || m) const _4f14dfca = () => import('..\\pages\\index.vue' /* webpackChunkName: "pages_index" */).then(m => m.default || m)
  2. export function createRouter () { return new Router({
  3. mode: 'history', base: '/',
  4. linkActiveClass: 'nuxt-link-active',
  5. linkExactActiveClass: 'nuxt-link-exact-active',
  6. scrollBehavior,
  7. routes: [
  8. {
  9. path: "/post",
  10. component: _7b01ffaa,
  11. name: "post" },
  12. {
  13. path: "/post/:id",
  14. component: _2b7fe492,
  15. name: "post-id" },
  16. {
  17. path: "/",
  18. component: _4f14dfca,
  19. name: "index" }
  20. ],
  21. fallback: false })
  22. }

5、使用插件 plugins 文件夹

如果项目中还需要引入其他的第三方插件,可以直接在页面中引入,这样在打包的时候,会将插件打包到页面对应的 js 里面,但要是别的页面也引入了同样的插件,就会重复打包。如果没有需要分页打包的需求,这时候可以配置 plugins,然后在根目录的 nuxt.config.js 中添加配置项 build.vendor 和 plugins,这里的 plugins 属性用来配置 vue.js 插件,也就是 可以用 Vue.user() 方法 的插件

默认只需要 src 属性,另外还可以配置 ssr: false,让该文件只在客户端被打包引入,如果是像 axios 这种第三方 (不能 use) 插件,只需要在 plugins 目录下创建 axios.js,然后在 build.vendor 中添加配置 (不需要配置 plugins)

这样在打包的时候,就会把 axios 打包到 vendor.js 中。

二、配置页面,实现首页的数据获取 —— 异步

1、在 static 文件中,新增样式 vue-blog-sq.css 文件

提醒,这里的文件,不会被打包,所以会在页面中呈现原有格式,如果想每次都被编译(可以实现压缩效果),需要写到 assets 资源文件夹中

13652172-fd70a23c4afbe317.png
image
13652172-21d7b10da2f49788.png
image

2、在 components 中,新建 layout 文件夹,然后新增页头页脚 组件

这个很简单,就是普通的 *.vue 组件写法,大家可以自行下载浏览

13652172-25efd2af18bbdb64.png
image

3、在 layouts 页面布局文件夹中,新增 blog.vue 布局

提醒:以后也可以单给用户增加布局,比如 user.vue

  1. <template>
  2. <div class="layout-default">
  3. <cl-header></cl-header>
  4. <nuxt class="layout-main"/>//注意,<nuxt />,必须有,类似一个 <router-view/>
  5. <cl-footer></cl-footer>
  6. </div>
  7. </template>
  8. <script type="text/javascript"> import clHeader from "~/components/layout/header.vue";
  9. import clFooter from "~/components/layout/footer.vue";
  10. export default {
  11. data () { return {};
  12. },
  13. mounted () {
  14. },
  15. components: {
  16. clHeader,
  17. clFooter
  18. }
  19. }; </script>

很简单的一个布局入口,是不是很像我们之前的 App.vue 中的 路由容器 —— <router-view /> ,但是又比其更丰富,因为我们之前的 app.vue 只能有一个入口,但是 nuxt 可以提供多个自定义 模板layouts,更方便。

4、根目录新增 config 文件夹,添加 index.js ,作为我们以后的配置文件,类似 .net core api 中的 appsetting.json 文件

  1. const config = { //开发环境配置,开发的时候
  2. development: { //api: "http://localhost:58427/api/",
  3. api: "http://123.206.33.109:8081/api/" }, //生产环境配置,部署的时候
  4. production: {
  5. api: "" }
  6. }; //获取当前环境变量,是 production或者development
  7. module.exports = config[process.env.NODE_ENV];

5、在 plugins 插件中,新增 server_site 文件夹,然后添加 http.js 和 index.js

1、为什么要使用插件?

我们可以在应用中使用第三方模块,一个典型的例子是在客户端和服务端使用 axios 做 HTTP 请求。

首先我们需要安装 npm 包:

npm install --save axios

然后在页面内可以这样使用:

  1. <template>
  2. <h1>{{ title }}</h1>
  3. </template>
  4. <script> import axios from 'axios' export default { async asyncData ({ params }) {
  5. let { data } = await axios.get(`https://my-api/posts/${params.id}`)
  6. return { title: data.title }
  7. }
  8. } </script>

有一个值得注意的问题是,如果我们在另外一个页面内也引用了 axios,那么在应用打包发布的时候 axios 会被打包两次,而实际上我们只需要打包一次。这个问题可以通过在 nuxt.config.js 里面配置 build.vendor 来解决:

  1. module.exports = {
  2. build: {
  3. vendor: ['axios']
  4. }
  5. }

经过上面的配置后,我们可以在任何页面里面引入 axios 而不用担心它会被重复打包。

2、为什么要分 server_site服务端 与 client_site客户端 插件

有些插件可能只是在浏览器里使用,所以你可以用 ssr: false 变量来配置插件只从客户端还是服务端运行。

举个栗子:

  1. nuxt.config.js:
  2. module.exports = {
  3. plugins: [
  4. { src: '~/plugins/vue-notifications', ssr: false }//设置ssr 为false
  5. ]
  6. }
  7. plugins/vue-notifications.js://定义一个插件
  8. import Vue from 'vue' import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)

同样地,如果有些脚本库你只想在服务端使用,在 Webpack 打包 server.bundle.js 文件的时候会将 process.server 变量设置成 true

3、配置服务端 http.js 和 index.js 的内容

13652172-7f6c474ba65226be.png
image
  1. // http.js 封装 axios,防止多处打包
  2. import Axios from "axios";
  3. import config from "~/config";//引入配置文件 // 实例化 axios()
  4. const http = Axios.create({
  5. baseURL: config.api,//根url
  6. timeout: 8000,
  7. validateStatus: function (status) { return status >= 200;
  8. }
  9. }); // 定义错误异常方法
  10. function LogicError (message, code, data) { this.message = message; this.code = code; this.data = data; this.name = "LogicError";
  11. }
  12. LogicError.prototype = new Error();
  13. LogicError.prototype.constructor = LogicError; // http 的request 请求
  14. http.interceptors.request.use((data, headers) => { return data;
  15. }); // http 的response 命令,失败的调用上边的失败异常方法
  16. http.interceptors.response.use(response => { const data = response.data; switch (data.success) { case true: return data.data; default: throw new LogicError(data.msg);
  17. }
  18. }, err => { throw new LogicError("网络请求错误");
  19. });
  20. export default http;// 输出http
  1. // 定义 http 插件,是一个全局变量
  2. import Vue from "vue";
  3. import http from "./http.js";// 引入http封装的axios
  4. const install = function (VueClass, opts = {}) { // http method
  5. VueClass.http = http;
  6. VueClass.prototype.$http = http;
  7. };
  8. Vue.use(install);// 在vue 中,使用该插件

6、在 nuxt.config.js 中引用我们的服务端插件

这样添加以后,我们就可以全局使用请求命令了,打包的时候,也只会打包一个

提示:1、记得需要按照提示安装 axios npm install --save axios

       2、引入的组件库必须配置 plugins, 但是有的组件库不支持 ssr.
13652172-36e460eeae796e29.png
image

7、设计修改 pages 下的 index.vue 页面,异步获取接口数据

提示:这个就是文章开头提到的问题

1、为什么要异步?

asyncData方法会在组件(**限于页面组件,也就是pages 文件夹下的vue文件 **)每次加载之前被调用。它可以在服务端或路由更新之前被调用。 在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

注意:由于asyncData方法是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。

Nuxt.js 提供了几种不同的方法来使用 asyncData 方法,你可以选择自己熟悉的一种来用:

  1. 返回一个 Promise, nuxt.js会等待该Promise被解析之后才会设置组件的数据,从而渲染组件.
  2. 使用 async 或 await (推荐使用)
返回 Promise
  1. export default {
  2. > asyncData ({ params }) { return axios.get(`https://my-api/posts/${params.id}`)
  3. > .then((res) => { return { title: res.data.title }
  4. > })
  5. > }
  6. > }
使用 async或await
  1. export default { async asyncData ({ params }) {
  2. > let { data } = await axios.get(`https://my-api/posts/${params.id}`)
  3. > return { title: data.title }
  4. > }
  5. > }

  1. <!-- index.vue 页面 -->
  2. <template>
  3. <div class="u-marginBottom40 js-collectionStream">
  4. <div class="streamItem streamItem--section js-streamItem">
  5. <div class="u-clearfix u-maxWidth1000 u-marginAuto">
  6. <div class="row u-marginTop30 u-marginBottom20 u-sm-marginLeft20 u-sm-marginRight20 u-xs-marginTop0 u-xs-marginRight0 u-xs-marginLeft0">
  7. <div v-for="post in blogs" :key="post.bID" class="postArticle postArticle--short is-withAccentColors">
  8. <!--......-->
  9. </div>
  10. </div>
  11. </div>
  12. </div>
  13. </div>
  14. </template>
  15. <script> import Vue from "vue";//引入 vue 实例,获取全局变量
  16. export default {
  17. layout: "blog",//这个就是我们自定义的模板布局 async asyncData (ctx) {// asyncData() 异步获取数据方法
  18. let blogs = []; try {
  19. console.log(1) const blogData = await Vue.http.get("Blog?page=1&bcategory=技术博文"); console.log(blogData);
  20. blogs = blogData; return {
  21. blogs: blogs,
  22. };
  23. } catch (e) { //ctx.error({ statusCode: 500, message: "出错啦" });//自定义错误页面
  24. }
  25. },
  26. data () { return {};
  27. },
  28. head () {//针对每一个页面,进行封装 head
  29. return {
  30. meta: [
  31. {
  32. name: "description",
  33. content: "老张的哲学是个人博客,利用NUXT.js的服务端渲染方案" }
  34. ]
  35. };
  36. },
  37. mounted () {},
  38. filters: {//过滤器,用来过滤时间字符串
  39. timeFormat: function (time) { if (!time) return ""; return time;
  40. }
  41. },
  42. methods: {},
  43. components: {
  44. }
  45. }; </script>

8、启动项目,就能看到我们的数据了

提示:如果报 less 错误,请安装

npm install less less-loader

13652172-65c6f9b0f20899f4.gif
image

三、页面是如何一步步加载数据的 ? —— 存疑

这里先给大家抛出几个问题:

1、我们通过第一次编译的时候,生成 .nuxt 临时文件夹,是服务端渲染还是客户端渲染?

2、我们打开浏览器的 调试工具,发现每次修改,会都生成一些提示,当然这都是 webpack 的热加载,那这些又是什么含义呢?

13652172-9fa1ed546a0d37d5.png
image

3、既然是服务端和客户端一起渲染,我们的 页面路由 是如何匹配到的呢?

4、打开我们的页面 network 网络请求,发现有很多不知道的文件,都是怎样生成的呢?

13652172-e6f7e7d57db0d357.png
image

其实这几个问题我们在之前的都通过文字的形式说过,因为时间的问题,今天暂时就说到这里,明天咱们再继续深入研究这个问题,顺便填充下详情页的数据。

四、CODE

https://github.com/anjoy8/Blog.Vue.Nuxt

QQ群:
867095512 (blod.core)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/971361
推荐阅读
相关标签
  

闽ICP备14008679号