Nuxt

笔记来自
Github实例

框架原理

  • 把Vue原本的dev开发模式改成Nuxt
  • 修改Vue的代码,让Vue的mount前的生命周期在服务器端完成
  • 这样改造后把本地的整个项目的代码包括node_modules一起移到服务器启动就行

迁移步骤

  • 按官网的步骤来就行
// npx命令安装了Node就自带了
npx create-nuxt-app <项目名>

// 选择NodeJS框架 Koa
// 选择UI框架
// 选择测试框架
// 完成
  • 配置文件
// nuxt.config.js
// 没有vue.config.js了

// 这个文件用来配置服务器的端口
// 渲染html页面的head标签数据,link,script,就是全局的css和js,都是用cdn地址
// 还有css和plugins,注意文件路径

module.exports = {
  mode: 'universal',
  server: {
    port: 8000,
    host: '127.0.0.1'
  },
  render: {
    csp: true
  },
  // axios的请求前缀,跟上面的port一致
  env: {
    baseUrl: 'http://127.0.0.1:8000'
  },
  router: {
    middleware: ['auth', 'i18n'],
    extendRoutes (routes, resolve) {
      routes.push({
        path: '/',
        redirect: {
          name: 'timeline-title'
        }
      })
    }
  },
  head: {
    title: 'title',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ],
    script: []
  },
  css: [
    '~/assets/css/reset.css',
    '~/assets/css/page-transition.css',
    '~/assets/scss/global.scss'
  ],
  plugins: [
    '~/plugins/axios.js',
    '~/plugins/request.js',
    '~/plugins/api.js',
    '~/plugins/vue-global.js'
  ],
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/style-resources',
    'cookie-universal-nuxt'
  ],
  axios: {
  },
  build: {
    babel: {
      plugins: [
        [
          "component",
          {
            "libraryName": "element-ui",
            "styleLibraryName": "theme-chalk"
          }
        ]
      ],
    },
    extend (config, ctx) {
    }
  }
}
  • Nuxt的路由
// 把 <router-link to="/">首页</router-link> 改成
<nuxt-link to="/">首页</nuxt-link>
// 用$router跳转的就不用改了
// Nuxt是没有vue-router的
// 他的路由是用文件路径生成的,比如
pages/
--| _slug/
-----| index.vue
--| users/
-----| _id.vue // 注意这里是下划线
--| index.vue
// 生成
router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    }
  ]
}
  • Nuxt的app.vue
// 以前的app.vue是  <router-view />
// 现在是在layouts/xx.vue自定义的
// 默认有一个叫default.vue
// 内容显示在 <nuxt /> 里

// 如果文件里没有error.vue,需要自己写一个,就随便写一个,写上404就行
// 那指定哪个layout在哪配置呢,往下看
  • Vue文件的修改
// 在原来的Vue的属性和方法的基础上,添加了下面的熟悉和方法
// 这些方法是用在渲染的时候的

// asyncData,最重要的一个键, 支持 异步数据处理,另外该方法的第一个参数为当前页面组件的 上下文对象。
// head,配置当前页面的 Meta 标签, 详情参考 页面头部配置API。
// layout,指定当前页面使用的布局(layouts 根目录下的布局文件)。详情请参考 关于 布局 的文档。
// loading,如果设置为false,则阻止页面自动调用加载提示
// transition,指定页面切换的过渡动效, 详情请参考 页面过渡动效。
// scrollToTop,布尔值,默认: false。 用于判定渲染页面前是否需要将当前页面滚动至顶部。这个配置用于 嵌套路由的应用场景。
// validate,校验方法用于校验 动态路由的参数。
// middleware,指定页面的中间件,中间件会在页面渲染之前被调用, 请参考 路由中间件

