回顾
哈喽大家好! 又是元气满满的周~~~ 二哈哈, 不知道大家中秋节过的如何, 马上又是国庆节了, 博主我将通过三天的时间, 给大家把项目二的数据添上(这里强调下, 填充数据不是最重要的, 最重要的是要配合着让大家明白 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 的核心 -- 渲染.
零, 今天要完成蓝色的部分
一, 重点温习框架中的几个部分文件 -- 铺垫
1,nuxt.config.JS 文件
项目的核心文件, 作为一个配置文件, 对全局配置起到一个十分重要的作用, 像我们的 webconfig 一样
- // 这些配置在你的项目里可能不一定都有, 但是我都会提到
- module.exports = {
- cache: {},
- 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
- ],
- // 默认 true
- dev: process.env.NODE_ENV !== 'production',// 不是生产环境
- // 创建环境变量
- env: {},
- // 配置 Nuxt.JS 应用生成静态站点的具体方式.
- genetate: {
- dir: '',
- minify: '',
- routes: [],
- },
- /*
- * vue-meta
- * Headers of the page
- */
- head: {
- title: '老张的哲学',
- meta: [
- { charset: 'utf-8' },
- { name: 'viewport', content: 'width=device-width, initial-scale=1' },
- { hid: 'description', name: 'description', content: 'Nuxt.JS project' }
- ],
- link: [
- { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
- ]
- },
- /*
- ** Customize the progress bar color
- */
- loading: { color: '#3B8070' },
- /*
- ** Build configuration
- */
- build: {
- /*
- ** Run ESLint on save
- */
- extend (config, { isDev, isClient }) {
- if (isDev && isClient) {
- config.module.rules.push({
- enforce: 'pre',
- test: /\.(JS|vue)$/,
- loader: 'eslint-loader',
- exclude: /(node_modules)/
- })
- }
- }
- },
- performance: {
- gzip: false,
- prefetch: true
- },
- // 引入 Vue.use 的插件
- plugins: [],
- // 默认当前路径
- rootDir: process.cwd(),
- router: {
- base: '',
- mode: 'history',
- linkActiveClass: 'nuxt-link-active',
- scrollBehavior: (to, from, savedPosition) => {
- // savedPosition 只有在 popstate 导航 (如按浏览器的返回按钮) 时可以获取.
- if (savedPosition) {
- return savedPosition
- } else {
- let position = {}
- // 目标页面子组件少于两个
- if (to.matched.length <2) {
- // 滚动至页面顶部
- position = { x: 0, y: 0 }
- }
- else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
- // 如果目标页面子组件中存在配置了 scrollToTop 为 true
- position = { x: 0, y: 0 }
- }
- // 如果目标页面的 url 有锚点, 则滚动至锚点所在的位置
- if (to.hash) {
- position = { selector: to.hash }
- }
- return position
- }
- },
- // default
- middleware: 'user-agent',
- // 扩展路由
- extendRoutes: () => {},
- // 默认同 rootDir
- srcDir: this.rootDir,
- transition: {
- name: 'page',
- mode: 'out-in'
- },
- watchers: {
- chokidar: {}, // 文件监控
- webpack: {
- aggregateTimeout: 300,
- poll: 1000
- }
- }
- }
- }
2. 视图模板
默认的 HTML 模版: 应用根目录下的 App.HTML 文件, 如果你没有找到这个文件, 则采用默认的模版, 当然你也可以自己新增, 配置,
这个更像是我们之前的 index.HTML 页面, 只不过把 <div id='App'></div> 挂载, 变成了填充的方式.
- <!DOCTYPE HTML>
- <HTML {{ HTML_ATTRS }}>
- <head>
- {{ HEAD }}
- </head>
- <body {{ BODY_ATTRS }}>
- {{ App }}
- </body>
- </HTML>
3. layouts 布局目录
可以修改该目录下的 default.vue 来修改默认布局 , 这个就是类似于我们之前的 App.vue 页面,
- <template>
- <div class="layout-default">
- <cl-header></cl-header>
- <nuxt class="layout-main"/>
- <cl-footer></cl-footer>
- <div class="layout-bg"></div>
- </div>
- </template>
其中 <nuxt/> 是必需的, 页面的主体内容会显示在这里 (类似于根节点的 <router-view/>)
此外还可以在目录下新增 http://www.PHP.cn/PHP/PHP-tp-append.HTML error.vue 作为错误页面, 具体的写法可以参考官方文档
4. pages 页面 路由
路由, 约定大于配置, 支持动态, 嵌套, 动态嵌套路由, 过渡效果和中间件, 通过文件夹目录名称, 组件名称, 生成路由配置, 默认的 transitionName 为 page, 可在 assets 中添加全局的过渡效果,
在匹配页面之前执行;
nuxt.config.JS --> 执行 middleware --> 匹配布局 --> 匹配页面
用于存放页面级别的组件, nuxt 会根据该目录下的页面结构生成路由
比如上图中的页面结构, 会生成这样的路由配置:
- 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)
- export function createRouter () {
- return new Router({
- mode: 'history',
- base: '/',
- linkActiveClass: 'nuxt-link-active',
- linkExactActiveClass: 'nuxt-link-exact-active',
- scrollBehavior,
- routes: [
- {
- path: "/post",
- component: _7b01ffaa,
- name: "post"
- },
- {
- path: "/post/:id",
- component: _2b7fe492,
- name: "post-id"
- },
- {
- path: "/",
- component: _4f14dfca,
- name: "index"
- }
- ],
- fallback: false
- })
- }
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 资源文件夹中
2, 在 components 中, 新建 layout 文件夹, 然后新增页头页脚 组件
这个很简单, 就是普通的 *.vue 组件写法, 大家可以自行下载浏览
3, 在 layouts 页面布局文件夹中, 新增 blog.vue 布局
提醒: 以后也可以单给用户增加布局, 比如 user.vue
- <template>
- <div class="layout-default">
- <cl-header></cl-header>
- <nuxt class="layout-main"/>// 注意,<nuxt />, 必须有, 类似一个 <router-view/>
- <cl-footer></cl-footer>
- </div>
- </template>
- <script type="text/JavaScript">
- import clHeader from "~/components/layout/header.vue";
- import clFooter from "~/components/layout/footer.vue";
- export default {
- data () {
- return {};
- },
- mounted () {
- },
- components: {
- clHeader,
- clFooter
- }
- };
- </script>
很简单的一个布局入口, 是不是很像我们之前的 App.vue 中的 路由容器 -- <router-view /> , 但是又比其更丰富, 因为我们之前的 App.vue 只能有一个入口, 但是 nuxt 可以提供多个自定义 模板 layouts, 更方便.
4, 根目录新增 config 文件夹, 添加 index.JS , 作为我们以后的配置文件, 类似 .net core API 中的 appsetting.JSON 文件
- const config = {
- // 开发环境配置, 开发的时候
- development: {
- //API: "http://localhost:58427/API/",
- API: "http://123.206.33.109:8081/API/"
- },
- // 生产环境配置, 部署的时候
- production: {
- API: ""
- }
- };
- // 获取当前环境变量, 是 production 或者 development
- module.exports = config[process.env.NODE_ENV];
5, 在 plugins 插件中, 新增 server_site 文件夹, 然后添加 http.JS 和 index.JS
1, 为什么要使用插件?
我们可以在应用中使用第三方模块, 一个典型的例子是在客户端和服务端使用 https://GitHub.com/mzabriskie/axios 做 HTTP 请求.
首先我们需要安装 NPM 包:
NPM install --save axios
然后在页面内可以这样使用:
- <template>
- <h1>{{ title }}</h1>
- </template>
- <script>
- import axios from 'axios'
- export default {
- async asyncData ({ params }) {
- let { data } = await axios.get(`https://my-API/posts/${params.id}`)
- return { title: data.title }
- }
- }
- </script>
有一个值得注意的问题是, 如果我们在另外一个页面内也引用了 axios, 那么在应用打包发布的时候 axios 会被打包两次, 而实际上我们只需要打包一次. 这个问题可以通过在 nuxt.config.JS 里面配置 build.vendor 来解决:
- module.exports = {
- build: {
- vendor: ['axios']
- }
- }
经过上面的配置后, 我们可以在任何页面里面引入 axios 而不用担心它会被重复打包.
2, 为什么要分 server_site 服务端 与 client_site 客户端 插件
有些插件可能只是在浏览器里使用, 所以你可以用 ssr: false 变量来配置插件只从客户端还是服务端运行.
举个栗子:
- nuxt.config.JS:
- module.exports = {
- plugins: [
- { src: '~/plugins/vue-notifications', ssr: false }// 设置 ssr 为 false
- ]
- }
- plugins/vue-notifications.JS:// 定义一个插件
- import Vue from 'vue'
- import VueNotifications from 'vue-notifications'
- Vue.use(VueNotifications)
同样地, 如果有些脚本库你只想在服务端使用, 在 Webpack 打包 server.bundle.JS 文件的时候会将 process.server 变量设置成 true.
3, 配置服务端 http.JS 和 index.JS 的内容
- // http.JS 封装 axios, 防止多处打包
- import Axios from "axios";
- import config from "~/config";// 引入配置文件
- // 实例化 axios()
- const http = Axios.create({
- baseURL: config.API,// 根 url
- timeout: 8000,
- validateStatus: function (status) {
- return status>= 200;
- }
- });
- // 定义错误异常方法
- function LogicError (message, code, data) {
- this.message = message;
- this.code = code;
- this.data = data;
- this.name = "LogicError";
- }
- LogicError.prototype = new Error();
- LogicError.prototype.constructor = LogicError;
- // http 的 request 请求
- http.interceptors.request.use((data, headers) => {
- return data;
- });
- // http 的 response 命令, 失败的调用上边的失败异常方法
- http.interceptors.response.use(response => {
- const data = response.data;
- switch (data.success) {
- case true:
- return data.data;
- default:
- throw new LogicError(data.msg);
- }
- }, err => {
- throw new LogicError("网络请求错误");
- });
- export default http;// 输出 http
- // 定义 http 插件, 是一个全局变量
- import Vue from "vue";
- import http from "./http.JS";// 引入 http 封装的 axios
- const install = function (VueClass, opts = {}) {
- // http method
- VueClass.http = http;
- VueClass.prototype.$http = http;
- };
- Vue.use(install);// 在 vue 中, 使用该插件
6, 在 nuxt.config.JS 中引用我们的服务端插件
这样添加以后, 我们就可以全局使用请求命令了, 打包的时候, 也只会打包一个
提示: 1, 记得需要按照提示安装 axios NPM install --save axios
2, 引入的组件库必须配置 plugins, 但是有的组件库不支持 ssr.
7, 设计修改 pages 下的 index.vue 页面, 异步获取接口数据
提示: 这个就是文章开头提到的问题
1, 为什么要异步?
asyncData 方法会在组件 (限于页面组件, 也就是 pages 文件夹下的 vue 文件) 每次加载之前被调用. 它可以在服务端或路由更新之前被调用. 在这个方法被调用的时候, 第一个参数被设定为当前页面的上下文对象, 你可以利用 asyncData 方法来获取数据, Nuxt.JS 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件.
注意: 由于 asyncData 方法是在组件 初始化 前被调用的, 所以在方法内是没有办法通过 this 来引用组件的实例对象.
Nuxt.JS 提供了几种不同的方法来使用 asyncData 方法, 你可以选择自己熟悉的一种来用:
返回一个 Promise, nuxt.JS 会等待该 Promise 被解析之后才会设置组件的数据, 从而渲染组件.
使用 async 或 await https://GitHub.com/lukehoban/ecmascript-asyncawait (推荐使用)
返回 Promise
- export default {
- asyncData ({ params }) {
- return axios.get(`https://my-API/posts/${params.id}`)
- .then((res) => {
- return { title: res.data.title }
- })
- }
- }
使用 async 或 await
- export default {
- async asyncData ({ params }) {
- let { data } = await axios.get(`https://my-API/posts/${params.id}`)
- return { title: data.title }
- }
- }
- <!-- index.vue 页面 -->
- <template>
- <div class="u-marginBottom40 JS-collectionStream">
- <div class="streamItem streamItem--section JS-streamItem">
- <div class="u-clearfix u-maxWidth1000 u-marginAuto">
- <div class="row u-marginTop30 u-marginBottom20 u-sm-marginLeft20 u-sm-marginRight20 u-xs-marginTop0 u-xs-marginRight0 u-xs-marginLeft0">
- <div v-for="post in blogs" :key="post.bID" class="postArticle postArticle--short is-withAccentColors">
- <!--......-->
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import Vue from "vue";// 引入 vue 实例, 获取全局变量
- export default {
- layout: "blog",// 这个就是我们自定义的模板布局
- async asyncData (ctx) {// asyncData() 异步获取数据方法
- let blogs = [];
- try {
- console.log(1)
- const blogData = await Vue.http.get("Blog?page=1&bcategory = 技术博文");
- console.log(blogData);
- blogs = blogData;
- return {
- blogs: blogs,
- };
- } catch (e) {
- //ctx.error({ statusCode: 500, message: "出错啦" });// 自定义错误页面
- }
- },
- data () {
- return {};
- },
- head () {// 针对每一个页面, 进行封装 head
- return {
- meta: [
- {
- name: "description",
- content: "老张的哲学是个人博客, 利用 NUXT.JS 的服务端渲染方案"
- }
- ]
- };
- },
- mounted () {},
- filters: {// 过滤器, 用来过滤时间字符串
- timeFormat: function (time) {
- if (!time) return "";
- return time;
- }
- },
- methods: {},
- components: {
- }
- };
- </script>
8, 启动项目, 就能看到我们的数据了
提示: 如果报 Less 错误, 请安装
NPM install Less Less-loader
三, 页面是如何一步步加载数据的 ? -- 存疑
这里先给大家抛出几个问题:
1, 我们通过第一次编译的时候, 生成 .nuxt 临时文件夹, 是服务端渲染还是客户端渲染?
2, 我们打开浏览器的 调试工具, 发现每次修改, 会都生成一些提示, 当然这都是 webpack 的热加载, 那这些又是什么含义呢?
3, 既然是服务端和客户端一起渲染, 我们的 页面路由 是如何匹配到的呢?
4, 打开我们的页面 network 网络请求, 发现有很多不知道的文件, 都是怎样生成的呢?
其实这几个问题我们在之前的都通过文字的形式说过, 因为时间的问题, 今天暂时就说到这里, 明天咱们再继续深入研究这个问题, 顺便填充下详情页的数据.
四, CODE
https://GitHub.com/anjoy8/Blog.Vue.Nuxt
来源: https://www.cnblogs.com/laozhang-is-phi/p/9697450.html