Nuxtjs3 备忘

作为一个本职工作是基础设施开发的后端人员,我将js比作linux系统,将vue比作docker/containerd,而nuxt就相当于kubernetes了。当然这个类比并不是非常准确。前端比容器大战要柔和的多,感觉大家都在底层上重复建设。对比容器技术,swarm/k8s/mesos也就5年左右就分出了胜负。前端目前主流还是react/vue/Angular这三家,基于这三家之上又有各自的服务端框架(更底层typescript倒是已经统一了),导致前端学习门槛较高,框架概念、设计模式其实也走了一遍后端框架的老路,很多概念后端开发可以平移过来。我比较相信Taylor Otwell(Laravel的创造者),他说vue好,所以我就跟着他一起用了。

前两篇记录了点vue2vue3的笔记,这一篇记录下nuxtjs3,这是官网,2022 年 4 月 20 日正式发布 nuxt3,代号为“Mount Hope”

Nuxt3是基于 Vue3 发布的 SSR 框架:

  • SPA应用:也就是单页应用,这些多是在客户端的应用,不能进行SEO优化(搜索引擎优化)。
  • SSR应用:在服务端进行渲染,渲染完成后返回给客户端,每个页面有独立的URL,对SEO友好,对应企业网站、商品展示 、博客这类型的展示型网站。

这是官网显示的一些优势说明:

  • 更轻量:以现代浏览器为基础的情况下,服务器部署和客户端产物最多减小75倍。
  • 更快:用动态服务端代码来优化冷启动。
  • Hybird:增量动态生成和其他高级模式现在都成为可能。
  • Suspense: 导航前后可在任何组件中获取数据。
  • Composition API : 使用Composition API 和 Nuxt3的composables 实现真正的可复用性。
  • Nuxt CLI : 权限的零依赖体验,助你轻松搭建项目和集成模块。
  • Nuxt Devtools :专属调试工具,更多的信息和快速修复,在浏览器中高效工作。
  • Nuxt Kit :全新的基于 TypeScript 和跨版本兼容的模块开发。
  • Webpack5 : 更快的构建速度和更小的构建包,并且零配置。
  • Vite:用Vite作为你的打包器,体验轻量级的快速HMR。
  • Vue3 : 完全支持Vue3语法,这一点特别关键。
  • TypeScript:由原生TypeScript和ESM构成,没有额外配置步骤。

安装

npx是npm从5.2版开始增加的命令

官方链接

npx nuxi init nuxt3-test  # 快速创建

image-20220517113727498

npm install
npm run dev

image-20220517113912229

生成文件如下:

image-20220517114211393

这里要重点提的是 nuxt.config.js 文件,所有配置都在里面。

一些常用的目录如下:

- pages               // 页面目录,Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。
- components          // 组件目录
- assets              // 静态资源目录
- public              // 不需要 vue 处理的静态资源,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。
- layouts             // 项目布局目录
- common              // 存储js文件
- plugins             // nuxt扩展的js文件
- store               // vuex的配置文件,Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。

配置参考链接:

删除根目录下的app.vue,然后pages目录下新建index.vue文件,就会自动关联vue-router,支持动态路由。

因为我是容器化环境开发的,所以改了一下运行端口到80端口,访问方便一些。如果发现配置不生效,可以尝试重启一卡容器(多个容器在同一个目录下启动npm run时感觉会冲突,就遇到有些配置不生效了)。

image-20220519113053640

一些官网备忘

setup函数中,可以useHead使用与元标记对应的键的元属性对象进行调用:title, titleTemplate, base, script, style, metaand link, 以及htmlAttrsand bodyAttrs。还有两个速记属性charsetviewport

data-fetching

Nuxt 提供useFetchuseLazyFetchuseAsyncDatauseLazyAsyncData处理应用程序中的数据获取。

state-management

useState是一个对 SSR 友好的ref 替代品。它的值将在服务器端渲染后(在客户端水化期间)被保留,并使用唯一键在所有组件之间共享。

server-routes

Nuxt 会自动扫描 、 和 目录中的文件~/server/api~/server/routes~/server/middleware注册具有 HMR 支持的 API 和服务器处理程序。

每个文件都应该导出一个用defineEventHandler().

路由

Nuxt使用了自动加载路由的方案,他会根据你项目的目录来自动创建router对象;

一个简单的示例,pages下的目录示例及其对应的路由如下所示:

test:
  a.vue     -- 对应路径:/test/a
  index.vue -- 对应路由:/test
index:
  _id:
    a.vue      -- 对应路由:/{id}/a
    index.vue  -- 对应路由:/{id}
  index.vue    -- 对应路由:/,会被包含到上一级的index.vue的<nuxt-child>中
index.vue      -- 对应路由:/,其中如果包含有nuxt-child元素,那么会将index目录下的index.vue加载到nuxt-child元素位置  
  • 在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
  • 如果你想将_id设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。

路由参数校验

Nuxt.js 可以让你在动态路由组件中定义参数校验方法。

举个例子: pages/users/_id.vue

export default {
  validate ({ params }) {
    // 必须是number类型
    return /^\d+$/.test(params.id)
  }
}

如果校验方法返回的值不为 true或Promise中resolve 解析为false或抛出Error , Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面。

未知嵌套深度的动态嵌套路由

如果您不知道URL结构的深度,您可以使用_.vue动态匹配嵌套路径。这将处理与更具体请求不匹配的情况。

文件结构:

pages/
--| people/
-----| _id.vue
-----| index.vue
--| _.vue
--| index.vue

知识点

一些代码只是为了做代码的结构备忘,不代表逻辑正确。

前端的语法糖太多了,我觉得这些语法糖是前端晦涩的一大原因。