// 放一个例子
// 这个方法需要主动调用,等于是自定义生命周期函数,而且必须是这个名字
// 这个方法代替了vue的 beforeCreate 和 created 两个生命周期
// mounted 还是在客户端执行,mounted不应该写数据请求,防止重复请求
// 结构参数,app是this实例,params是请求的url,store是vuex实例
async asyncData({ app, params, store }) {
  // 可以在asyncData获取和修改store
  // 往下看可以发现这个categoryList的数据是来自validate的
  let categoryList = store.state.category.recommendCategoryList
  categoryList[0].name = "text";
  store.commit('category/UPDATE_TIMELINE_CATEGORY_LIST', categoryList)
  
  // 作者榜单
  let res = await app.$api.getAuthorRank({
    channel: params.name,
    after: '',
    first: 20
  }).then(res => res.s === 1 ? res.d : {})
  return {
    categoryList,
    authors: res.edges,
    pageInfo: res.pageInfo
  }
},
head() {
  return {
    title: this.pageInfo.name || 'xxx'
  }
},
async validate ({ app, params, store }) {
  // validate是执行在asyncData之前的 
  // 可以在validate获取和修改store
  if (params.id && params.id != 'undefined') {
    let categoryList = []
    // 获取分类列表缓存
    if (store.state.category.timelineCategoryList.length) {
      categoryList = store.state.category.timelineCategoryList
    } else {
      categoryList = await app.$api.getCategories().then(res => res.s === 1 ? initCategoryList.concat(res.d.categoryList) : initCategoryList)
      store.commit('category/UPDATE_TIMELINE_CATEGORY_LIST', categoryList)
    }
    return true
  }
  return false
},
// 下面就是原Vue的老代码
data() {
  return {
    pinDetail: {},
    page: 1,
    comments: []
  }
},
...
  • 上面我们使用了vuex
// 但是为什么在nuxt.config.js的plugins里没有看到引入
// 因为nuxt自带了,只要项目里有个store的文件夹
// 放入js就行,js里是函数型的state 和 同步的mutations

// text.js
export const state = () => ({
  isTopbarBlock: true, // 顶部栏是否显示
})

export const mutations = {
  UPDATE_TOPBAR_BLOCK(state, payload){
    state.isTopbarBlock = payload
  }
}

// 在page的validate里用commit执行
store.commit('文件名/UPDATE_TOPBAR_BLOCK', 参数)
  • 请求三人组
// Nuxt不能用正常的axios,一定要用@nuxtjs/axios,他默认让vue.$axios = axios
// 还有cookie-universal-nuxt插件,这个插件默认让vue.$cookie = 请求用户的cookie

// plugins/axios.js,对axios进行配置
export default function ({ app: { $axios, $cookies } }) {	 
   $axios.defaults.baseURL = process.env.baseUrl
   $axios.defaults.timeout = 30000
   $axios.interceptors.request.use(config => {
	config.headers['X-Token'] = $cookies.get('token') || ''
	config.headers['X-Device-Id'] = $cookies.get('clientId') || ''
	config.headers['X-Uid'] = $cookies.get('userId') || ''
	return config
   })
   $axios.interceptors.response.use(response => {
	if (/^[4|5]/.test(response.status)) {
	   return Promise.reject(response.statusText)
	}
	   return response.data
   })
}

// plugins/request.js
export default ({ app: { $axios } }, inject) => {
  let requestList = {}
  let methods = ['get', 'post', 'put', 'delete']
  methods.forEach(method => {
    let dataKey = method === 'get' ? 'params' : 'data'
    requestList[method] = function(url, data) {
      return $axios({
        method,
        url,
        [dataKey]: data
      }).catch(err => {
        console.error(err)
        return {
          s: 0,
          d: {},
          errors: [err]
        }
      })
    }
  })
  inject('request', requestList)
}

// plugins/api.js
export default ({ app: { $request } }, inject) => {
  inject('api', {
    /**
     * 登录验证
     * @param {string} password - 密码
     * @param {string} phoneNumber - 手机号码
     */
    loginAuth(data) {
      return $request.post('/v1/auth/login', data)
    },
    /**
     * 身份验证
     */
    isAuth() {
      return $request.get('/v1/auth/authentication')
    }
    ...
  })
}
  • Vue全局设置
// plugin/vue.global.js

// 在这里配置一些组件和过滤器
import Vue from 'vue'
import xxx from '~/components/xxx'
import utils from '~/utils/utils'

Vue.use(xxx)

Vue.filter('formatTime', d => utils.formatTime(d))

export default function (context, inject) {
  inject('utils', utils)
}
  • 服务器搭建,这里用Koa
// server/index.js

const fs = require('fs')
const Koa = require('koa')
const cors = require('koa2-cors')
const helmet = require('koa-helmet')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = new Koa()
const router = new Router()

// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = app.env !== 'production'

function useMiddleware(){
  app.use(helmet())
  app.use(bodyParser())
  //设置全局返回头
  app.use(cors({
    origin: function(ctx) {
      return 'http://localhost:8000'; //cors
    },
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
    maxAge: 86400,
    credentials: true,  // 允许携带头部验证信息
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
    allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Token', 'X-Device-Id', 'X-Uid'],
  }))
}

