分类: nuxt3

  • Nuxt3 如何传递 Token

    Nuxt3 如何传递 Token

    昨天我在思否上看到这个问题:nuxt3 请求 token 要怎么传?我之前也被这个问题困扰过,我猜测不少刚刚从 Vue3(SPA) 转向 Nuxt3(SSR) 开发的同学也会受困于这个问题,所以就把答案扩展一下,写成博文,分享给大家。

    Vue3(SPA)里使用 Token

    在 Vue3 开发的单页应用(Single Page Applicati,SPA)里使用 Token 比较简单。一般来说,我们会在项目初始化的时候读取 localStorage,拿到标记用户身份的 token,然后放到内存当中,在请求时放到 header 里面发出。如果找不到 token,就认为用户没有登录,那么就通过路由将用户指引到登录页,或者不需要登录也能看的页面。

    使用 Token 可以避免 CSRF 攻击,因为从外部网站无法同时满足访问 localStorage,和将信息放入 http header 这两个条件。所以在单页应用里,使用 token 验证用户身份非常常见,有着各种开源、强壮、高效的实现。

    这个过程相信做过 SPA 的同学都很熟悉,不多说了。

    Nuxt3 请求的特点

    从 Vue3(SPA) 到 Nuxt3(SSR),主要涉及一个思路的转换。

    在 Vue(SPA)里,所有请求都是  请求,即完成网页加载、JS 执行完毕后,再发起请求。这些请求,有以下特点:

    1. 可以认为完全由开发者控制,即开发者确定请求发起的条件,然后在基本确定的时候发起请求。
    2. 这些请求大部分只请求数据,且有明显的特征,比如 HTTP Header 里包含 Authorization
    3. 这些请求里基本不包含静态资源。

    在 Nuxt 里,因为 SSR 的存在,所以请求至少可以分成两类:

    1. 数据交互类,跟 SPA 里基本一致。
    2. 页面渲染类,SSR 核心所在。

    后者最大的变化是:它会影响到 HTML 的内容,而通常来说,HTML 会被视作静态资源。我们知道,在网络环境里,存在大量缓存节点,假如跟用户相关的敏感数据渲染成 HTML,缓存到 CDN 当中,会是非常严重的安全问题。所以 Nuxt 在 SSR 时,不会携带 cookie;只有当页面渲染完成,由于用户操作而主动发起的请求里,才会携带 cookie。

    怎么识别 SSR 请求

    Nuxt2 时期,使用 Option API 的时候,有一个很明确的 asyncData() 函数,它会在 SSR 阶段被调用,发起请求获取数据。

    Nuxt3 时期,Composition API 会稍嫌模糊一些。这个时候,整个 setup() 函数都会在 SSR 期间执行,而 useFetchuseAsyncData 则负责在这个阶段发起请求,获取数据。因为这个阶段是在服务器的 node.js 环境里执行的,所以自然无法使用用户的 localStorage,也无法使用用户本地存储的 token。

    但是因为打开网页的请求是由用户发起的,所以其实用户有把自己的 cookie 发给服务器。如果我们希望 SSR 阶段用 cookie 鉴别用户身份,可以手动将 cookie 放在 useFetchuseAsyncData 产生的请求当中,参考 官方文档 useRequestHeaders

    基本上,假如我们不了解 Nuxt3 的设计思路,简单看了眼文档就上手写代码,大概率所有页面数据相关的请求都会是 SSR 请求。至少我是如此。

    使用 server: false 仅在网页里发起请求

    当然,Nuxt3 提供了只在网页里发起请求的方法,也就是基本等效于我们之前在 SPA 里发起请求的方法,那就是在 useFetchoptions 里标记 server: false

    const { pending, data: posts } = useFetch('/api/comments', {
    server: false
    })

    再演示个更扣题的方式

    const { pending, data: posts } = useAsyncData(
    'posts',
    async () => {
    const token = localStorage.getItem('token');
    const data = await $fetch('/api/comments', {
    headers: {
    Authorization: `Bearer ${token}`,
    },
    });
    return data;
    },
    {
    server: false,
    }
    );

    (代码高亮插件还是崩的,大家凑合看,等我回头修……)

    详情请看 官方文档

    这样的请求就会在页面完成渲染后,才会发起请求。在这些请求里,如果需要使用 token,就按照以往的方式使用,即可。

    server: falselazy: true 的区别

    如果大家仔细看 Nuxt3 官方文档,会发现还有一个参数:lazy: true,以及两个语法糖函数:useLazyFetchuseLazyAsyncData

    这两个函数也是先加载页面,再加载数据。它们与 server: false 的区别在于,后者是把全部请求都放在网页端进行;而 lazy: true 则只在网页跳转的时候起作用。

    不使用 lazy: true 的情况下,当我们从 A 页面进入 B 页面时,如果 B 页面里需要加载数据,Nuxt3 就会等待数据加载完成之后,再进行页面渲染。如果你使用一个比较慢的网络,页面切换就会卡卡的,体验非常明显。

    如果使用 lazy: true,页面切换一下就完成了,但是数据可能还没加载到,此时我们需要手动处理加载状态。比如放个转转的菊花在页面中间。但是如果我们直接打开 B 页面,Nuxt3 还是会先把数据请求回来,完成页面渲染,然后呈现完整的 HTML 给我们,这样可以确保 SEO 效果。

    server: false,则会彻底放弃这部分数据的 SSR,对于用户身份相关的数据,就比较合适。

    总结

    其实就跟当年从前端学 Node.js 一样,新技术最大的挑战在于:使用场景不同、运行环境不同,思路需要很大变化,需要不同的理解,不能照搬以前的做法。

    Nuxt3 涉及到网页渲染,涉及到网络链路上的缓存层,为了防止出现信息泄漏,它会主动放弃一些数据传输。同时,由于它的一部分运行环境在服务器上,所以有一些用户数据它没法使用。于是在开发 Nuxt3 应用的时候,我们必须搞清楚这些变化,然后作出适当的调整。

    希望上面文章对大家有所帮助。如果文中有问题,请不吝指出。如果你对 Vue3、Nuxt3 开发有兴趣,有疑问,欢迎留言评论。

  • Bug求助+悬赏

    Bug求助+悬赏

    哎,老革命遇到新问题,创业+远程工作之后,想拉几个人跟我一起排查 bug 也找不到,只好公开求助+悬赏。

    首先请大家看下面这段视频:

    故障描述

    简单来说,我们做了一个网站:Dailylift.io。在这个网站上,你能描述自己今天的状态,然后得到一些心灵慰藉:你信仰的目标会给你写一封信。

    这个功能并不复杂,我选择用 Nuxt3 + Supabase 作为技术栈。我的理由是:

    1. 因为我们需要 SEO,我希望利用 Nuxt3 SSR 功能提升 SEO 效果。
    2. Supabase 提供包括开箱即用的用户系统在内的一系列 Serverless 功能,可以帮助我们的开发提速。
    3. Supabase 提供 Nuxt3 SDK,比 Auth0 更方便。
    4. Supabase 提供 VectorPG,方便我们使用 Embedding

    但是实际运行起来之后,却发现一些我未曾预料到的问题,主要跟用户登录状态有关。具体复现步骤为:

    1. 用户未登录时,先填写感受和目标,然后点“收信”,我们会要求他登录
    2. 用户登录后,我们需要先检查他的收信记录,确保每天只有收一封信
    3. 接下来,如果他今天还没有收信,我们就继续之前的过程,给他写信
    4. 但是偶尔,前面都正常,登录之后就卡住了,无法进入发信流程

    我尝试了很多方案,想解决这个问题,每次我都觉得自己搞定了,我甚至还写了一篇博客作为分享:使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:2. 处理用户注册/登录。但是之后老板就会再次遇到这个问题,然后把我一通数落。我怎么也找不到稳定重现的方式,只能反复查验代码,希望找到其中的漏洞。

    我有几个怀疑:

    1. Nuxt + Supabase,理论上存在两个环境有用户态:SSR与浏览器。我不确定这里会不会有问题。比如,为了防止 refresh token 的时候触发 onAuthStateChange,我用 useSupabaseUser 获取初始用户状态。会不会是它导致后面判断出错了(很小概率)?
    2. 我需要先检查用户的邮件历史,再决定是否请求信件。这里可能也会有问题,比如两次请求之间的先后顺序。
    3. Supabase SSO 只支持 redirect,必须跳走再回来,所以浏览器里的状态无法保存。从第三方回来,处理 access_token 期间,可能因为网络状态、本地内存状态等,进入到某个我没处理的分支,导致失败。
    4. 登录之后,onAuthStateChange 到用户登录彻底完成之前,中间其实有一小段时间,会不会是网络问题导致它被卡住了?

    总之吧,我现在一个人开发,面对这些问题有些焦头烂额,所以希望所有看到这篇博客的同学,能帮我测试一下网站:Dailylift.io,帮我想想其中可能存在的坑,如果您有处理类似问题的经验,也请多分享一些。无论您:

    1. 帮我找到稳定重现的方式
    2. 帮我固定住 bug 出现的现场
    3. 帮我找到代码中潜藏的问题
    4. 引导我找到正确的方向
    5. 分享有价值的经验

    我一定大红包伺候!感谢大家,期待帮忙。


    更新:

    2023-08-31

    今天我早上测试的时候,遇到一个很诡异的问题:我通过 Google 登录后,遇到一个加载信件列表 400 错误,导致获取新信件失败。

    看路径和参数,这个请求应该是正常的。最诡异的是,我在 Devtools 里看不到这个错误,只能看到两个 GET /letters 的请求,都是正常 200。

    我现在怀疑这个问题是不是这样的:

    1. SSR 阶段它也会发起一个请求
    2. 这个请求在某个情况下会失败
    3. 这个失败会导致渲染出的页面携带着失败的状态,继而无法继续。

    另外,因为我们使用 TiDB Serverless,会不会是冷启动时会产生长延迟,导致 Vercel Serverless 失败?不过我看了数据库,也有很多是几个小时后成功的,也不太像。

  • Nuxt 3 最佳实践与常见问题

    Nuxt 3 最佳实践与常见问题

    最近比较忙,也有些犯懒,所以博客视频都没怎么更新。今天一看,竟然断更将尽一个月,那无论如何得攒点东西。就把前阶段的笔记贴出来吧,是我使用 Nuxt3 的一些经验心得,希望对大家有所帮助。

    如果大家在使用 Nuxt3 期间有经验可以分享,或者问题需要讨论,欢迎留言。

    Nuxt 3 简介

    Nuxt.js 是一个基于 Vue.js 的服务端渲染应用框架,可以帮助开发者快速构建出高性能、SEO 友好的 Web 应用。Nuxt.js 3 是该框架的最新版本,它具有更快的启动时间、更快的构建时间和更先进的开箱即用功能。

    然而,在使用 Nuxt.js 3 进行项目开发时,我遇到了不少问题。本文会记录这些问题,以及我的经验。

    最佳实践

    下面是我从之前的问题里找到的最佳实践,应该问题不大,可以照搬。

    部署

    Nuxt 3 可以直接部署在 Vercel,兼具 SEO 与高用户体验。

    Serverless function

    默认情况下 Nuxt3 /api 和渲染都走 serverless function,理论上要弱于 Edge Function。我曾试着切换到 Edge Function,服务立刻就挂了,回头找个小服务摸索一下试试看。

    目前来看效能还可以,暂时没有体感延迟。我建议大家不要强求,把切换功能留给 Nuxt 核心团队来处理吧。

    加载远程数据

    SSR

    使用 useFetchuseAsyncData,Nuxt 会先加载远程数据,然后渲染页面,再进行页面的跳转。

    注意:SSR 阶段,组件发出的请求不会携带 cookie,这个设计是有意为之,避免个性化数据错误的出现在缓存层里面。如果希望 SSR 中包含个性化数据,可以手动操作,但请务必小心,不要泄漏不必要的请求头,因为可能带来安全隐患。

    具体做法可以参考官方文档:Passing Headers and cookies

    先跳转,再加载

    使用 useLazyFetchuseLazyAsyncData,Nuxt 不会等待请求完成,它会先渲染页面并跳转,然后在数据加载完成之后,更新视图。

    平时加载数据

    如果我们要像平时使用 Vue 那样加载数据并渲染,最好使用 $fetch

    使用 pendingrefresh 等复用请求代码

    比如页面里我们远程加载了一个对象,然后接下来,我们要重新加载这个对象,或者我们要根据某个参数加载别的对象,就可以利用 useFetchuseAsyncData 等返回的 pending 来渲染状态,或者用 refresh 重新发起请求,复用请求代码。

    尽量使用 default() 返回默认值

    很多时候我们远程加载的数据不是简单对象(文本、数字等),而是某个比较复杂的对象,比如一篇文章、一组人员数据。通常来说组件里的模版就负责渲染这组数据,如果不返回默认值,渲染可能会出错,可能导致页面跳转失败。

    所以我建议大家尽量返回默认值。

    配置 routes

    Nuxt 默认会对每个页面进行服务器端渲染,但很多时候我们并不需要,比如各种后台页面。所以我建议项目起始阶段就把各页面的渲染策略制定好。

    参考文档:Rendering Modes · Nuxt Concepts对首页、关于我们之类的纯静态页面,可以启用预渲染(preredering);对后台页面,就禁用 ssr;等。

    但是,大家也要小心错误的缓存机制,可能会把不应该缓存的页面缓存下来,造成渲染出错。

    使用 localStorage

    我们知道,Nuxt3 最大的特性就是 SSR,它会在服务器端渲染完页面,再返回给客户端。而 localStorage 顾名思义,只存在于用户本地,服务器端并不知道它的存在,也无法使用。所以如果我们要像平时那样使用 localStorage,我们必须控制好只在用户本地使用。如果要在 Nuxt3 里读取 localStorage 里的数据,我建议在 layout 里进行。经我测试,这是我们最初能插入 JS 的位置。

    // <script setup>
    import useStore from '~/store';
    
    const store = useStore();
    // 只有在客户端的时候才执行
    if (process.client) {
      store.init();
    }

    已有解,寻求最优解

    接下来的问题,我的方案能解决,但我不确定是否是最好的方案。所以我也在寻找更好的做法。

    1. useAsyncData 难以打断点

    问题

    useAsyncData 会在服务器端执行,所以在 Devtools 里打断点可能拦截不到。

    解法

    我目前的解法是在里面插入 console.log() ,利用热更新,切换页面,让断点生效。

    2. onBeforeMount 读取 localStorage 会丢失响应式

    版本

    v3.4(新版本待测试)

    问题

    非常诡异。

    我有一个变量 myRate ,因为一些理由,所以要把数据存在 localStorage 。我们知道 nuxt 在服务器端渲染的时候没有 window 对象,所以我就把它放在 onBeforeMount 。

    但是这样就会导致 myRate 丢失响应式,且只在 input className 丢失,非常诡异。

    解决

    我暂时放在 onMounted,虽然会造成页面抖动,但是工作正常。

    问题

    页面中有一个 nuxt-link 生成的 <a> ,点击无效,没有报错,浏览器和开发环境均无报错。

    解决

    实际上是 Vue 模版中某个变量名拼错了,渲染失败。但是不知道为何没有错误信息。找到并修正后,使用正常。

    暂时无解

    使用 route.hash 加载数据的话,SSR 会缺少数据

    当我们请求包含 hash 的地址,route.hash 在服务器端可能为空,导致无法正常启动服务器端渲染。请求会在渲染后再发出,造成界面抖动 CLS。

    解决方案就是不要用 hash 作为数据标记。

    @nuxtjs/i18n no_prefix 策略下,无法 SSR

    版本

    Nuxt@3.5 + @nuxtjs/i18n@8.0.0-beta.12

    问题

    1. 使用 no_prefix 策略
    2. detectBrowserLanguage: false
    3. 没有 SSR,渲染出来的是 key,然后变成目标语言

    Tips

    GET 请求里不能包含 body,否则可能 405

    如果用 useFetch 发起的 GET 请求包含 body/server/api 的接口会报告 405 Method Not Allowed 错误。

    这个问题主要不好排查。


    总结

    开始用 Nuxt 的时候,我没想到会有这么多问题:不就是提前渲染一下模版嘛,能麻烦到哪儿去?实际操作之后,发现很多熟悉的操作都不好处理,比如从 localStorage 里面取数据然后渲染到页面去,就要区分函数执行环境,否则就会报错;要渲染用户相关数据,得考虑服务器运行环境的复杂性,包括网络和多层缓存,不能想当然的使用 cookie 与 token。

    总之,心怀敬畏,持续学习,多看文档。有问题,欢迎提问;有建议,欢迎指教。大家一起进步。

  • 【视频】Nuxt3+Vercel+Serverless 全栈开发(5):部署到 Vercel+开发统计功能

    【视频】Nuxt3+Vercel+Serverless 全栈开发(5):部署到 Vercel+开发统计功能

    课程继续。仍然结合近期的开发经验,分享最近我比较喜欢的全栈+高效+免费+云原生技术方案。

    https://www.bilibili.com/video/BV16h411N762/ (请 B 站有号的同学帮忙点赞)

    本次课程内容:

    • 将作品部署到 Vercel
    • 使用自定义域名保障国内访问
    • 开发统计功能页面

    终于讲到部署环节了。其实 Vercel 对现代化 Web 全栈开发是非常重要的组成部分,它自带的 Serverless 功能是实现服务器端渲染(SSR)的关键,而且我们也需要使用 Serverless 来完成数据交互。部署到 Vercel 非常简单,点几下就能完成。

    Vercel 官方网站 vercel.com 在中国大陆可以无障碍访问,但是免费提供的二级域名 *.vercel.app 不行。解决方案很简单,自己注册一个域名,然后 CNAME 过去即可。注册域名的选项很多,各大云平台如阿里云、腾讯云均可。自己的小域名如果不提供大规模服务,不备案也可以用。价格大概一年十几块吧。

    然后讲解制作统计页面,主要涉及 Redis 的操作和前后端数据交互。之前埋下的数据结构设计,在此可以更好的演示。

    至此,这个系列基本连载完成。从技术选型、到项目搭建、到前后端开发、到最终部署上线,都覆盖到了。其它想讲的内容还有很多,不过都属于锦上添花,对于有开发经验的同学来说,相信现在已经可以动手。未来当然会继续深入,把统计功能加强一些,把关系型数据库也纳入进来。进阶课也在规划中,比如面对内容更庞大的网站,这个项目中太过简单的数据交互、缓存处理、SEO 都不够用,需要进阶强化。敬请期待吧。

    本周正好端午节,休息一周,周三(6月21日)晚上直播答疑,欢迎围观。


    这期视频也剪得很细,还做了字幕,希望大家能学到东西。

    如果你有任何问题、建议,欢迎留言讨论。请 b 站有号的同学帮忙分享完播一键三连,谢谢大家。

    另外,我也在 YouTube 上上传了一份,大家有空的话,麻烦帮忙关注下我的油管频道,感谢感谢。肉山全栈小课堂 – YouTube

  • 【视频】Nuxt3+Vercel+Serverless 全栈开发(4):投票接口+数据处理+了解服务器端渲染

    【视频】Nuxt3+Vercel+Serverless 全栈开发(4):投票接口+数据处理+了解服务器端渲染

    课程继续。仍然结合近期的开发经验,分享最近我比较喜欢的全栈+高效+免费+云原生技术方案。

    本次课程内容:

    • 创建 Post 接口,完成投票
    • 显示投票结果数据
    • 了解服务器端渲染

    我们先利用 Nuxt3 的路由机制,创建 /api/rate.post.ts 接口,用来处理用户投票,将数据记录进入 Redis 数据库的动作。

    为了保证数据安全,我每次会写入两次数据,一个键值是 $$$_{uid},主 key,用来日常显示;另一个键值是 {uid}_{小时},用来备份。这样假如某些情况下我们的投票系统被攻击,也能快速会滚到最近一小时的数据。并且这个备份频率也可以按需调整。

    然后我们在前端使用 $fetch 方法调用接口完成投票。并介绍了计算成绩的逻辑。

    最后介绍了 SSR 的结果,这样就能解释,为什么我们要使用 Nuxt3 内建的 useAsyncDatauseFetch 而不是平时常见的远程请求库手动请求。

    这期视频也剪得很细,还做了字幕,希望大家能学到东西。

    如果你有任何问题、建议,欢迎留言讨论。请 b 站有号的同学帮忙分享完播一键三连,谢谢大家。

    另外,我也在 YouTube 上上传了一份,大家有空的话,麻烦帮忙关注下我的油管频道,感谢感谢。肉山全栈小课堂 – YouTube

  • 【视频】Nuxt3+Vercel+Serverless 全栈开发(3):实现投票效果,使用 Redis

    【视频】Nuxt3+Vercel+Serverless 全栈开发(3):实现投票效果,使用 Redis

    课程继续。仍然结合近期的开发经验,分享最近我比较喜欢的全栈+高效+免费+云原生技术方案。

    本次课程内容:

    • CSS 里使用 ~ 兄弟选择器实现投票效果
    • 使用 Upstash Redis
    • 使用 Nuxt3 风格的加载方法读取数据

    打分组件,我们需要用到 ~ 选择器选择某个节点后面的所有弟弟节点,配合蒙版属性 mask-* 可以实现我们要的打分效果。

    接下来我们开始接触远程数据的操作。Vercel 免费版 Redis 每天只有 1k 的请求数,不太够用;如果升级付费版,也没法针对某个部分单独升级。所以很明显,Upstash 这样专门的服务商更合适。

    Nuxt3 为实现 SSR,对远程请求进行了封装,提供 useFetchuseLazyFetchuseAsyncDatauseLazyAsyncData 这 4 个新方法。其中前两者可以视作后两者的封装。在本节课里,我们先实现了读取数据的 API,然后用 useFetch 加载数据,实现服务器端渲染。

    这期视频也剪得很细,还做了字幕,希望大家能学到东西。

    如果你有任何问题、建议,欢迎留言讨论。请 b 站有号的同学帮忙分享完播一键三连,谢谢大家。

    另外,我也在 YouTube 上上传了一份,大家有空的话,麻烦帮忙关注下我的油管频道,感谢感谢。肉山全栈小课堂 – YouTube

  • 【视频】Nuxt3+Vercel+Serverless 全栈开发(2):配置 TailwindCSS,使用 grid 布局

    【视频】Nuxt3+Vercel+Serverless 全栈开发(2):配置 TailwindCSS,使用 grid 布局

    课程继续。仍然结合近期的开发经验,分享最近我比较喜欢的全栈+高效+免费+云原生技术方案。

    本次课程内容:

    • 配置 TailwindCSS
    • 使用 grid 布局构建页面

    Nuxt3 为支持 SSR,架构要复杂很多,所以大部分组件都要专门适配,比如 pinia,就有 @nuxt/pinia;vue-i18n,也有 @nuxt/i18n。同时,大部分组件的配置也都要归拢到一起,所以使用的时候一般要特意找一下,配一下。

    由于界面布局的因素,使用 flex 需要多一个容器,于是我干脆使用了 display:grid,这样代码会更简单。可能大家平时用 grid 不多,所以不妨从这个小项目入手。

    这次视频剪得比较细,字幕也做了,希望大家能学到东西。

    如果你有任何问题、建议,欢迎留言讨论。请 b 站有号的同学帮忙分享完播一键三连,谢谢大家。

    另外,我也在 YouTube 上上传了一份,大家有空的话,麻烦帮忙关注下我的油管频道,感谢感谢。肉山全栈小课堂 – YouTube

  • 【视频】Nuxt3+Vercel+Serverless 全栈开发(1):技术选型介绍,项目基础搭建

    【视频】Nuxt3+Vercel+Serverless 全栈开发(1):技术选型介绍,项目基础搭建

    大家好,时隔小半年,我又来做系列视频了。这次计划结合近期的一些开发经验,分享最近我比较喜欢的全栈+高效+免费+云原生技术方案。

    这套技术方案大约是这样:

    • Nuxt3
      • 提供 Vue 开发环境
      • 提供服务器渲染 SSR 能力
      • 提供 API 环境
    • Vercel
      • 提供部署环境和各项网站能力
      • 提供 Edge Function 和各种存储
      • 提供全球化服务能力
      • 提供 SSL
      • 免费额度足够用很久
    • Upstash
      • 提供 Redis
      • 免费额度足够用很久
      • 付费也不贵
    • TiDB Cloud Serverless
      • 云数据库,MySQL 兼容 API
      • HTTP endpoint
      • 免费额度足够用很久
    • 其它
      • Nuxt- i18n
      • TailwindCSS
      • DaisyUI
      • GitHub
      • pnpm

    使用这套技术方案,可以应对绝大多数网站需求,部署在全球化网络里,使用丰富的免费资源。云原生会带来天生的高可用性、鲁棒性、伸缩性。

    这是第一次分享,详细介绍了这套技术选型,nuxt3 项目的基础搭建,和它自带的路由、服务器 API 系统。

    这次会把每次的分享压缩到 25~30 分钟,降低观看门槛。

    如果你有任何问题、建议,欢迎留言讨论。请 b 站有号的同学帮忙分享完播一键三连,谢谢大家。

    另外,我也在 YouTube 上上传了一份,大家有空的话,麻烦帮忙关注下我的油管频道,感谢感谢。肉山全栈小课堂 – YouTube