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下載下來,沒用的代碼刪了,改成自己的就行


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM