nuxt 学习

267 阅读4分钟

1介绍

1.1简介

基于vue的通用应用框架,支持服务端渲染。

1.2特点

  1. 路由生成
  2. 服务端渲染
  3. 代码分层
  4. 静态文件服务

1.3渲染流程

1.4安装

npx create-nuxt-app <项⽬名>

1.5代码结构

2.项目实战

2.0 代码结构

2.2路由

2.2.1路由⽣成

pages⽬录中所有 *.vue ⽂件⾃动⽣成应⽤的路由配置,新建:

  • pages/admin.vue 商品管理⻚
  • pages/login.vue 登录⻚

以下划线作为前缀的 .vue⽂件 或 ⽬录会被定义为动态路由,如下⾯⽂件结构

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

会⽣成如下路由配置:

{
path: "/detail/:id?",
component: _9c9d895e,
name: "detail-id"
}

如果detail/⾥⾯不存在index.vue,:id将被作为可选参数

2.2.2嵌套路由

创建内嵌⼦路由,你需要添加⼀个 .vue ⽂件,同时添加⼀个与该⽂件同名的⽬录⽤来存放⼦视图组件。

构造⽂件结构如下:

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

⽣成的路由配置如下:

{
path: '/detail',
component: 'pages/detail.vue',
children: [
{path: ':id?', name: "detail-id"}
]
}

测试代码,detail.vue

<template>
    <div>
        <h2>detail</h2>
        <nuxt-child></nuxt-child>
    </div>
</template> 

2.2.3配置路由

要扩展 Nuxt.js 创建的路由,可以通过 router.extendRoutes 选项配置。例如添加⾃定义路由:


export default {
    router: {
        extendRoutes(routes, resolve) {
            routes.push({
                name: "foo",
                path: "/foo",
                component: resolve(__dirname, "pages/custom.vue")
            });
        }
    }
}

3.自定义布局

//layouts/blank.vue
<template>
  <div>模板
    <nuxt></nuxt>
  </div>
</template>

//使用

 <template>
    <div >
      xxx
    </div>
</template>
    <script>
        export default { 
        layout:'blank'
}
    </script>

4.⻚⾯配置

⻚⾯组件就是 Vue 组件,只不过 Nuxt.js 为这些组件添加了⼀些特殊的配置项给⾸⻚添加标题和meta等

export default {
    head() {
        return {
            title: "课程列表",
            // vue-meta利⽤hid确定要更新meta
            meta: [{
                name: "description", hid: "description", content: "set pagemeta" }],
                link: [{ rel: "favicon", href: "favicon.ico" }],
            };
 },
};

5.生命周期

Nuxt扩展了Vue的生命周期,大概如下:

export default {
  middleware () {}, //服务端
  validate () {}, // 服务端
  asyncData () {}, //服务端
  fetch () {}, // store数据加载
  beforeCreate () {  // 服务端和客户端都会执行},
  created () { // 服务端和客户端都会执行 },
  beforeMount () {}, 
  mounted () {} // 客户端
}

6 数据请求

6.1 asyncData

该方法是Nuxt最大的一个卖点,服务端渲染的能力就在这里,首次渲染时务必使用该方法。
asyncData会传进一个context参数,通过该参数可以获得一些信息,如:

export default {
  asyncData (ctx) {
    ctx.app // 根实例
    ctx.route // 路由实例
    ctx.params  //路由参数
    ctx.query  // 路由问号后面的参数
    ctx.error   // 错误处理方法
  }
} 
//pages/index.vue 商品列表查询 服务端会根据asyncData命名 做服务端渲染
<template>
  <div>
    <h2>商品列表</h2>
    <ul>
      <li v-for="good in goods" :key="good.id">
        <nuxt-link :to="`/detail/${good.id}`">
          <span>{{good.text}}</span>
          <span>¥{{good.price}}</span>
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default { 
  data() {
    return {
      goods: []
    }
  },
  async asyncData({ $axios, error }) {
    const { ok, goods } = await $axios.$get("/api/goods");
    if (ok) {
      // 此处返回的数据会和data进行合并
      return {
        goods
      };
    }
    // 错误处理
    error({statusCode: 400, message: '数据查询失败请重试~'})
  }
};
</script>


//  获取商品详情,/index/_id.vue
<template>
  <div>
    <pre v-if="goodInfo">{{ goodInfo }}</pre>
  </div>
</template> 
<script>
export default {
  async asyncData({ $axios, params, error }) {
    if (params.id) {
      // asyncData中不能使⽤this获取组件实例
      // 但是可以通过上下⽂获取相关数据
      const { data: goodInfo } = await $axios.$get("/api/detail", { params });
      if (goodInfo) {
        return { goodInfo };
      }
      error({ statusCode: 400, message: "商品详情查询失败" });
    } else {
      return { goodInfo: null };
    }
  },
};
</script>

6.2 nuxtServerInit

通过在store的根模块中定义 nuxtServerInit ⽅法,Nuxt.js 调⽤它的时候会将⻚⾯的上下⽂对象作为第2个参数传给它。当我们想将服务端的⼀些数据传到客户端时,这个⽅法⾮常好⽤。

nuxtServerInit仅在服务端执⾏
范例:登录状态初始化,store / index.js
//nuxtServerInit只能写在store / index.js
export const actions = {
  nuxtServerInit({ commit }, { app }) {
    const token = app.$cookies.get("token");
    if (token) {
      console.log("nuxtServerInit: token:" + token);
      commit("user/init", token);
    }
  }
};
//安装依赖模块:cookie - universal - nuxt
//npm i - S cookie - universal - nuxt

//注册, nuxt.confifig.js
modules: ["cookie-universal-nuxt"],

7 中间件

//声明 middleware/auth.js
export default function({ route, redirect, store }) {
    // 上下⽂中通过store访问vuex中的全局状态
    // 通过vuex中令牌存在与否判断是否登录
    if (!store.state.user.token) {
        redirect("/login?redirect="+route.path);
    }
}

//单页面注册
<script>
export default {
    middleware: ['auth']
}
</script>
// nuxt.confifig.js 全局注册
router: {
    middleware: ['auth']
},

8.插件

Nuxt.js会在运⾏应⽤之前执⾏插件函数,需要引⼊或设置Vue插件、⾃定义模块和第三⽅模块时特别有⽤。

范例代码:接⼝注⼊,利⽤插件机制将服务接⼝注⼊组件实例、store实例中,

//创建plugins / api - inject.js
export default ({ $axios }, inject) => {
  inject("login", user => {
    return $axios.$post("/api/login", user);
  });
};

//注册插件,nuxt.confifig.js
plugins: [
  "@/plugins/api-inject"
],

范例:添加请求拦截器附加token

//创建plugins / interceptor.js
export default function ({ $axios, store }) {
  $axios.onRequest(config => {
    if (store.state.user.token) {
      config.headers.Authorization = "Bearer " + store.state.user.token;
    }
    return config;
  });
}
//注册插件,nuxt.confifig.js
plugins: ["@/plugins/interceptor"]

3.完整代码

3.1客户端代码

//layouts/default.vue
<template>
  <div>
    <nav>
      <nuxt-link to="/">首页</nuxt-link>
      <nuxt-link to="/admin" no-prefetch>管理</nuxt-link>
      <nuxt-link to="/cart">购物车</nuxt-link>
    </nav>
    <!-- page中定义的内容 -->
    <nuxt />
  </div>
</template>


//pages/index.vue
<template>
  <div>
    <h2>商品列表</h2>
    <ul>
      <li v-for="good in goods" :key="good.id">
        <nuxt-link :to="`/detail/${good.id}`">
          <span>{{good.text}}</span>
          <span>¥{{good.price}}</span>
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

//pages/login.vue
<template>
  <div>
    <h2>用户登录</h2>
    <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 {
  layout: "blank",
  data() {
    return {
      user: {
        username: "",
        password: ""
      }
    };
  },
  methods: {
    onLogin() {
      this.$store.dispatch("user/login", this.user).then(ok => {
        // 登录成功重定向
        if (ok) {
          const redirect = this.$route.query.redirect || "/";
          this.$router.push(redirect);
        }
      });
    }
  }
};
</script>

//pages/detail.vue
<template>
  <div>
    <h2>detail page</h2>
    <!-- nuxt-child表示嵌套关系 -->
    <!-- 类似router-view -->
    <nuxt-child></nuxt-child>
  </div>
</template>
<script>
  export default {
    
  }
</script>

//pages/detail/_id.vue
<template>
  <div>
    {{$route.params.id}}
  </div>
</template>

//pages/admin.vue
<template>
  <div>
    <h2>admin page</h2>
  </div>
</template>
<script>
export default {
  middleware: ['auth']
};
</script>

//middleware/auth.js
export default function({ route, redirect, store }) {
  // 上下文中通过store访问vuex中的全局状态
  // 通过vuex中令牌存在与否判断是否登录
  if (!store.state.user.token) {
    redirect("/login?redirect="+route.path);
  }
}

//store/index.js
export const actions = {
  nuxtServerInit({ commit }, { app }) {
    // nuxt-universal-cookie用法如下
    // app是server实例也就是koa实例
    const token = app.$cookies.get("token");
    // 表名是登录用户
    if (token) {
      console.log("nuxtServerInit: token:"+token);
      commit("user/init", token);
    }
  }
};


//store/user.js
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, getters }, u) {
    return this.$login(u).then(({ token }) => {
      if (token) {
        commit("init", token);
      }
      return getters.isLogin;
    });
  }
};

