Nuxt初識

服務端渲染

服務器知識:koa、node.js
SSR原理:
*將同⼀個組件渲染爲服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最後將這些靜態標記"激
活"爲客戶端上徹底可交互的應⽤程序。*
應⽤場景:css

  1. 應⽤須要更好的 SEO
  2. 應⽤須要更快的內容到達時間 (⾸屏渲染時間),特別是對於緩慢的⽹絡狀況或運⾏緩慢的設備。

ssr的侷限:html

  1. 更⼤的服務器端負載
  2. 較⾼的學習成本
  3. ⼀些外部擴展庫使⽤會受限

nuxt安裝
npx create-nuxt-app <項⽬名>
啓動
npm run devvue

⽬錄結構node

assets:資源⽬錄 assets ⽤於組織未編譯的靜態資源如 LESS 、 SASS 或 JavaScript 。jquery

components:組件⽬錄 components ⽤於組織應⽤的 Vue.js 組件。Nuxt.js 不會擴展加強該⽬錄下webpack

Vue.js 組件,即這些組件不會像⻚⾯組件那樣有 asyncData ⽅法的特性。ios

layouts:佈局⽬錄 layouts ⽤於組織應⽤的佈局組件。web

middleware: middleware ⽬錄⽤於存放應⽤的中間件。ajax

pages:⻚⾯⽬錄 pages ⽤於組織應⽤的路由及視圖。Nuxt.js 框架讀取該⽬錄下全部的 .vue ⽂vuex

件並⾃動⽣成對應的路由配置。

plugins:插件⽬錄 plugins ⽤於組織那些須要在 根vue.js應⽤ 實例化以前須要運⾏的

Javascript 插件。
static:靜態⽂件⽬錄 static ⽤於存放應⽤的靜態⽂件,此類⽂件不會被 Nuxt.js 調⽤ Webpack 進⾏構建編譯處理。 服務器啓動的時候,該⽬錄下的⽂件會映射⾄應⽤的根路徑 / 下。
store: store ⽬錄⽤於組織應⽤的 Vuex 狀態樹 ⽂件。 Nuxt.js 框架集成了 Vuex 狀態樹 的相關功 能配置,在 store ⽬錄下建立⼀個 index.js ⽂件可激活這些配置。
nuxt.confifig.js: nuxt.config.js ⽂件⽤於組織Nuxt.js 應⽤的個性化配置,以便覆蓋默認配置。

約定優於配置

約定優於配置
路由:pages⽬錄中全部 *.vue ⽂件⽣成應⽤的路由配置
導航:

<nuxt-link to="/">⾸⻚</nuxt-link>

嵌套:製造⼀個.vue⽂件和⽂件夾同名

pages/
--| main/
--| main.vue

動態路由:⽂件名或者⽂件夾名稱要帶_

pages/
--| main/
-----| detail/
--------| _id.vue

⻚⾯

⾃定義佈局:

  1. 建立layouts/main.vue
<template>
    <div>
    <nav>
      <n-link to="/main">index</n-link>
      <!-- 別名 -->
      <n-link to="/main/admin" no-prefetch>管理</n-link>
      
      <N-link to="/main/cart">gouwu</N-link>
    </nav>
    <nuxt-child />
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style lang="scss" scoped>

</style>

pages/login.vue

<template>
    <div>
        <h1>用戶登陸</h1>
        <el-input v-model="user.username"></el-input>
        <el-input type="password" v-model="user.password"></el-input>
        <el-button @click="onLogin">登陸</el-button>
    </div>
</template>

