nuxtjs

https://zh.nuxtjs.org/

nuxt是什么?

nuxt是一个专注于ui渲染的应用框架,你可以使用Nuxt创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码
使用nuxt你可以快速搭建一个项目,而省略了繁琐的webpack babel sass vue-loader等等配置,同时nuxt还提供了服务器端渲染功能,
所以nuxt提供的是一种快速搭建项目的能力,
我们可以从nuxt中学到什么?
1:vue项目结构
nuxt推荐的项目结构是
assets资源目录,
components组件目录,
layouts母版页, 这个之前没写过
pages页面,
plugins插件:我之前都是把vue插件直接写在main.js中
static静态文件,
Store vuex目录,
middleware中间件目录 在每一个页面加载之前调用 如果是spa模式 就是vue的beforeRouter方法
我们可以在上面的基础上扩展新的文件:
filter 数据过滤文件 提供统一的数据整理能力
2:nuxt省区了所以的配置,同时他给我们提供了一个配置文件的入口,以保证如果我们需要特定化配置,不至于没有地方进行配置
3:为支持服务器端渲染 nuxt提供了asyncData fetch nuxtServerInit功能
4:nuxt为我们提供了去扩展nuxt能力的方法 就是模块
5:nuxt提出了view层中间件的想法:中间件的思路是在每一个页面加载和渲染之前执行的方法,我们可以用来执行鉴权或其他方法
6:nuxt提出了layout的想法,我们之前都没有想过去使用模板来定制化我们的界面
所以本质上,nuxt提供一个基本的项目结构,并把服务器端渲染集成进来,同时将当前前端发展的最新技术应用于nuxt中,如预加载 页面过度效果等

下载项目

  • npx create-nuxt-app 项目名
  • yarn create nuxt-app 项目名

    1.在集成的服务器端框架之间进行选择:

  • None (Nuxt 默认服务器)

  • Express
  • Koa
  • Hapi
  • Feathers
  • Micro
  • Fastify
  • Adonis (WIP)

    2. 选择您喜欢的 UI 框架:

  • None (无)

  • Bootstrap
  • Vuetify
  • Bulma
  • Tailwind
  • Element UI
  • Ant Design Vue
  • Buefy
  • iView
  • Tachyons

    3.选择您喜欢的测试框架:

  • None (随意添加一个)

  • Jest
  • AVA

    4.选择你想要的 Nuxt 模式 (Universal or SPA)

    5.添加 axios module 以轻松地将 HTTP 请求发送到您的应用程序中。

    6.添加 EsLint 以在保存时代码规范和错误检查您的代码。

    7.添加 Prettier 以在保存时格式化/美化您的代码。

  • 启动项目 npm run dev

    pages 路由配置

    根据目录结构会自动更新,

  • 一级路由目录结构

    1. -pages<br /> -index.vue<br /> -user.vue<br />会自动根据目录生成路由配置表
    1. [{
    2. path: "/",
    3. component: component,
    4. name: "index",
    5. },
    6. {
    7. path: "/user",
    8. component: component,
    9. name: "user",
    10. }
    11. }]
  • 二级路由目录结构

    1. -index.vue<br /> -user.vue<br /> -user | 文件夹<br /> -login.vue
    1. [
    2. {
    3. path: "/",
    4. component: component,
    5. name: "index",
    6. },
    7. {
    8. path: "/user",
    9. component: component,
    10. name: "user",
    11. children:[
    12. {
    13. path: "login",
    14. component: component,
    15. name: "user-login",
    16. }
    17. ]
    18. }
    19. ]
  • 动态路由配置

    -detail | 文件夹
    -_id.vue

    1. [
    2. {
    3. path: "/detail/:id?",
    4. component: component,
    5. name: "detail-id",
    6. }
    7. ]

跨域

在nuxt.config.js文件下配置

  1. modules: [
  2. // https://go.nuxtjs.dev/axios
  3. "@nuxtjs/axios",
  4. "@nuxtjs/proxy"
  5. ],
  6. axios: {
  7. // See https://github.com/nuxt-community/axios-module#options
  8. proxy: true, // 表示开启代理
  9. prefix: "/api", // 表示给请求url加个前缀 /api
  10. credentials: true // 表示跨域请求时是否需要使用凭证
  11. },
  12. proxy: {
  13. "/api": {
  14. target: "http://localhost:7001", // 目标接口域名
  15. changeOrigin: true, // 表示是否跨域
  16. pathRewrite: {
  17. "^/api": "" // 把 /api 替换成‘’
  18. }
  19. }
  20. }

在页面调用时

  1. this.$axios.get('/list).then(res=>{
  2. //请求结果
  3. })

