分类: 技术

各种开发心得,包括语言、软件工程、开发工具等

  • React Native + Expo 入门级实战开发多平台应用 WhiteScreen:3. 深入开发,完成应用主体

    React Native + Expo 入门级实战开发多平台应用 WhiteScreen:3. 深入开发,完成应用主体

    感谢剪辑同学的努力工作,第三集上线。

    油管地址:https://youtu.be/0Ix-Y-MPQY0

    B站地址:https://www.bilibili.com/video/BV1CxnJzfEAk/

    继续分享 React Native+Expo 应用开发,帮大家补上全栈开发的最后一块拼图:移动应用开发。

    在这个系列视频里,我会用一个入门级小应用 WhiteScreen 作为范例,介绍如何使用 React Native+Expo 开发移动应用,并上传到应用商店。虽然国内市场基本被几个超级应用垄断,但是海外的广阔天地仍然大有可为。而 React Native 可以最大限度的利用我们已有的技术积累,让我们快速完成原型开发与需求验证,是全栈开发的不二之选。

    这期是第三次课,主要介绍:

    1. 使用 Zustand 管理状态,在页面间同步数据
    2. 使用原生组件进行布局
    3. 实现简单动画
    4. 实现本地数据存储于使用

    希望能给大家带来更多的可能性,在这个不那么容易的年代,让大家都有更多的选择余地与发展空间。

    希望大家留下宝贵的一键三连分享收藏,让更多的人能看到我的视频。

    有任何问题和建议,欢迎留言弹幕一起讨论。

    谢谢大家!

  • 【视频教程】React Native + Expo 入门级实战开发多平台应用 WhiteScreen:1. 移动应用开发现状+项目简介+技术栈简介

    【视频教程】React Native + Expo 入门级实战开发多平台应用 WhiteScreen:1. 移动应用开发现状+项目简介+技术栈简介

    前几天我在博客 感谢赞助商 Mizu Financial,重启我的自媒体之路 提到,因为工作调整,下一阶段我要捡起自媒体,在新的工作开始之前,提升一下自身的品牌价值。所以,我开启了新系列视频教程的录制。

    现在新视频终于上线,敬请观看。

    油管地址:https://youtu.be/YEPvihSIQkw

    B站地址:https://www.bilibili.com/video/BV1ZgHoztE4v/

    (更多…)
  • 聊聊错误/异常处理

    聊聊错误/异常处理

    又是繁忙的一周。突然发现以前没聊过错误/异常处理,准备分享一下。

    这里我就不细究错误(Error)/异常(Exception)的定义了,大体上程序执行过程中,一旦出现就会阻碍后续代码顺利执行的问题,我们都当它们是错误。我们要保证自己的代码能得到顺利执行,就要防止被它们破坏,就要捕获并处理错误。这就是接下来要讨论的要点。

    有些同学在这个领域搞得不伦不类:要么从来不捕获任何错误;要么所有地方都丢一个 try ... catch ...;要么最外层包一个大 try ... catch ...。这些做法都不对。本文分享我的观点和做法。

    开发时能够不出错,尽量不要出错

    虽然我自己也整天写 bug,并且被自己的 bug 折麽得死去活来,但我还是要说:我们应该在开发阶段尽量避免出现错误,保证我们的代码能够长时间运行不出错。这就需要我们考虑到各种边界情况,提前做出预判,操作前多做检查,避免出错。

    比如,如果使用 node.js 要操作文件,就先判断目标是否存在,目录是否已经创建过。而不是捕获各种可能出现的错误。用户要上传数据给服务器后端处理,就要先尽可能排除掉不可接受的情况,而不是被服务器拒绝后再转达给用户。

    捕获无法在开发阶段排除的错误

    也有一些错误,我们实在无法在开发阶段排除。最常见的就是网络问题。因为我们无法预期用户在怎样的网络条件下使用我们的产品,而现在用户的使用场景又极为丰富:无论是高速运动的火车,还是跨国网络环境,又或是第三方服务突然挂掉,都可能导致网络不畅,而请求失败。

    所以我们必须在发起网络请求时做好错误捕获,并且处理好网络造成的问题。比如:

    1. 告知用户当前状态,正确渲染 UI
    2. 保护好未能保存到服务器上的数据
    3. 提醒用户重试
    4. 提供其它临时保存方案

    简而言之,发起网络请求时没有 try ... catch ... 我在 code review 时是不会通过的。

    开发中见到错误要及时处理

    我见过一些同学的开发环境,跑起来满屏错误,但是好像也能运行,于是他们就不管。这样做是不行的。因为会被抛出的错误大多是未捕获的(Uncaught),它们大概率会破坏到某些代码的执行,没有被影响多半只是因为运气好,没有破坏到当前正在执行的逻辑。早晚还是要遭。

    另外,如果项目遇到了一些奇怪的问题,不知道该怎么解决;同时控制台里有一些奇奇怪怪的报错,那么解决这些报错很可能会带来意想不到的收获。

    总之,不要漏掉错误,不要容忍错误。否则积累技术债。

    要理解错误冒泡的中断机制,尤其是异步操作

    异步回调中的错误处理比较难做,因为回调前后是两个函数栈,所以执行时函数无法捕获到回调时的错误。举个例子吧:

    someAsyncMethod() // 可以直接被外层 try catch 捕获
      .then(() => {
        // 这里如果发生错误,无法被外层捕获
      })
      .catch(e => {
        // 所以我们通常要在最后处理错误
      })

    异步函数由于浏览器的升级,可以在堆栈最底层捕获到上层发生的错误。所以我们可以在底层函数,通常来说也就是执行时的函数捕获错。比如,我们有一个业务函数,要调用一个封装好的请求函数,那么,错误处理就应该放在业务函数里,而不是请求函数里:

    // 业务代码
    async function doDeletePost(id) {
      try {
        await deleteItem('post', id);  
      } catch (e) {
        // 应该在这里处理错误
      }
    }
    
    // 这里虽然是异步函数,请求远程接口,但不需要处理错误
    async function deleteItem(type, id) {
      await fetch(`/api/${type}/${id}, {
        method: 'DELETE',
      });
    }

    另外注意,没有 await 的异步函数就不存在错误捕获,不要写出这样的代码:

    function doDeletePost(id) {
      try {
        deleteItem('post', id); // 没有 await,没有意义
      } catch (e) {
        // 错误处理
      }
    }

    不要封装错误处理,在业务端处理,并提供准确的信息

    有些同学喜欢封装错误处理,我觉得当年的 axios 二次封装难辞其咎。对此我坚决反对,比如:后台同步数据出错,可能不需要告知用户;但是用户主动发起的操作失败就必须告知用户。加载数据失败和删除数据失败,其错误信息也是不一样的,应该准确传达给用户。

    所以,我们应该在业务端发起请求的地方捕获并处理错误,而不是封装一个通用请求类,并且集中处理可能的错误,给出无法区分的信息。

    错误信息要有价值

    有些同学拿到错误之后,直接 alert('出错了。') 这种错误信息对用户、对开发者都没有帮助。以网络请求为例,比较常见的做法是:

    1. 返回的信息首先应该遵守 HTTP 规范,即不同的结果使用不同的响应码。
    2. 返回的响应体包括 code 字段,用来传输错误码,它跟 HTTP 状态码没关系。成功的结果,code=0;错误的结果,code 则使用全局唯一的错误码,方便前后端协同排查。
    3. 错误码可以使用方便理解的规范,比如 XXXX-XXX-X 的形式,从大类到小类再到具体的函数位置等。这样,在任何位置捕获到错误,都可以快速定位到可能出错的环节,进行排查,解决问题。

    总结

    希望大家都掌握好错误处理的方法,能够妥善、快速的解决问题。如果各位对错误处理有一些特殊的理解,或者对错误处理有疑问,欢迎留言讨论。

  • 系列视频制作中:Next.js 全栈开发每日一签网站

    系列视频制作中:Next.js 全栈开发每日一签网站

    之前的几个系列基本都连载完毕。前些天开了一个新坑:全栈架构师系列。但是这个系列比较难录:讲得深,应用场景就窄,观众就少,像我这种发免费课的方式就比较亏;讲得浅,我觉得也没啥意思,担心将来大家一看,“就这?”。还有一点,技术难题不是每天都能遇到,要积累适合的话题,还要不担心泄漏技术秘密,其实比较难搞。所以只录了一期,因为第二期迟迟没想好怎么录,一直也没发。

    前阵子跟 201 老同事吃饭,了解到她也“毕业”了,正在小红书上创作“每日一签”,突然我觉得可以跟以前做的 拜拜 应用结合起来,搞个每日一签网站:

    1. 固定更新能提供 SEO 价值
    2. 拜佛和求签本身就是强关联的功能,可以把用户留住

    于是我们一拍即合,就有了 每日一签 这个网站。这个网站用到以下技术栈:

    1. 后台使用 Strapi 快速搭建,部署在 Zeabur 的 Docker 服务上
    2. 全栈开发使用 Next.js,App Router,全部 SSR,数据使用 Strapi RESTful API
    3. CSS 使用 TailwindCSS,并且基于 TailwindCSS 家的 Commit 模版二次开发
    4. 网站部署在 Vercel 上
    5. 数据库使用 TiDB Cloud Serverless (这次 TiDB Cloud 在我学习 Strapi 的过程中立了大功)
    6. 存储和 CDN 使用 Cloudflare R2
    7. 统计服务也用 Vercel

    这套技术栈完全免费,各种云服务的免费额度可以跑到一个很高的量。是我非常推荐的全栈技术栈,也是各种独立开发、出海创业的首选。所以从开始我就想把它做成一套全栈开发课程。正好可以作为之前几套全栈开发系列教程的补充:

    1. 基于 Next.js + React,便于大家学习和扩展技术栈
    2. 更复杂的网站,更丰富的功能
    3. 更复杂的数据结构和 API,但是更简单的后端处理
    4. 更全面的基础设施服务

    那么说干就干,我就一边开发一边制作视频。新的课程可能包含以下章节:

    1. 项目介绍,技术栈简介(视频制作中)
    2. 使用 Monorepo 管理复杂项目
    3. 使用 Docker 部署 Strapi 应用到 Zeabur (下周一 10-14 录制,正好填上欠 Zeabur 的推广)
    4. 使用 TiDB Cloud 提供数据库服务
    5. 使用 Cloudflare R2 提供存储和 CDN
    6. 使用 Next.js + Commit 模版开发网站
    7. 使用 Shadcn 组件库
    8. 添加每日页,制作 sitemap,提升 SEO
    9. 使用前端推送功能
    10. 使用 og 开发分享功能

    希望新的系列课程能帮助大家学会现代化的全栈开发,学会使用各种现代化的研发平台,给大家开展独立开发和技术出海带来新的机会。如果你对全栈开发、独立开发、技术出海感兴趣,请关注我的系列视频和博客更新。如果你对相关技术有疑问想寻求解答,欢迎留言。

  • 记一次不成功的数据库搬家

    记一次不成功的数据库搬家

    我这个博客始建于 2011 年,当时我从 ZOL 离职,表达欲旺盛的我,希望找个地方继续写博客,于是就买了台集成 WordPress 的虚拟主机,开始写技术博客。除博客之外,我也利用这个平台学习 Linux、nginx、PHP、MySQL 等服务器端软件的使用,从中受益良多。

    后来忘记哪年,从虚拟主机换到 VPS,终于可以拥有自己全权控制的服务器了。当时很兴奋,觉得自己应该在后端取得更深入的发展,于是开始研究 PHP + WordPress 开发,准备基于 WordPress 做一些尝试,模版、插件、WooCommerce,感觉能做的东西很多。

    不过坦率地说,WordPress 历史太久,历史包袱太多,代码架构不好,二次开发很难受,属于能用但是不好用的状态,所以我的学习进度一直不快。另一方面,我的职业机会也不少,所以真正投入在这个方向的时间并不多,拖着拖着就黄了。

    不过我一直在坚持更新博客,后来我发现数据库也不小,每次导出 + 备份都需要不少时间。正巧当时我了解到 TiDB,还恰逢 TiDB Cloud 初次发布,感觉似乎可以把数据库放在云端,这样就一下解决了自动扩容和自动备份这两个需求(其实都没需要)。于是就把数据库迁移过去。

    最初使用 TiDB Cloud 时,因为刚刚参加完 TiDB Hackathon,账户上有免费赠送的额度,所以没感觉有什么问题。但是从去年开始,我发现每个月都要付给 TiDB 几美金费用。我研究了一下 TiDB Cloud 的计费体系,主要分成两大项:

    1. 容量。免费 5G,我的用量远远不够。
    2. 计算用量,RU。不太确定怎么计算,但是我用超甚多,所以费用都产生自这里。

    我首先排除 Slow SQL 的问题。WordPress 这点做得挺好,毕竟是世界上用户数量最大的软件,我也没有用很多来源不明的插件,所以并没有 Slow SQL。

    接下来通过 Metrics 面板查阅,看不太出来用量和时间有什么关系,偶尔我的博客访问量大的时候,RU 会涨一点点,但其实影响不大。

    然后我试图增加 CDN,给页面增加缓存时间等,意图通过降低访问压力降低数据库消耗,但也都没什么实际作用。

    然后我尝试移除一些插件,比如 Jetpack 系列,避免来自 WordPress 官方的请求消耗掉我的额度,也失败了。

    最后我只好诉诸代码,研究来研究去,得到结论:

    1. 我这个博客时间很久,收录也不错,多半已经处于各种垃圾营销的大列表里面
    2. 于是会有很多自动化的机器人来抓取、注册、灌垃圾评论等等
    3. 这些机器人大部分不走网页,没法被缓存,而且每次都会大量读写 wp_options
    4. 不知道具体原因,不过对于 TiDB Cloud 来说,这种对小表的高频请求会消耗大量的 RU
    5. 在不改变架构的前提下,这个问题无法解决

    于是,在九月份被刷掉 $15 之后,我终于忍不了了,利用国庆,把数据库迁回本地。随便刷吧,nnd。下一步,肯定是要重新建站了,把 WordPress 当成纯 C MS,前端用现代化架构重新构建。正好也督促自己把 Awesome Comment 的 SaaS 做起来。

    最后强调一下,这个跟 TiDB Cloud 没关系,纯粹因为项目不合适。我现在有好几个服务都在蹭他们家的免费流量,一点问题没有,非常顺利。

  • 【转载】Ilya Sutskever 的 Prompt tips

    在 X 上看到这个分享,不得不说,Ilya 真是技术宅,发的图片,还是糊的……

    用 macOS 的文字识别摘下来慢慢看。

    1. Communicate clearly and precisely when writing prompts. The ability to clearly state tasks and describe concepts is crucial.
    2. Be willing to iterate rapidly, sending many prompts to the model in quick succession.
      Good prompt engineers are comfortable with constant back-and-forth refinement.
    3. Consider edge cases and unusual scenarios when designing prompts. Think about how your prompt might fail in atypical situations.
    4. Test your prompts with imperfect, realistic user inputs. Don’t assume users will provide perfectly formatted or grammatically correct queries.
    5. Read and analyze model outputs carefully. Pay close attention to whether the model is following instructions as intended.
    6. Strip away assumptions and clearly communicate the full set of information needed for a task. Break down the task systematically to ensure all necessary details are included.
    7. Think about the “theory of mind” of the model when writing prompts. Consider how the model might interpret your instructions differently than intended.
    8. Use version control and track experiments when working with prompts. Treat prompts like code in terms of management and iteration.
    9. Ask the model to identify unclear parts or ambiguities in your instructions. This can help refine and improve your prompts.
    10. Be precise without overcomplicating. Aim for clear task descriptions without building unnecessary abstractions.
    11. Consider the balance between typical cases and edge cases. While handling edge cases is important, don’t neglect the primary use case.
    12. Think about how prompts integrate into larger systems. Consider factors like data sources, latency, and overall system design.
    13. Don’t rely solely on writing skills; prompt engineering requires a mix of clear communication and systematic thinking. Good writers aren’t necessarily good prompt engineers, and vice versa.
    14. When working with customers, help them understand the realities of user input.
      Guide them to consider real-world usage patterns rather than idealized scenarios.
    15. Practice looking at data and model outputs extensively. Familiarize yourself with how the model responds to different types of prompts and inputs.
  • 【视频】接单小故事:关于主人翁意识,主动承担责任。只有大家都靠谱,才会有好结果。

    【视频】接单小故事:关于主人翁意识,主动承担责任。只有大家都靠谱,才会有好结果。

    本来想周末写技术文章的,但是由于苦攻《黑神话:悟空》,加上最近几周开始尝试用 React Native + Expo 开发移动 App。时间非常紧张,所以水一篇视频文章来更新。

    这次跟大家讲一个真实的故事。因为故事中的人物都是朋友,所以我一概隐去真名实姓。故事保真,细节大差不差,目的不是批判谁,希望给大家带来一些启发。

    希望分享给大家的是:

    1. 创业做事情,目标是共同成功
    2. 所以,没有谁是领导谁是员工,大家都要积极主动,能多做就不要少做
    3. 做好事情,大家一起受益;事情黄了,大家都没好处
    4. 大家都靠谱,才是真靠谱

    希望给大家带来一些启发,尤其是在现在这个时间点,大环境不好,又有一批新人刚刚毕业,我希望大家都能本着把事情做好的想法积极努力。

    有任何问题和建议,欢迎留言讨论。看完视频的同学麻烦去b站帮忙三连分享,谢谢大家。

  • 2024 中国大陆搭建 React Native 开发环境

    2024 中国大陆搭建 React Native 开发环境

    我从 Web 前端做起,后来发展到全栈,至今十几年。我觉得大陆的网络环境对 Web 开发还算比较友好,除去 Google 之外,大部分网站都能轻易访问,大部分网络产品都能自由使用。比如 GitHub,NPM,直接访问都没什么问题。只有少数几个软件包比较麻烦,也多半跟 Google 有关,比如关联到 chromium 的 Electron、Puppeteer 等。

    上周开始准备做 React Native 开发,真的踩了不少坑,感觉大陆网络对移动开发相当不友好……Vincent 问我:搭环境需要这么久么?问一下 GPT 不是一两个小时就搞定了?我微笑着告诉他,他把这件事情想简单了。于是他也的确花了一天时间才把 demo 跑起来。这里我就分享一下最新的知识吧。

    Expo

    Expor 之于 React Native,就像 Next.js 之于 React。它是一个上层的框架,提供很多常用的组件和工具函数。比如基于文件目录结构的路由管理系统,以及从之衍生出的全局 URL、页面间跳转功能等。还有一些页面组件,比如 Stack 等。

    我建议使用 Expo,我相信能给我们节省很多自己码代码的时间。学别人规划好的框架,很多时候比自己慢慢手搓要快很多。

    不需要 Expo Go

    Expo 的网站会推荐我们安装 Expo Go 作为开发调试工具。不过实测之后我发现,App Store 的 iOS 版本无法手动输入 URL,而且我们构建项目的时候,就会包含 Expo Go 的功能,所以这个东西在不需要 EAS(即 Expo 提供的云编译服务)时完全没用,可以不用考虑。

    iOS

    Ruby & Gem

    使用 React Native 需要用到 CocosPods,这是基于 Ruby 写的一些东西,所以必须通过 Gem 下载。国内访问 Gem 源非常的慢,几乎不可用。

    不过解决方案很简单,换国内镜像即可。

    升级 Xcode 及 SDK

    虽然可能不开发原生应用,但我相信所有开发者 macOS 上都有安装 Xcode。第一次安装 Xcode 的时候,可能会随手装一些 SDK 并创建虚拟机,那么当你准备尝试 React Native 开发,配置开发环境的时候,旧的 SDK 和虚拟机就可能带来问题。

    解决方案也不复杂:删掉旧的虚拟机,安装新版本 SDK,即可。

    项目目录不能有空格

    2024 年了,居然还有这种问题……总之吧,从根目录算起,一直到我们的 React Native 项目目录,目录名都不能有空格。我建议尽量只使用英文字母和数字作为目录名,几乎不会遇到问题。

    Android

    Android 所需的网络环境之恶劣超出我的想象。可能跟它比较依赖 Google 服务有关,大量服务要么完全无法访问,要么速度非常慢。而且跟梯子没什么关系,很多东西可以直连下载,但就是慢的无法使用。

    不要使用太新的 SDK

    比如以目前这个时间点,Android 35 和 NDK 27 都不行,最高只可以用 Android 34 + NDK 26。我甚至建议大家用旧一些的,反正我们搞 React Native,没必要追新。

    手动处理 gradle-{VERSION}-all.zip

    我的 Android 环境 第一关卡在下载 gradle-N-N-all.zip 上。其实我本地网络直连下载也没问题,但就是太慢。200+MB 的包每次都超时。解决方案是先手动下载,慢慢下不着急,普通网络也几乎不会失败。

    然后把文件放到 android/gradle/wrapper/ 目录下面。接着修改 android/gradle/wrapper/gradle-wrapper.properties 文件,把其中 distributionUrl 指向对应的相对路径,即可。

    再次执行 expo run:android,就从本地处理,速度飞快。

    手动处理
    react-android-{VERSION}-debug.aar

    第二个卡住我的是 react-android-0.74.5-debug.aar,问题表现跟上面的 gradle 一样,不是不能下载,就是慢。以至于我抱着试一试的想法坚持了两天……

    最终找到解决方案,手动下载文件,然后找到本地目录 $HOME/.gradle/caches/modules-2/files-2.1/com.facebook.react/react-android ,在里面找到对应的版本号,然后在里面可以看到几个目录,找到里面有 .pom 文件的目录,把刚才下载好的文件放进去,即可。

    其它文件

    其它文件下载也需要很久,但是不至于因为超时失败,我就不多说了,耐心等待即可。我也试过换源,但是不知为何,都不成功,可能跟 React Native 有关?国内镜像源都只针对了一般 Android 仓库?

    总结

    希望以后网络环境会愈来越好。希望这篇博客里的经验对大家有用。

    以后要好好钻研 React Native 开发了,相信会有更多的内容分享给大家。敬请期待吧。如果各位读者有什么想看的,也不妨留言告诉我。

  • 解决跨时区 Nuxt SSR 导致的页面离奇 Bug

    解决跨时区 Nuxt SSR 导致的页面离奇 Bug

    打开后台一看,如果本周再不写博客,就三周没更了,还是抽点时间分享点东西吧。今天结合近期解决的问题,分享一下出海服务全球用户,并且使用 SSR 时可能遇到的问题。

    问题

    我厂的 AI 日记产品 DailyLift 遇到一个非常奇怪的问题。合伙人发现,在午夜过后,打开我们的网页,界面会渲染得非常奇怪。

    这里的问题很多,不管是文字样式还是背景图案,都很奇怪,只看错误截图和代码完全没有思路。给不了解我厂产品的读者介绍一下上图中的 bug:

    1. No data 表示当天无数据,正常情况时字体很小。
    2. 只有具体的数字才应该显示成这么大的字体。
    3. 配图也不对,no data 对应的应该是其它图片。
    4. No data 自然不应该有弧形进度条。

    难以复现的 bug

    我们是远程工作,我一般会把工作时间固定在早上 9:30~晚上 6:30。当我次日开始工作,打开开发环境开始 debug,这个 bug 完全无法重现。对合伙人来说,这个 bug 也是时有时无。所以,这个 bug 在我们的任务列表里开了关关了开,反反复复,非常难搞。

    但是随着时间发展,我们还是总结了一些问题特征:

    1. 此问题只在午夜零点之后出现
    2. 此问题只在刚发布过日志之后出现(这个很关键,可惜我解决 bug 那天才发现)
    3. 此问题只在新加载页面时出现
    4. 此问题只在线上(生产 / 测试)环境中出现

    至此,我还是没有什么思路,只是觉得,可能要午夜的时候来调试一下。

    捕获 bug

    上上周,儿子考试结束,我们带他去长沙武汉各玩两天,逛街三天,赶车三次,日均 1w 步之后,我的膝盖很累。导致周三的力量举 PR 测试状态奇差,离既定目标差了一半。(感兴趣的同学可以看下:年中测试新极限,逛街三天赶车两次,状态很差,只完成目标的一边。下次继续努力吧

    晚上睡觉前,我心情比较低落,毕竟努力了半年,之前的运动表现估算起来,肯定不止这个数字。于是我就随手打开 DailyLift,写了篇日志。然后,我鬼使神差地想起这个 bug,随手刷新了一下页面,正所谓失之东隅,收之桑榆,竟然复现了这个 bug!

    简单介绍一下我们的技术选型:

    1. Nuxt
    2. Supabase
    3. 部署在 Vercel,非企业用户,只能选择特定区域,于是我们选择美西服务器

    开发环境无法复现,线上环境稳定复现。虽然表象无法推测问题所在,但也是巨大进步。我思来想去,突然想到,会不会跟服务器端渲染(SSR)有关?于是我查看源代码——注意,是源代码,不是 DOM 树;因为 DOM 树可能会被 Vue(Nuxt)修改过,不是初始状态——一看,果然,服务器端渲染的结果跟我页面的效果不一样。而这样的结果,是由于时区导致的。

    我的博客发表在 12 点之前,刷新页面在 12 点之后。如果大家观察上图,可以发现,我们是按照日期来分割数据的。我身处 +8 时区,所以我的 N 日 0 点,是 UTC N-1 日 16 点;Vercel 服务器位于美西,差不多是 -7 时区。于是,Vercel 在服务器端渲染页面的时候,他认为我的这篇日志,应该是 N-1 日 9 点钟写的,N-1 天还没结束,所以是当天(Today)的日志。 但是当我在本地运行 Vue 的时候,我已经是 N+1 日了,于是我本地的代码认为日志发表在昨天,今天还没写过日志。

    于是,Nuxt 尝试对 DOM 进行修改,但不知为何,它复用了服务器端传回来的部分 HTML,修改了部分 HTML,所以就出现了前面的 bug。

    解决 Bug

    找到问题根源之后,解决问题就没那么难了。唯一的难点就是克服测极限带来的身体疲劳,不过找到问题的兴奋感冲淡了这份疲劳。

    查阅文档之后我发现,Vercel 倒是早就帮我们准备了解决方案,我们只需要用useRequestHeader('x-vercel-ip-timezone') 就可以获取到用户所在的时区,接下来,只要保证整个 SSR 在这个时区里进行就好。

    我使用的 dayjs 来处理时间,它自带 timezone 功能,所以给所有 dayjs() 后面加上 dayjs().tz(tzHeader) 就可以。

    总结

    最后,困扰我们一个月之久的 Bug 终于解决。我也意识到处理时区在全球化应用开发时的重要性。合伙人问我有没有办法确保类似的问题不再发生,我回他:我这辈子都不会忘了处理时区了……

    希望这篇博客对你的出海应用开发有帮助。如果你对 Nuxt、服务器端渲染、云服务器等有问题,欢迎留言讨论。