//nuxt.config.js 
module.exports = {
  mode: 'universal', 
  // router配置
  router: {
    extendRoutes(routes, resolve) {
      routes.push({
        path: '/foo',
        component: resolve(__dirname, 'pages/othername.vue')
      })
    },
    // middleware: ['auth']
  }, 
  plugins: [
    '@/plugins/element-ui',
    '@/plugins/api-inject',
    '@/plugins/interceptor',
  ], 
  modules: [
    '@nuxtjs/axios',
    "cookie-universal-nuxt"
  ],
  axios: {
    proxy: true
  },
  proxy: {
    "/api": "http://localhost:8080"
  }, 
}

//plugins/interceptor.js
export default function({ $axios, store }) {
  // onRequest是@nuxtjs/axios模块提供的帮助方法
  $axios.onRequest(config => {
    // 附加令牌
    if (store.state.user.token) {
      config.headers.Authorization = "Bearer " + store.state.user.token;
    }
    return config;
  });
}
//plugins/api-inject.js
// 参数1上下文
// 参数2注入函数
export default ({ $axios }, inject) => {
  // 将来this.$login
  inject("login", user => {
    return $axios.$post("/api/login", user);
  });
};

//layouts/default.vue
<template>
  <div>
    <nav>
      <nuxt-link to="/">首页</nuxt-link>
      <nuxt-link to="/admin" no-prefetch>管理</nuxt-link>
      <nuxt-link to="/cart">购物车</nuxt-link>
    </nav>
    <!-- page中定义的内容 -->
    <nuxt />
  </div>
</template>

3.2服务端接口代码

//server/api.js
// 此文件并非nuxt生成,它为演示项目提供数据服务接口
const Koa = require('koa');
const app = new Koa();
const bodyparser = require("koa-bodyparser");
const router = require("koa-router")({ prefix: "/api" });

// 设置cookie加密秘钥
app.keys = ["some secret", "another secret"];

const goods = [
  { id: 1, text: "xxxxx1", price: 1000 },
  { id: 2, text: "xxxxx2", price: 1000 }
];

// 配置路由
// 获取产品列表
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 };
  }
});

// 解析post数据并注册路由
app.use(bodyparser());
// 注册路由
app.use(router.routes());
app.listen(8080, () => console.log('api服务已启动'))



//下面是脚手架自动生成的
//server/index.js
const Koa = require('koa')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')

const app = new Koa()

// Import and Set Nuxt.js options
let 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

  // Build in development
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  } else {
    await nuxt.ready()
  }

  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()

4.打包部署方式

{
  "scripts": {
    //  开发环境
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    //  打包
    "build": "nuxt build",
    //  在服务端运行
    "start": "cross-env NODE_ENV=production node server/index.js",
    //  生成静态页面
    "generate": "nuxt generate"
  }
} 

1.开发测试

npm run dev

2.服务端部署

通过node服务响应请求,需要先编译服务端与客户端代码

npm run build  
npm start   

2.静态发布

直接根据当前页面生成所有静态页面

npm run generate