附带token请求头

  1. nuxt.config.js 配置文件

    1. plugins :[
    2. {src:'~/plugins/axios',mode:'client'}
    3. ]
  2. 在plugins文件夹下面创建axios.js

    1. export default function({$axios,redirect}){
    2. $axios.onRequest(config=>{
    3. console.log("Making request to"+config.url)
    4. //从本地获取token
    5. let token = localStorage.getItem("token")
    6. //如果token不为空,将token放入请求中
    7. if(token){
    8. config.headers.authorization = token
    9. }
    10. return config
    11. })
    12. //错误处理机制
    13. $axios.onError(error => {
    14. const code = parseInt(error.response&&error.response.status)
    15. if(code===400){
    16. redirect('/400')
    17. }
    18. })
    19. }

    vuex - store仓库

  • 模块方式: store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)
  • Classic(不建议使用): store/index.js返回创建 Vuex.Store 实例的方法。

store
-user.js

  1. //user.js
  2. export const state = () => ({
  3. count: 1
  4. });
  5. export const mutations = {
  6. add(state) {
  7. state.count = state.count + 1;
  8. }
  9. };
  10. export const actions = {
  11. xx({ commit }) {
  12. commit("add");
  13. }
  14. };

在页面中 我们通过this.$store.state.user.count 拿到仓库数据
通过 this.$store.commit(‘user/add’)调用mutations的方法
通过this.$store.dispatch(‘user/xx’)调用actions 异步方法

nextjs

https://www.nextjs.cn/docs/getting-started

基于文件路径的路由

页面