function useRouter(path){
  // 路由就是接口,这里怎么配置就不写了
}

async function start () {
  // Instantiate nuxt.js
  const nuxt = new Nuxt(config)

  const {
    host = process.env.HOST || '127.0.0.1',
    port = process.env.PORT || 3000
  } = nuxt.options.server

  await nuxt.ready()
  // Build in development
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  }
  useMiddleware()
  useRouter()
  app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
    nuxt.render(ctx.req, ctx.res)
  })
  app.listen(port, host)
}

start()
  • 到这里就大致都改好了,看看Nuxt的命令有哪些
"scripts": {
  "dev": "cross-env NODE_ENV=development nodemon --inspect server/index.js --watch server",
  "build": "nuxt build",
  "start": "nuxt start",
  "generate": "nuxt generate",
  "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
},
  • 执行npm run dev,会打包出一个.nuxt文件,顺便打开服务器,直接访问就行,如果要放到服务器,就把所有的代码移到服务器,同样的方法启动就行

研究一下.nuxt文件

// router.js,这个就是上面说到的自动生成的路由文件
// App.js,就是读取layouts文件夹生成出来的模板文件

// index.js,这个非常值得研究
// index.js把nuxt.config.js里的配置读取后生成出来的
// 最开始可以看到很多的plugins
// 然后引入可很多的component
// 还有一个inject方法,这个方法在上面的请求三人组里可以看到很多次,就是在这里调用的,是一个固定写法,用来给Vue实例注册$xx属性的,看代码

const inject = function (key, value) {
  if (!key) {
    throw new Error('inject(key, value) has no key provided')
  }
  if (value === undefined) {
    throw new Error(`inject('${key}', value) has no value provided`)
  }
  key = '$' + key
  // Add into app
  app[key] = value
}

// server.js,这个就是服务器渲染的核心
// 在这里把请求的路径解析后,知道请求的是哪个页面的路由,然后读取文件,看代码
export default async (ssrContext) => {
  
  ssrContext.redirected = false
  ssrContext.next = createNext(ssrContext)
  ssrContext.beforeRenderFns = []
  ssrContext.nuxt = { layout: 'default', data: [], fetch: [], error: null, state: null, serverRendered: true, routePath: '' }

  const { app, router, store } = await createApp(ssrContext)
  const _app = new Vue(app)

  ssrContext.nuxt.routePath = app.context.route.path
  ssrContext.meta = _app.$meta()
  ssrContext.asyncData = {}

  const beforeRender = async () => {
    await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
    ssrContext.rendered = () => {
      ssrContext.nuxt.state = store.state
    }
  }

  /*
  ** Set layout
  */
  let layout = Components.length ? Components[0].options.layout : NuxtError.layout
  if (typeof layout === 'function') {
    layout = layout(app.context)
  }
  await _app.loadLayout(layout)
  if (ssrContext.nuxt.error) {
    return renderErrorPage()
  }
  layout = _app.setLayout(layout)
  ssrContext.nuxt.layout = _app.layoutName

  // asyncDatas非常的核心
  const asyncDatas = await Promise.all(Components.map((Component) => {
    const promises = []

    if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
      const promise = promisify(Component.options.asyncData, app.context)
      promise.then((asyncDataResult) => {
        ssrContext.asyncData[Component.cid] = asyncDataResult
        applyAsyncData(Component)
        return asyncDataResult
      })
      promises.push(promise)
    } else {
      promises.push(null)
    }

    // Call fetch(context)
    if (Component.options.fetch && Component.options.fetch.length) {
      promises.push(Component.options.fetch(app.context))
    } else {
      promises.push(null)
    }

    return Promise.all(promises)
  }))

  await beforeRender()

  return _app
}

如果有必须使用的js,但不需要在服务端使用的js

  • 比如mavon-editor
// nuxt.config.js 中添加plugins配置
plugins: [
  ...
  { src: '@/plugins/vue-mavon-editor', ssr: false }
],


// 在页面或者组件中引入套上<no-ssr>
<template>
  <div class="mavonEditor">
    <no-ssr>
      <mavon-editor :toolbars="markdownOption" v-model="handbook"/>
    </no-ssr>
  </div>
</template>

我还是不会用怎么办
把上面的github下载下来,没用的代码删了,改成自己的就行

posted @ 2020-06-09 10:20  一个年轻淫  阅读(738)  评论(0编辑  收藏  举报