<script>
    export default {
        data(){
            return {
                user:{
                    username:"",
                    password:""
                }
            }
        },
        methods:{
            onLogin(){
                this.$store.dispatch("user/login",this.user).then(ok=>{
                    if(ok){
                        this.$router.push("/")
                    }
                })
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>
  1. ⻚⾯中引⼊pages/main.vue
export default {
  layout: 'main',
};

main/admin.vue

<template>
    <div>
        <h1>商品管理頁</h1>
    </div>
</template>

<script>
    export default {
        middleware: 'auth'
    }
</script>

<style lang="scss" scoped>

</style>

/main/cart.vue

<template>
    <div>
        <h1>購物車</h1>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style lang="scss" scoped>

</style>

mian/index.vue

<template>
  <div>
    <h1>首頁</h1>
    <ul>
      <li v-for="good in goods" :key="good.id">
        <nuxt-link :to="{name:'main-detail-id',params:{id:good.id}}">
        <span>{{good.text}}</span>
        <span>¥{{good.price}}</span>
        <button @click="addCart(good)">加購物車</button>
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>


export default {
  head(){//head裏面項目
    return{
      title:"列表",
      meta:[{name:"description",hid:"description",content:"set page meta"}],
      link:[{rel:"favicon",href:"favicon.ico"}],
      script:[{src:"https://cdn.jsdelivr.net/npm/jquery/dist/jquery.js"}]
    }
  },
  layout:'users',
  // 在這個組件建立以前發送ajax請求 數據回來以後纔開始建立 優先級大於下面的data
  async asyncData({$axios,error,redirect}) {
    //asyncData早於時間點 因此不能用this訪問組件實例
    const result = await $axios.$get('/api/goods')
    if(result.ok){
      return {goods: result.goods}
    }
    //錯誤處理
    error({statusCode: 400,message:"查詢數據失敗"})
  },
  data(){
    return {
      goods:[
        {id:1,text:'Web',price:4444},
        {id:2,text:'移動',price:5555}
      ]
    }
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 100px;
  color: #35495e;
  letter-spacing: 1px;
}

.subtitle {
  font-weight: 300;
  font-size: 42px;
  color: #526488;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}
</style>

mian/detail/_id.vue

<template>
    <div>
        <h2>商品的詳情</h2>
        ${{goodInfo}}
    </div>
</template>

<script>
    export default {
        async asyncData(ctx) {
            console.log(ctx)
            const {$axios,params,error} = ctx
            //不能經過this獲取組件實例 可是能夠經過上下文獲取相關數據
            const result = await $axios.$get("/api/detail",{params})
            if(result.data){
                return {goodInfo: result.data}
            }
            error({statusCode:400,message:"商品詳情查詢失敗"})
        },
    }
</script>

<style lang="scss" scoped>

</style>

異步數據:
asyncData
它在組件建立以前執⾏,⾥⾯不要⽤this訪問組件實例
第⼀個參數是上下⽂
能夠在服務端也能夠客戶端都執⾏
asyncData會和data融合

<template>
  <div>
    <h1>首頁</h1>
    <ul>
      <li v-for="good in goods" :key="good.id">
        <nuxt-link :to="{name:'main-detail-id',params:{id:good.id}}">
        <span>{{good.text}}</span>
        <span>¥{{good.price}}</span>
        <button @click="addCart(good)">加購物車</button>
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>


export default {
  head(){//head裏面項目
    return{
      title:"列表",
      meta:[{name:"description",hid:"description",content:"set page meta"}],
      link:[{rel:"favicon",href:"favicon.ico"}],
      script:[{src:"https://cdn.jsdelivr.net/npm/jquery/dist/jquery.js"}]
    }
  },
  layout:'users',
  // 在這個組件建立以前發送ajax請求 數據回來以後纔開始建立 優先級大於下面的data
  async asyncData({$axios,error,redirect}) {
    //asyncData早於時間點 因此不能用this訪問組件實例
    const result = await $axios.$get('/api/goods')
    if(result.ok){
      return {goods: result.goods}
    }
    //錯誤處理
    error({statusCode: 400,message:"查詢數據失敗"})
  },
  data(){
    return {
      goods:[
        {id:1,text:'Web',price:4444},
        {id:2,text:'移動',price:5555}
      ]
    }
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 100px;
  color: #35495e;
  letter-spacing: 1px;
}

.subtitle {
  font-weight: 300;
  font-size: 42px;
  color: #526488;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}
</style>

接⼝準備:

  1. 建立server/api.js
const router = require("koa-router")({prefix:"/api"})
const goods = [
    {id:1,text:"web",price:1999},
    {
      id: 2,
      text: "移動",
      price: 199
    },
];
router.get("/goods",ctx => {
    ctx.body = {
        ok: 1,
        goods
    }
});
router.get("/detail",ctx => {
    ctx.body = {
        ok:1,
        data:goods.find(good => good.id == ctx.query.id)
    }
});
router.post("/login",ctx => {
    const user = ctx.request.body
    if(user.username === "jerry" && user.password ==='123'){
        // 將token存入cookie
        const token = 'a mock token'
        ctx.cookies.set('token',token)
        ctx.body = {
            ok:1,token
        }
    } else {
        ctx.body = {ok: 0}
    }
});
module.exports = router

2. 修改服務器配置,server/index.js

const Koa = require('koa')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
// 解析post的數據
const bodyparser = require("koa-bodyparser")
const api = require('./api')
const app = new Koa()
// 設置cookie加密祕鑰
app.keys = ["some secret","another secret"]
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = app.env !== 'production'

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()
  }
// 解析post數據並註冊路由
  app.use(bodyparser())
  app.use(api.routes())
  // 頁面渲染
  
  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)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}

start()

中間件

中間件會在⼀個⻚⾯或⼀組⻚⾯渲染以前運⾏咱們定義的函數,常⽤於權限控制、校驗等任務。
⾸⻚重定向,建立middleware/index-redirect.js

export default function({route,redirect}){
    //中間件能夠得到上下文,裏面有各類有用信息
    //經常使用的有app,route,redirect,store
    if(route.path === '/'){
        redirect('/main')
    }
}

註冊中間件,nuxt.config.js

router: {
  middleware: ['index-redirect']
},

插件

Nuxt.js會在運⾏Vue應⽤以前執⾏JS插件,須要引⼊或設置Vue插件、⾃定義模塊和第三⽅模塊時特
別有⽤。
建立plugins/api-inject.js

// 注入接口 利用插件機制將服務接口注入組件實例,store實例中
export default({$axios},inject) => {
    inject("login",user => {
        return $axios.$post("/api/login",user)
    })
}

註冊插件,nuxt.config.js

plugins: [
  "@/plugins/api-inject"
],

vuex

⽤戶登陸及登陸狀態保存,建立store/user.js

// 應用根目錄若是存在Store目錄,Nuxt.js將啓用vuex狀態數,定義各狀態樹時具名導出state,mutations,getters.actions便可
export const state = () => ({
    token:''
})
export const mutations = {
    init(state,token){
        state.token = token
    }
}
export const getters = {
    isLogin(state) {
        return !!state.token
    }
}
export const actions = {
    login({commit},u){
        return this.$login(u).then(({token})=>{
            if(token){
                commit("init",token)
            }
            return !!token
        })
    }
}

使⽤狀態,建立中間件middleware/auth.js

export default function({redirect,store}){
    console.log("token:" + store.state.user.token)
    //經過vuex中令牌存在與否判斷是否登陸
    if(!store.state.user.token){
        redirect("/login")
    }

}

註冊該中間件,admin.vue

export default {
  middleware: 'auth'
}

狀態初始化,store/index.js

export const actions = {
    nuxtServerInit({commit},{req}){
        // 服務端就將vuex狀態填充
        // 參數1是vuex的上下文
        // 參數2是nuxt的上下文
        // req.ctx是KOA的上下文  由於在server/index 賦的值
        const token = req.ctx.cookies.get('token')
        if(token){
            console.log("初始化token")
            console.log(token)
            commit('user/init',token)
        }
    }
}

nuxt.config.js

module.exports = {
  mode: 'universal',
  /*
  ** Headers of the page
  */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#fff' },
  /*
  ** Global CSS
  */
  css: [
    'element-ui/lib/theme-chalk/index.css'
  ],
  //路由配置
  router: {
    // 順序從前日後執行
    middleware: ['index-redirect']
  },
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    '@/plugins/element-ui',
    '@/plugins/api-inject',
    {src:"@/plugins.axios",mode:"client"}//僅客戶端執行
  ],
  /*
  ** Nuxt.js dev-modules
  */
  buildModules: [
  ],
  /*
  ** Nuxt.js modules
  */
  modules: ['@nuxtjs/axios'],
  // axios: {
  //   proxy: true
  // },
  // proxy: {
  //   "api": "http://localhost:8080"
  // },
  /*
  ** Build configuration
  */
  build: {
    transpile: [/^element-ui/],
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    }
  }
}

pluygins/axios.js

export default function({$axios}){
    // 利用$axios模快幫忙方法setToken設置全局請求頭
    // 此處省略token截取邏輯
    $axios.setToken(document.cookie,'Bearer')
}

佈局

總體佈局 默認的
layouts/default.vue

<template>
  <div>
    <nuxt />
  </div>
</template>

<style>
html {
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: border-box;
  margin: 0;
}

.button--green {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #3b8070;
  color: #3b8070;
  text-decoration: none;
  padding: 10px 30px;
}

.button--green:hover {
  color: #fff;
  background-color: #3b8070;
}

.button--grey {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #35495e;
  color: #35495e;
  text-decoration: none;
  padding: 10px 30px;
  margin-left: 15px;
}

.button--grey:hover {
  color: #fff;
  background-color: #35495e;
}
</style>

自定義佈局
layouts/error.vue

<template>
    <div class="container">
        <h1 v-if="error.statusCode === 404">頁面不存在</h1>
        <h1 v-else>應用發生錯誤異常</h1>
        <nuxt-link to="/">首頁</nuxt-link>
    </div>
</template>

<script>
    export default {
        props:['error']
    }
</script>

<style lang="scss" scoped>

</style>

/layouts/users.vue

<template>
<div>
    <h1>自定義佈局</h1>
    <nuxt></nuxt>
</div>
</template>

image.png

相關文章
相關標籤/搜索