不过,“无他,但手熟尔”,在前端也是一样。

  1. 命令行nuxi

    npx nuxi init nuxt3-test  # 快速创建
    
  2. layout 布局 html(静态)

    layout文件夹
    page文件夹
    
  3. 引入 css

    <style scope>
    .abc {}
    </style>
    
  4. 应用状态state

    <script setup>
      const options = reactive({
          xxx: "xxx"
      });
    </script>
       
    <template>
      <button class="abc" :class="options.xxx === 'xxx' && 'option-active'">abc</button>
    </template>
    
  5. a标签替换为 NuxtLink 标签

    <NuxtLink to="/">Home</NuxtLink>
    
  6. 简单的typescript

    我觉得typescript的语法有点像golang。 看着也挺舒服的~

    先定义个interface类型,一些选项/常量用enum定义,再const一个实例。

    <script setup lang="ts">
      interface OptionState {
        xxx: string,
        yyy: string
      }
         
      enum Xxx {
        AAA = "aaa",
        BBB = "bbb"
      }
       
      const obj := OptionState{
          xxx: Xxx.AAA,
          yyy: Xxx.BBB
      }
         
      const options = reactive<OptionState>({
          xxx: Xxx.AAA,
          yyy: Xxx.BBB
      });
    </script>
    
  7. import 其他文件里定义的内容

    import {OptionState} from "@/data"  // 其他文件要 export特定的定义
    

    教程有的为了简化内容,直接准备一个json文件,然后import数据进来:

    <script setup lang="ts">
    import {xxx} from "@/data.json"
         
    const rest = {
      first: [...xxx].splice(0,25),
      second: [...xxx].spice(25,25),
    };
    </script>
         
    <template>
      <Row v-for="r in rest.first" :key="r.id" />
      <Row v-for="r in rest.second" :key="r.id" />
    </template>
         
    
  8. 更新state

    <script setup lang="ts">
      const computeSelectedNames = () => {
        const filterNames = names
        	.filter((name) => name.xxx === options.xxx )
        	.filter((name) => name.yyy === options.yyy )
        	.filter((name) => {
            return ...
          })
             
          selectdNames.value = filterdNames.map((name) => name.xxx )
           
      };
      const selectdNames = ref<string[]>{[]}
    </script>
       
    <template>
      <button class="abc" @click="computeSelectName">Find</button>
      {{ selectedNames }}
    </template>
    
  9. 注意拆分component,以及通过props在父子组件中传值

    <script setup lang="ts">
      interface OptionProps{
        option: {
            id: string,
          	title: string,
            catagory: string,
            index: number
        };
      }
         
      const props = defindProps<OptionProps>()
    </script>
    

    在父组件中传入:

       
    
   
   
   
   大写开头的标签就是component,有一些是 `nuxt` 保留的,用来作为头部header内容的,类似于 `metadata`

   

10. 通过事件触发父子组件传值。

    ```vue
    // 父:
    <script setup lang="ts">
      const removeName = (index: number) => {
        const filteredNames = [...selectdNames.value]
        filterNames.splice(index,1)
        selectedName.value = filterNames 
      }
    </script>
    
    <template>
      <CardName v-for="(name, index) in selectedNames" :key="name" :name="name" @remove="()=> removeName(index)" :index="index" />
    </template>
    
    // 子CardName:
    <script setup lang="ts">
      interface NameProps{
        name: string;
        index: number
      }
      
      const props = defindProps<NameProps>();
      const emit = defindEmits(["remove"])
      
      const removeName = () => {
        emit("remove", props.index)
      }
    </script>
    
    <template>
      <p @click="removeName">xxx </p>
    </template>

  1. 常用的循环语句

    v-for
    
  2. Pages, 基于文件的路由route

    <script setup lang="ts">
    // import {}  from "#app"; // 用来让编辑器不报错的,其实没什么用。
    import xxx from "@/data.json"
          
    const route = useRoute();
    const name = route.params.name;
          
    const x = xxx.find(r => r.name === name)
    </script>
        
    <template>
    {{ name }}
    </template>
    
  3. 404页面

  4. Layouts

    创建 layouts 文件夹,并创建default.vue文件。

    在page中声明使用特定的layout

    <template>
      <div>
        <NuxtLayout name="custom">
          <Xxx />
        </NuxtLayout>
      </div>
    </template>
    

    在layout中插入slot(假设layout名error)

    <template>
      <div>
        <slot name="xxx" />
        <div>...</div>
      </div>
    </template>
    

    在page中:

    <template>
      <div>
        <NuxtLayout name="error">
          <template #xxx>
            <h1></h1>
          </template>
        </NuxtLayout>
        <div>...</div>
      </div>
    </template>
    
  5. metadata

    image-20220523155833406

    为了方便控制,也可以写在script里,

    image-20220523160139589

    也可以使用 outside stylesheets 方式:

    在nuxt.config.js里配置。

    export default defineNuxtConfig({
        charset: "utf-8",
        viewport: "width=device-width, initial-scale=1, maximum-scale=1",
        meta: {
            link: [
                { rel: "stylesheet", href: "/assets/css/style.css" },
                { rel: "stylesheet", href: "/assets/css/font-awesome.min.css" },
            ],
            script: [
                { src: '/assets/js/jquery.min.js' },
              ],
        }
    })
    
  6. 网页数据状态设计

  7. composable,useState

    用来保存状态。不能用 ref 做跟踪,因为这里没有render,会造成内存泄漏。

    image-20220523162132417

    image-20220523162252693

  8. Global State

    image-20220523162655826

    image-20220523162728965

  9. 生成静态网站

    目前还是测试阶段,官方不建议用于生产。

参考资料


vuejs3 备忘 CEO 一百天 - 池建强