一般前端web应用都可以简化为,基于路由的页面和API两部分。Next的路由系统基于文件路径自动映射,不需要做中性化的配置。
一般都约定在根目录pages文件夹内:

  • ./pages/index.tsx —> 首页 /
  • ./pages/admin/index.tsx —> /admin
  • ./pages/admin/post.tsx —> /admin/post

    1. import styles from './style.module.css'
    2. function About() {
    3. return <div>About</div>
    4. }
    5. export default About

    默认导出一个React的组件,Next就会帮你默认生成对应路由的页面。

  • 你不用关心head里面资源如何配置加载

  • 可以像SPA应用一样,使用css-in-js,css module,less,sass等样式import方式。

    页面间的导航

    1. import Link from 'next/link'
    2. function Home() {
    3. return (
    4. <ul>
    5. <li>
    6. <Link href="/about">
    7. <a>About Us</a>
    8. </Link>
    9. </li>
    10. </ul>
    11. )
    12. }
    13. export default Home

    注意,Link中最好独立包裹a元素。

    增加Head

    1. import Head from 'next/head'
    2. function About() {
    3. return (
    4. <div>
    5. <Head>
    6. <title> Hipo Log - {props.post?.name ?? ''}</title>
    7. </Head>
    8. content
    9. </div>
    10. );
    11. }
    12. export default About

    Dynamic import 代码拆分

    Next也支持ES2020的dynamic import()语法,可以拆分代码,或者有些第三方组件依赖浏览器API时候精致服务端渲染(ssr: false)

    1. import dynamic from 'next/dynamic'
    2. const DynamicComponentWithCustomLoading = dynamic(
    3. () => import('../components/hello'),
    4. {
    5. loading: () => <p>...</p>,
    6. ssr: false
    7. }
    8. )
    9. function Home() {
    10. return (
    11. <div>
    12. <Header />
    13. <DynamicComponentWithCustomLoading />
    14. <p>HOME PAGE is here!</p>
    15. </div>
    16. )
    17. }
    18. export default Home

    注意:在页面代码要谨慎import代码!!
    越多引入,上线访问后加载的js就越多,特别是下面钩子函数要注意,不要引入多余代码

    API

    API类型的路由约定在./pages/api 文件夹内,next会自动映射为/api/*路径的API

    1. import { NextApiRequest, NextApiResponse } from 'next'
    2. export default (req: NextApiRequest, res: NextApiResponse) => {
    3. res.status(200).json({ name: 'John Doe' })
    4. }

    请求方法通过req中取到。
    如此你就可以很轻松的生成一个API。

    动态路由

    正常的应用,都有动态路由,next中讨巧使用文件命名的方式来支持。

  • ./pages/post/create.js —> /post/create

  • ./pages/post/[pid].js —> /post/1 , /post/abc 等,但是不会匹配 /post/create
  • ./pages/post/[...slug].js —> /post/1/2, /post/a/b/c等,但是不会匹配/post/create , /post/abc

动态参数可以通过req.query对象中获取({ pid }{ slug: [ 'a', 'b' ] }),在页面中可以通过router hook获取:

  1. import { useRouter } from 'next/router';
  2. function About() {
  3. const router = useRouter();
  4. const { bID, pID } = router.query
  5. return <div>About</div>
  6. }

页面SSR 钩子以及SSG

大部分的应用内容,都不是纯静态的,我们需要数据查询才能渲染那个页面,而这些就需要同构钩子函数来满足,有了这些钩子函数,我们才可以在不同需求下作出极佳体验的web应用。

getServerSideProps(SSR)每次访问时请求数据

页面中export一个asyncgetServerSideProps方法,next就会在每次请求时候在服务端调用这个方法。

  • 方法只会在服务端运行,每次请求都运行一边getServerSideProps方法
  • 如果页面通过浏览器端Link组件导航而来,Next会向服务端发一个请求,然后在服务端运行getServerSideProps方法,然后返回JSON到浏览器。

getServerSideProps方法主要是升级了9.3之前的getInitialProps方法
9.3之前的getInitialProps方法有一个很大的缺陷是在浏览器中reqres对象会是undefined。也就是使用它的页面,如果是浏览器渲染你需要在组件内再显示地请求一次。开发体验不太好。 如果没有特殊问题,建议使用getServerSideProps替代getInitialProps方法。 示例:

  1. import { GetServerSideProps, NextPage } from 'next'
  2. interface PostProps {
  3. list: Post[]
  4. }
  5. const App: NextPage<PostProps> = props => {
  6. return <div></div>
  7. }
  8. export const getServerSideProps: GetServerSideProps<PostProps> = async context => {
  9. const list = await context.req.service.post.getPost(context.params.postID)
  10. return {
  11. props: {
  12. list
  13. }
  14. }
  15. }
  16. export default App

getStaticPropsgetStaticPaths(SSG)构建时请求数据

所谓的SSG也就是静态站点生成,类似像hexo或者gatsbyjs都是在build阶段将页面构建成静态的html文件,这样线上直接访问HTML文件,性能极高。
Next.js 再9.0的时候引入了自动静态优化的功能,也就是如果页面没有使用getServerSidePropsgetInitialProps方法,Next在build阶段会生成html,以此来提升性能。
但是正如上文说的,一般应用页面都会需要动态的内容,因此自动静态优化局限性很大。
Next 在9.3中更近了一步,引入了getStaticPropsgetStaticPaths方法来让开发者指定哪些页面可以做SSG优化。

  • 使用getStaticProps方法在build阶段返回页面所需的数据。
  • 如果是动态路由的页面,使用getStaticPaths方法来返回所有的路由参数,以及是否需要回落机制。

    1. export async function getStaticPaths() {
    2. // Call an external API endpoint to get posts
    3. const res = await fetch('https://.../posts')
    4. const posts = await res.json()
    5. // Get the paths we want to pre-render based on posts
    6. const paths = posts.map(post => ({
    7. params: { id: post.id },
    8. }))
    9. // We'll pre-render only these paths at build time.
    10. // { fallback: false } means other routes should 404.
    11. return { paths, fallback: true };
    12. }
    13. export const getStaticProps: GetStaticProps<InitProps> = async ({ params }) => {
    14. const data = await fetch(
    15. `http://.../api/p/${params.bookUUID }/${
    16. params.postUUID }`
    17. );
    18. return {
    19. props: {
    20. post: data,
    21. },
    22. };
    23. };

    使用非常的简单,需要注意的是:

  • getStaticPaths方法返回的fallback很有用:如果fallbackfalse,访问该方法没有返回的路由会404

  • 但是如果不想活着不方便在build阶段拿到路由参数,可以设置fallbacktrue,Next在访问build中没有的动态路由时候,先浏览器loading,然后服务端开始build该页面的信息,然后再返回浏览器渲染,再次访问该路由该缓存就会生效,很强大!!
  • 静态缓存目前没办法很灵活的更新!!,例如博客内容在build或者fallback生效之后发生更改,目前没办法很方便的替换缓存。

    如何选择SSR还是SSG?

  1. 如果页面内容真动态(例如,来源数据库,且经常变化), 使用getServerSideProps方法的SSR。
  2. 如果是静态页面或者伪动态(例如,来源数据库,但是不变化),可以酌情使用SSG。

上面就是Next.js中主要的部分了,下面是一些可能用到的自定义配置。

自定义App

./pages/_app.tsx来自定义应用App,可以配置全局的css,或者getServerSideProps方法来给每个页面添加数据。

  1. function MyApp({ Component, pageProps }) {
  2. return <Component {...pageProps} />
  3. }
  4. export default MyApp

自定义Document

./pages/_document.tsx来自定义页面的Document,可以配置页面html,head属性,或者使用静态getInitialProps方法中renderPage方法来包括整个react 应用。

  1. import Document, { Html, Head, Main, NextScript } from 'next/document'
  2. class MyDocument extends Document {
  3. static async getInitialProps(ctx) {
  4. const initialProps = await Document.getInitialProps(ctx)
  5. return { ...initialProps }
  6. }
  7. render() {
  8. return (
  9. <Html>
  10. <Head />
  11. <body>
  12. <Main />
  13. <NextScript />
  14. </body>
  15. </Html>
  16. )
  17. }
  18. }
  19. export default MyDocument

<Html>, <Head />, <Main />`<NextScript />都是必须的。`

  • 上述app和document中使用getServerSideProps或者getInitialProps方法让整个应用都无法自动静态优化
  • 上述app和document中在浏览器中不执行,包括react的hooks或者生命周期函数。

    自定义构建

    Next自然也可以自定义构建,根目录使用next.config.js来配置webpack,可以用来支持less编译,按需加载,path alias等。
    下面是Hipo Log中的配置,支持了Antd design的按需加载。
    1. const withLess = require('@zeit/next-less');
    2. const withCss = require('@zeit/next-css');
    3. const path = require('path');
    4. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    5. const fixMiniCss = (nextConfig = {}) => {
    6. return Object.assign({}, nextConfig, {
    7. webpack(_config, options) {
    8. // force react-scripts to use newer version of `mini-css-extract-plugin` and ignore css ordering warnings
    9. // (related to issue: https://github.com/facok/create-react-app/issues/5372)
    10. let config = _config;
    11. if (typeof nextConfig.webpack === 'function') {
    12. config = nextConfig.webpack(_config, options);
    13. }
    14. let miniCssExtractOptions = {};
    15. for (let i = 0; i < config.plugins.length; i++) {
    16. const p = config.plugins[i];
    17. if (
    18. !!p.constructor &&
    19. p.constructor.name === MiniCssExtractPlugin.name
    20. ) {
    21. miniCssExtractOptions = { ...p.options, ignoreOrder: true };
    22. // config.plugins[i] = new MiniCssExtractPlugin(miniCssExtractOptions);
    23. break;
    24. }
    25. }
    26. // config.plugins.push(new MiniCssExtractPlugin(miniCssExtractOptions));
    27. config.resolve.alias = {
    28. ...config.resolve.alias,
    29. '@pages': path.join(__dirname, './pages'),
    30. '@components': path.join(__dirname, './components'),
    31. '@entity': path.join(__dirname, './dbEntity'),
    32. '@services': path.join(__dirname, './services'),
    33. '@lib': path.join(__dirname, './lib'),
    34. '@util': path.join(__dirname, './services/util'),
    35. '@type': path.join(__dirname, './app.type'),
    36. };
    37. return config;
    38. },
    39. });
    40. };
    41. module.exports = fixMiniCss(
    42. withLess({
    43. cssModules: true,
    44. ...withCss({
    45. webpack(config, options) {
    46. const { dev, isServer } = options;
    47. if (isServer) {
    48. const antStyles = /antd\/.*?\/style\/css.*?/;
    49. const origExternals = [...config.externals];
    50. config.externals = [
    51. (context, request, callback) => {
    52. if (request.match(antStyles)) return callback();
    53. if (typeof origExternals[0] === 'function') {
    54. origExternals[0](context, request, callback);
    55. } else {
    56. callback();
    57. }
    58. },
    59. ...(typeof origExternals[0] === 'function' ? [] : origExternals),
    60. ];
    61. config.module.rules.unshift({
    62. test: antStyles,
    63. use: 'null-loader',
    64. });
    65. }
    66. return config;
    67. },
    68. }),
    69. })
    70. );

    自定义服务

    Next也支持node启动,以此来和其他框架配合实现更复杂的服务端功能,譬如Hipo Log 使用它来绑定数据库typeorm等。
    1. / server.js
    2. const { createServer } = require('http')
    3. const { parse } = require('url')
    4. const next = require('next')
    5. const dev = process.env.NODE_ENV !== 'production'
    6. const app = next({ dev })
    7. const handle = app.getRequestHandler()
    8. app.prepare().then(() => {
    9. createServer((req, res) => {
    10. // Be sure to pass `true` as the second argument to `url.parse`.
    11. // This tells it to parse the query portion of the URL.
    12. const parsedUrl = parse(req.url, true)
    13. const { pathname, query } = parsedUrl
    14. if (pathname === '/a') {
    15. app.render(req, res, '/b', query)
    16. } else if (pathname === '/b') {
    17. app.render(req, res, '/a', query)
    18. } else {
    19. handle(req, res, parsedUrl)
    20. }
    21. }).listen(3000, err => {
    22. if (err) throw err
    23. console.log('> Ready on http://localhost:3000')
    24. })
    25. })