我的技术和生活

  • 【电鸭分享】如何找到一份兼职远程工作?

    【电鸭分享】如何找到一份兼职远程工作?

    自我介绍

    大家好,我是 Meathill,中年光头程序员,兴趣使然的分享者。我喜欢编程,希望把程序员作为终身职业坚持下去。我也喜欢学习新技术,分享各种技术和职业相关的话题。目前,我跟一位老板合作,尝试用 AI 做一些改善人们生活的产品。

    我跟电鸭缘分不浅。初识是在一早一晚,后面来到电鸭。去年,在前厂资金链断裂,被裁员之后,我在电鸭上找到两份工作机会,一份就是我目前的全职远程;另一份因为时间有限,只帮他们做一些顾问工作,提供咨询和建议。

    所以我经常以电鸭自来水自居,在各种帖子里挺电鸭。我看到有些同学想找兼职、远程的工作,但是一时不明就里,所以写一篇文章分享一下。

    先说总则

    1. 坚持做好事,做对的事
    2. 广结善缘
    3. 保持耐心,不要急于求成

    努力做好你现在手头的工作

    如果你现在还没找到很好的远程,或兼职机会,那么我建议你,先努力做好手头工作。

    我认为,现在的工作,是你最容易得到别人认可、积累技术经验、学习项目管理知识的机会。基本上,我能合作到现在的设计师、开发者、甚至老板,都是在某段职业经历中结识的。长期合作让我们有充足时间了解彼此,磨合协作方式,建立长期联系。这样积累的人际关系远比日后网上随机组队来的靠谱。

    所以,不管你正在经历什么样的工作状态,我都建议你:

    • 努力做好手头工作
    • 建立好的合作关系
    • 寻求长期合作伙伴

    做好自我介绍

    常在电鸭闲逛的同学会看到很多招募帖,帖子下面总会有或多或少的自荐跟贴。不过坦率地说,大部分自荐跟帖都是浪费时间,除非你写了一个机器人自动跟帖,否则单凭这样的跟帖,能找到靠谱的工作机会,才怪。

    电鸭虽然需要花费电量才能“买”到联系方式,但其实电量获取并不难,多分享些知识、经验,攒几百电量轻轻松松。

    用电量获取联系方式之后,认真写一封求职信,讲明自己的优势,自己能拿出多少工作时间,自己对项目的期待等,发给招募方,大部分时间都能收获认真的回复。即使有多人同时竞争这个机会,愿意先付出的人常常也能脱颖而出。

    扩展自己的技术栈

    与大公司兵强马壮粮草充足不同,会在电鸭这样的第三方平台招募开发者的老板,通常来说预算有限,无法支持完整团队。不过,另一方面,他们要做的全新产品通常也没有多少用户,基本上随便用什么技术栈都能应对。

    所以,如果我们能够独立完成项目开发,机会就多的多。

    目前来说,最合适的技术栈当然是基于 Node.js 的 Nuxt/Next,配合 Vercel、Cloudflare 等云平台,前后端同构、Serverless Function 高速响应及自动伸缩、免费额度完全可以应对 MVP 需求等。可以做到快速低成本启动,对老板和开发者来说都是好事。建议大家有空多看看。

    私货时间,推荐我去年做的全栈开发教学视频:Nuxt3+Vercel+Serverless 数据库全栈开发

    扩展自己的团队

    技术栈再全面,也很难一个人做完全部工作,很多时候,团队带来的帮助绝不是简单的 1+1,而是 N*N。所以我建议大家可以的话,努力组建自己的团队。独行快,众行远。

    不过我不觉得在论坛上随机组队是个好办法,我觉得还是从工作合作中找比较靠谱的人一起组队比较好。

    团队人可以不用太多,每项工作都有人能胜任即可,比如我日常合作的就只有几位 PM+设计师、前端、全栈工程师而已。如果他们没时间,很多东西我宁愿不做。

    只做利润率高、值得付出的项目

    接下来就涉及到“项目选择”这个话题了。 我认为,真正值得做的项目并不多,能够合作的老板也很少,能够开心协作的团队更难找,所以我建议大家对远程兼职这件事不要太执着,很多时候,做不了接不到,是老天在帮你。

    比如论坛上很多几百块,小几千但是要求很高的项目, 我就觉得完全没必要做。

    不过话说回来,我也不觉得嫌工资低就可以去别人帖子里阴阳怪气。只要大家都坦诚相待,认真做事,及时结算,那就是好的合作关系。

    与合适的人建立长期合作关系

    正如前面所说,好团队、好老板、好项目很难找到,所以我建议大家一旦遇到了,就好好珍惜,有时候,亏点时间、亏点钱、累一点,也得维护好。

    我的建议是,如果你还年轻,比如处在职业生涯的前 10 年,有长期做远程、独立开发、接单外包这方面的想法,那么我建议你找好队友,找好老板,找好项目,好好建立长期合作的关系。不要太在意一时一地的得失。

    总结

    正如我在一个帖子里的回复那样:

    我觉得,只要你是个靠谱、出色、且技术栈相对全面的程序员,兼职私单的机会几乎是做不完的。

    我的个人经验告诉我,按照前面的行事要点,坚持一段比较长的时间,就能达到:只要市场上有人在找人做一些兼职私单,那你就有很大的机会。

    希望我的分享对大家有帮助,如果你有意见、疑问、其它想分享的内容,欢迎跟帖讨论。祝大家 2024 都能有各种各样丰富多彩的职业机会。

    我还有其它一些 关于“程序员副业”的分享,欢迎大家观看,并求一键三连。

  • CSS 小教程:在网格型选择工具上添加渐变背景

    CSS 小教程:在网格型选择工具上添加渐变背景

    新年开始了,要努力,本博客开始 2024 年招商,欢迎各位想推广产品的老板投广告,目前定价 4800/年,亦可增加评测文章、教学文章配合,详情可与我联系。具体详情更新在 本站广告 2024 年招商


    好几个月之前,我遇到这样一个需求:

    这个东西叫作 Emotional Scale,是一大堆描述情绪的形容词,存在从积极到消极的渐进关系,老板希望我们用颜色给予它们比较明显的区分。于是我就想到这样一种表现方式:

    1. 用网格化来布局每个形容词
    2. 给它们添加从上至下的渐变背景
    3. 再让 ChatGPT 给每个形容词选一个 emoji

    最后一项不用多说,直接丢给 ChatGPT 就行。这里重点说下前面两个。

    网格化布局用 display:grid + grid-template-columns: repeat(N, minmax(0, 1fr)) 即可,其中 N 可以根据宽度调整成需要的列数。

    接下来处理渐变。这里的难点在于,每个小格子是独立的,要维持统一的渐变比较困难。我想到两个方式:

    1. 画一个大的渐变背景,然后用 clip-path
    2. 通过计算的方式给每个小格子加渐变背景

    后面我决定选择第二种方式。我总共有 38 个格子,一行 4 个的话,就是 10 行;有 6 个颜色,刚好每两行是一个过渡。于是一行就应该是 0 -> 50%,如何渐变一半呢?我尝试了一下,发现可以用 -100% 和 200% 来做。

    于是我就配合 Vue 开发了这个组件:

    <script lang="ts" setup>
    function getBgColor(index: number): string {
    const colors = ['#1B4397', '#648EF7', '#5DC264', '#FFAE43', '#F15146', '#b91c1c']; // 总共 6 个颜色
    const start = index / 8 >> 0; // 8个=2行,就是两行完成两个颜色的过渡
    const end = start + 1;
    const line2 = index % 8 >= 4;

      // 借助 CSS 变量完成过渡效果
    return `--tw-gradient-stops: ${colors[start]} ${line2 ? '-100%' : ''}, ${colors[end]} ${line2 ? '' : '200%'}`;
    }
    </script>

    <template lang="pug">
      .grid.grid-cols-4.gap-2
    label.flex.flex-col.justify-center.items-center.bg-base-100.rounded-3xl.w-full.aspect-square.cursor-pointer.bg-gradient-to-b(
    v-for="([label, face], index) in todayDoingsScale"
    class="hover:bg-base-200"
    :style="getBgColor(index)"
    :key="index"
    @click="doSelect"
    )
    input.hidden(
    type="radio"
    name="feel"
    :value="label"
    v-model="score"
    )
    </template>

    最终完成的效果就如同上面截图所示。

    CSS 作为前端开发的必备技能,搞起来别有一番乐趣。新特性不断被添加进来,就有越来越多的功能可以实现,有越来越多的功能可以更容易的实现。相信我们下一步的开发环境和产品体验会更好。

    如果各位读者对上面的 CSS 实现有什么问题,或者有其它关于 CSS 的问题,欢迎留言提问。

  • SSR,云平台,ChatGPT——我的 2023 技术关键词

    SSR,云平台,ChatGPT——我的 2023 技术关键词

    前言

    2023 年,因为换工作,启动新项目等原因,我对我的技术栈进行了比较大的更新,主要集中在这三个方向:

    1. SSR(Server Side Rendering,服务器端渲染)。之前我开发的项目基本上都是 SPA(Single Page Application),比如 Vue,但之后我会越来越多开始用 Nuxt。由于基础设施的发展,以后 SSR 会更方便更好用。
    2. 云平台。以前我大概买了 3、4 台云服务器用来做各种尝试,在上面各种折腾。去年使用 Vercel、Supabase、CloudFlare 平台之后,我已经不打算再在服务器上浪费时间了,云平台实在太好用了。未来我会努力把所有服务都迁移到云平台上,新增产品都直接云原生。
    3. ChatGPT。相信不只是我,很多人都会把 ChatGPT 作为去年技术的首选关键词。如今我不仅在上面完成产品开发,日常也会使用它替代大部分的搜索;甚至我家孩子写作业也会使用它来帮忙。我认为,未来 ChatGPT 就像是搜索引擎一样,决定了一个人的起点和成长速度。

    接下来逐个分享。

    服务器端渲染,SSR

    起初我不是很看重 SSR,我总觉得,我当年也写过 PHP,有什么“服务器端渲染”我没见过?实际用过之后,我承认:真香……

    首先,使用 SSR 可以提升用户体验,且有利于 SEO,这点相信大家都知道。如果对其原理不太清楚的话,欢迎观看我的视频:从浏览器渲染机制理解 Web 性能——“在浏览器地址栏输入 URL,按下回车后会发生什么?”

    其次,如今的 SSR 与当年 PHP 模版套页面的实现有很大区别:

    1. 语言同构化:开发难度大大降低,没有心智负担。
    2. 数据传递与状态管理:虽然数据不能完全通用,但是框架尽量会帮我们处理好,让我们在服务器端和客户端都能自由使用。
    3. 渲染由边缘计算负责:这一点有点依赖云平台,不过考虑到浏览器的渲染机制,SSR 并不会拖慢渲染速度,用户体验只会更好。
    4. 页面切换不需要重新加载。对于旧的编程语言来说,因为前后端环境割裂,所以页面切换的时候都是重新加载完整页面;但是新框架下,则只需要加载数据即可,此处跟 SPA 的体验无二。

    第三,如今的 SSR 框架都很好的整合了服务器,包括中间件等功能,还有各种官方第三方模块支持,能大大降低我们开发服务器软件的成本。所以已经是我启动新项目的不二之选。

    云平台

    以前我长期维护好几台服务器,一方面可以部署自己做的产品 demo,另一方面也可以部署一些开源项目方便日常使用。因为各种云都有面向新用户的优惠活动,所以成本不高,我觉得值得一搞。

    自己的服务器当然比较比较自由,坏处就是免不了产生运维成本,即使使用 docker 也一样。部署新代码至少要去跑一遍拉取脚本,对吧?我的一位老板朋友甚至请我帮忙写了一套服务器脚本,用来做 CI/CD。

    初期这么搞没问题,但后来就越来越觉得功能不够,性价比也太低,开始寻求替代方案。之前我参加 Hackathon 的时候了解到 Vercel 云平台。它与 GitHub Pages 不同,支持 SSR、支持云函数,配合一些云数据库,比如 Upstash,可以快速搭建起来一套可用的服务。去年年初,我的那位老板朋友想做一套打分系统,放在他的静态网站里,于是,我就尝试用 Nuxt.js + Upstash 开发了一套,并且部署在 Vercel 上,效果非常好,免运维,多环境,推到 GitHub 自动部署,实在太好用。

    我把这个过程制作成了系列课程:Nuxt3+Vercel+Serverless 数据库全栈开发。大家感兴趣不妨看一看。

    后面一发不可收拾,过去一年我不再采购新的单体服务器,旧的服务器用完也不再续费。新产品都部署在 Vercel 等云平台上面,帮我节省了大量的时间。

    Vercel 去年年中的时候开通了存储功能,实际上就是打包了几家云数据库服务来卖,我也很快获准开通。从此,云平台使用就更加顺利了。临近年底,我尝试 CloudFlare Pages,效果也非常好。他们家的优势是自带统计分析功能,远比 Vercel 大方,一站式解决更省心。

    云数据库方面,我使用 Upstash 的 Redis,KV 数据库足以满足大部分产品需求。数据库用 Supabase 和 TiDB 比较多。前者支持 PG Vector,方便我们进行 LLM Embedding & Search;后者则提供 5GB 免费额度,比较好用。云存储有 CF 的 R2,空间和流量也相当充足。如果不是 PHP 太老没人支持,我都想把博客这台机器退掉了。

    ChatGPT,以及其它

    ChatGPT 更是值得大书特书的一个技术关键词。不过考虑到大家去年一整年应该已经被类似的内容淹没了,所以我这里就少写一些,只说说我的情况。

    我目前订阅了 ChatGPT+,方法是借用国外亲戚的手机号注册,并且用他的手机号注册 PayPal,通过 Google Play 订阅。订阅的原因是 ChatGPT 4 + DALL-E 都可以随便用,比 API 便宜得多。

    在编程领域,GPT-4 比 GPT-3.5 好太多了,知识库更新到去年 4 月份之后,除了 next.js 14 的内容外,我日常的编程问题大多可以用 GPT-4 解决,比如:

    • 写正则
    • 写 SQL
    • 查函数、查第三方库
    • 纠正函数错误

    帮我节省了大量的 Google 时间,单凭这点,每月 $19.99 的订阅费用就很值得。

    除此之外,我还在继续使用 GitHub Copilot。Copilot 也很好用,除了生成工具函数、编写测试外,我发现翻译语言和框架方面也有很大的作用。去年我就完全靠它开发了一个 flutter 应用,方法就是把 TS+Vue 写好的代码丢给它让它翻译。

    所以,无论是学习新东西,保障日常开发,还是扩展新领域,AI 对我都帮助巨大。

    总结

    总而言之,如果再有同学问,前端想学后端,应用学什么语言框架以及是否需要搭自己的服务器?我都会建议他们:不要学 Express、Koa;习惯用 Vue 就学 Nuxt,习惯用 React 就学 Next.js;不需要搭建服务器,就用云存储就能解决绝大多数问题。

    我还建议大家,尽快想办法开通 ChatGPT,再不济国产大模型也要用起来,未来是 AI 的时代,学会用 AI,效率会大幅度提升。半年的初入门新人,善用 AI 可以赶上 3 年的老程序员;而老程序员学会用 AI 之后,可以快速把自己的能力扩展到其它领域。

    以上,就是我去年关键的技术栈总结,希望对大家有所帮助。如果大家有什么意见建议,想说的想聊的,欢迎留言。

  • 山穷水尽疑无路,柳暗花明又一村——告别 2023,迎接 2024

    山穷水尽疑无路,柳暗花明又一村——告别 2023,迎接 2024

    回顾 2023

    过去几年职业发展不太顺。先是告别 O 厂,但是被金山坑;然后被 Code.fun 收留,可惜也没能坚持到最后。今年开年,面对各种唱衰的预期,说心不慌那肯定是假话。好在 2022 年年底,ChatGPT 3.5 在业内崭露头角,当时我就觉得,2023 一定会有新机会。

    事实证明果然没错。2023 年过完新年,微软 PR 部门回来上班之后,开始马不停蹄地大力宣传 ChatGPT。然后 ChatGPT 成功引爆全行业,成为 2023 年最大的亮点。而我呢,拜 ChatGPT 所赐,结识新老板 Vincent,找到新的工作机会,顺利苟到年底。

    去年算是 ChatGPT 元年,开发活动非常多,我也到处呼朋引伴参与其中。做得比较好的有年初的 思否 AIGC Hackathon,我们的作品 拜拜 获得最佳人气奖。并且在过去的一年里,虽然没有做过任何推广,也没有悉心维护,现在每天都仍有十个左右的用户在使用。后来我们用 Awesome Comment 报名参加了 TiDB Serverless Hackathon,可惜在一众没有前途的 AI 作品的包围下,没有获得成绩。

    年初没有工作的时候,我还跟一家 Web3 教育公司的创始人合作,开发了 HackQuest.io 的 Hackathon 版本。如今他们的版本迭代顺利,相信来年会取得更大进展,回头我也去学学 Web3 试试。虽然我始终认为区块链根上存在一些问题,但是学点东西总不是坏事。

    技术总结

    对我个人来说,去年有三大块技术升级,我觉得每块都值得大书特书,所以这里就不赘述了,简单列举一下,回头慢慢总结。

    1. Vue3(SPA)-> Nuxt3(SSR)
    2. ChatGPT
    3. 云服务 Serverless

    生活方面

    坚持健身一年,取得了不错的进展。年中测试三大项极限,从 2022 年底的卧推 97.5,深蹲 110,硬拉拉不起来(肚子太大……),成长到 2023 年中卧推 110,深蹲 135,硬拉 150。春节前再测一次,希望能再有 30kg 的成长。

    医院体检两次,指标都很好,多年未见的一切正常。血压日常也降到 70/110 附近。不过多半还会有脂肪肝。

    美中不足,年底有些放纵,体重未能达标,目前空腹 104 左右。

    知识分享

    去年在 B 站上做了 20 个分享视频,包含两大系列和若干单项分享:

    1. 模拟面试系列
    2. Nuxt3+Vercel+Serverless 数据库全栈开发

    去年没有爆款,成长比较稳定,目前粉丝数量 2845,播放数 12.6w。去年开始把视频搬运到 YouTube,目前订阅用户数 93。

    博客方面,产出一般吧,毕竟时间精力有限。年初写 AI 与 ChatGPT 的时候访问量不错,写的比较多;下半年开始做视频、尝试推进产品开发的时候,就不可避免的减少博客创作。最终数字大约 60 篇,勉强一周一篇。将来估计也会跟视频结合起来,毕竟长期来看,视频很重要,而且越来越重要。

    希望来年能把挖下的坑都填上,哈哈。

    购物

    我去年几乎没有买大件,只在年初的时候购入一台 Steam Deck 512 版本。这个产品我非常满意,我去年的游戏游玩几乎都全在这台 Steam Deck 上面。拿起就玩放下就停的感觉太好了。推荐给每个喜欢玩游戏的同学,尤其是如今已经不怎么玩游戏的同学。

    还愿 2023

    1. 继续锻炼,目标 100kg 失败,目前 104kg
    2. 把 side project 做起来 失败
    3. 争取做两个能挣钱的 side project 失败
    4. 努力做视频,保证周更,目标 4k 粉 凑合吧,我觉得还算满意
    5. 努力写博客,争取能提款 Adsense 终于攒到 $100
    6. 每天回答知乎/思否一个问题
    7. 除了思否、Hackathon,再至少参加 1、2 次开发者活动吧

    许愿 2024

    总结完毕,再次许愿:

    1. 我们的产品取得突破性进展
    2. 继续锻炼,这次真的要减到 100kg
    3. 学会 SEO
    4. 学会 Web3 开发
    5. 把 side project 做起来
      • 拜拜
      • (待定)
    6. 两个系列视频 + 若干单项视频,全年不低于 20 个,B 站 4k 粉,油管 500 订阅
    7. 写博客,周更,填坑
    8. 每天回答知乎/思否一个问题
    9. 参加两次开发者活动

    总结

    2023,很多收获,也很多遗憾。总之,继续加油吧!

  • 【视频】2024 浏览器扩展开发必备:CRXJS Vite Plugin

    【视频】2024 浏览器扩展开发必备:CRXJS Vite Plugin

    早在开坑 使用 CRXJS Vite 插件开发 ChatGPT SidePanel 插件(一) 系列时我就想做这个视频了。时隔数日,这个插件修复了不少问题,热重载更好用了,也适配了 Vite5,更加值得推荐。

    CRXJS Vite plugin 可以大大改进我们开发浏览器扩展时的体验。本视频就简单介绍一下这个插件,带大家初步体会它的能力。另外浏览器扩展近期还开放了 Side Panel 的能力,非常适合做与页面相关的功能。本视频里也一并推荐。

    希望大家喜欢,请留下您的意见想法与一键三连,我们过两天再见。

    视频大纲:

    1. 介绍浏览器扩展
    2. 使用 Vite + CRXJS Vite Plugin 搭建项目框架
    3. 使用 Side Panel 创建扩展
    4. 总结

    有任何问题、意见、建议,欢迎留言弹幕私信与我交流。目前我正在筹划新的系列教程,关于方向的选择,想听听大家的意见,请大家移步 B 站帮我投票选择:https://t.bilibili.com/877511288038621239,谢谢。

    无论选择哪个方向,我都会遵循 #build-in-public 的方式,欢迎关注我的博客。

  • 【视频】如何快速修 bug+如何开发低代码树状图

    【视频】如何快速修 bug+如何开发低代码树状图

    感谢 Latteat 老板上舰。

    有位同学在开发中遇到了一些问题,花费了很多时间也没能解决。其实修 bug 是有一些技巧的,通过改善内部变量的可观测性,可以很好的提升修 bug 的效率。同时,多多利用浏览器的渲染机制,减少自己手动计算的代码量,也能降低出 bug 的几率。

    希望大家都能从视频中学到东西。如果你有什么问题,欢迎在评论区提出。

    视频大纲:

    1. 开场白,介绍问题
    2. 加强可观测性,找到 bug
    3. 开发树状图:DOM 结构
    4. 开发树状图:添加样式
    5. 开发树状图:调整节点位置
    6. 总结:四个要点

    有任何问题、意见、建议,欢迎留言弹幕私信与我交流。另外,我现在还在组织模拟面试,想参加的同学,也请跟我联系。目前下周还是空缺,欢迎投稿。

  • Vue3 中使用 h + render 渲染组件,实现自定义弹窗功能

    Vue3 中使用 h + render 渲染组件,实现自定义弹窗功能

    Vue3 里面使用模版添加组件非常常见,不过有时候,我们不方便直接把组件写入模版。比如,最常见的情况,在用户进行一些危险的、不可逆的操作前,我们常常需要要求用户确认。要求不高的时候,可以原生 JS confirm;但是如果要求高一些,我们就希望自定义弹窗样式。

    这个时候,如果必须事先把弹窗组件放入组件模版,就会很麻烦;如果我们能像使用原生 confirm 一样,使用 if (!myConfirm(message)) return,就会好很多。

    但是我没能在 Vue 官方文档找到合适的方案,还是 GPT-4 给我提供了思路,实验之后发现效果比较理想,所以写个笔记记录一下。

    import { h, render } from 'vue';
    import { ModalsConfirm } from '#components';

    export function showConfirmModal(message: string, title = 'Confirm') {
      // 因为要等待用户操作,所以必须返回 Promise
    return new Promise((resolve) => {
        // 创建一个新节点,用来容纳 modal
    const node = document.createElement('div');
        // 使用 `h` 创建虚拟节点,其中ModalsConfirm 是做好的 Vue SFC
    const vnode = h(ModalsConfirm, {
    message,
    title,
          // `on` + `事件名称` 即事件处理函数
    onConfirm() {
    node.remove();
    resolve(true);
    },
    onCancel() {
    node.remove();
    resolve(false);
    },
    });
    document.body.appendChild(node);
        // 使用 `render` 将虚拟节点添加到 DOM 树里
    render(vnode, node);
    });
    }

    使用的时候只需要调用即可:

    function onDelete(item) {
      if (!(await showConfirmModal('are u sure?')) return;

      // 真实的删除逻辑
    }

    GPT-4 之后,在辅助开发方面的提升非常大,理解更准确,幻觉更少。大家有机会的话多试试。

  • 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 开发有兴趣,有疑问,欢迎留言评论。

  • 小教程:React 创建支持切换效果的导航组件

    小教程:React 创建支持切换效果的导航组件

    需求

    需求不复杂,如上图所示:

    1. 导航栏,里面有若干个链接按钮
    2. 点击切换的时候,会有一个高亮标记随着选中的按钮移动
    3. 按钮尺寸不确定,高亮标记的尺寸随着选中的按钮变化

    选择技术方案

    我的第一想法是 CSS 纯原生,这样使用起来会非常简单。但是考察之后,发现不太可行,难点在于:

    1. 高亮标记如果跟着按钮走,无法处理按钮间切换
    2. 如果放在父容器里,可以在按钮间切换,但是尺寸没法跟着按钮走

    所以短暂思考之后,我只能退而求其次,选择和框架结合的开发方式:高亮标记使用绝对定位来控制位置;当用户切换导航(点击按钮)的时候,使用 JS 获取目标按钮的位置和尺寸,并且赋给高亮标记;使用 CSS 变量控制动画,以提升代码的复用性,确保 CSS 规则不要太死硬。

    这个过程并不复杂,我们可以利用事件冒泡机制,获取被点击的按钮,然后利用 getBoundingClientRect() 计算坐标。

    CSS

    CSS 部分比较简单,我们用一个伪元素作为高亮标记,然后配合 CSS 变量移动它即可。唯一需要注意的是,伪元素要跟按钮重合,所以必须用到 z-index,按钮也必须手动设置成 position: relative

    (WP 代码高亮插件挂了,大家将就看……)

    .slide-navigator {
    --highlight-x: 0; /* 标记的 x 坐标 */
    --highlight-width: 0; /* 标记的宽度 */
    position: relative;

    &::before {
    content: '';
    position: absolute;
    z-index: 0;
    bottom: 0;
    left: var(--highlight-x);
    width: var(--highlight-width);
    background: #ffd850;
    transition: all 0.2s;
    }

    > * {
    position relative;
    }
    }

    这里暂时没考虑响应式,如果要切换横纵,可以增加一个 Y 坐标。如果需要定制样式,可以将高亮标记的样式抽出来单独处理。

    React JSX 组件

    作为组件,导航里面的按钮要跟着业务逻辑走,最好在业务组件里生成,然后通过 props.children 传进来。点击事件可以直接交由父容器,也就是本组件侦听,然后利用 event.target 来判断选中的是哪个按钮。

    这里还有一个抉择,即怎么确定高亮按钮是哪个。我考虑了一下,觉得将这个逻辑一并放在业务组件里比较好,然后通过 props.currentIndex 传进来使用。这样能确保本组件的复用性。坏处就是每个用到这个组件的地方都要写一个函数来判断,略嫌麻烦。

    最终组件代码如下:

    (WP 代码高亮插件挂了,大家将就看……)

    import {
    CSSProperties,
    FC,
    MouseEventHandler,
    useEffect,
    useRef,
    useState
    } from 'react';

    interface SlideHighlightProps {
    children: React.ReactNode;
    className: string;
    currentIndex: number;
    }

    type SlideNavigatorHighlight = CSSProperties & {
    '--highlight-x'?: string;
    '--highlight-width'?: string;
    };

    const SlideHighlight: FC<SlideHighlightProps> = function (props) {
    const { className, children, currentIndex } = props;
    const root = useRef<HTMLDivElement>(null);
    const [navStyle, setNavStyle] = useState<SlideNavigatorHighlight>();

    /* 处理按钮点击事件,找到被点击的按钮,并将其位置和宽度赋给高亮标记 */
    const onClick: MouseEventHandler<HTMLDivElement> = (event) => {
    if (!root.current) return;

    const target = Array.from(root.current.children).find((v) =>
    v.contains(event.target as Node)
    ) as HTMLElement;
    const { left } = root.current.getBoundingClientRect();
    const { left: l, width } = target.getBoundingClientRect();
    setNavStyle({
    '--highlight-x': `${l - left}px`,
    '--highlight-width': `${width}px`
    });
    };

    /* 组件初始化时,或者导航切换时,改变高亮标记的位置 */
    useEffect(() => {
    if (!root.current) return;

    /* 如果导航不应该高亮,就把标记隐藏起来 */
    if (currentIndex === -1) {
    setNavStyle({
    '--highlight-width': `0px`
    });
    return;
    }

    const { left } = root.current.getBoundingClientRect();
    const target = root.current.children[currentIndex] as HTMLElement;
    const { left: l, width } = target.getBoundingClientRect();
    setNavStyle({
    '--highlight-x': `${l - left}px`,
    '--highlight-width': `${width}px`
    });
    }, [currentIndex]);

    return (
    <div ref={root} className={'slide-navigator ' + className} style={navStyle} onClick={onClick}>
    {children}
    </div>
    );
    };

    export default SlideHighlight;

    总结

    我的 React 经验不太多,不考虑项目结构倒也问题不大,但是抽象组件的经验比较少,这次也是在 ChatGPT 帮助下才写好,所以赶紧趁热发一篇笔记。

    如果你对 React 经验比较丰富,发现我上面的代码有问题,欢迎指出。如果你对 React 开发有问题,对组件开发有疑问,也欢迎留言讨论。

  • 值得关注的 CSS 新特性

    值得关注的 CSS 新特性

    CSS 作为前端的重要组成部分,虽然受瞩目的程度逊于 JS,但是也在不停地进步。CSS 可以让我们的开发环境更好,用户体验更佳,所以大家也需要保持关注。这里记录一些值得关注的新特性(评判标准由我主观决定),有些已经实装,可以取代旧样式,提供更好的效果;有些还没有普及,但是可以逐步应用到我们的产品当中,渐进式增强。

    overflow-wrap

    这个属性我还是通过 TailwindCSS 学到的。它可以用来处理文本换行,拥有 3 个可选值:

    1. overflow-wrap: normal 默认值,按照标准模式换行
    2. overflow-wrap: anywhere 可以在任意处换行
    3. overflow-wrap: break-word 尽量利用宽度,只在超宽时打破单词换行

    我们知道,中文一个字就是一个字,随时可以换行;而英文则把一个单词算作一个字,单词内部不能随意断开。但是有时候,容器宽度不够,文字就会撑破容器。以前我们只有 word-break: break-all,但是 word-break: break-all 会在单词的任意位置断开,甚至在不必要的时候断开;而 overflow-wrap: break-word 会尝试把单词放到独立一行,如果还是不行,那就再断开。所以它的效果会更好。推荐大家使用。

    Scoped CSS

    CSS 的优先级非常重要,平时常常用到,面试的时候也会常常问到。CSS 优先级分成若干层级:

    1. #id
    2. .class
    3. element
    4. [attr="value"]
    5. :pseudo-class

    这样的优先级规划在大部分场景下,都能正常工作,但是当我们需要使用公共仓库,或者开发公共仓库的时候,就会遇到困难。

    比如 Awesome Comment 项目,它是一个嵌入页面的评论框,用户可以把它嵌入到自己的网站当中,给自己的网站添加评论功能。我们使用 TailwindCSS + DaisyUI 作为样式库,直接使用一切都好,但是当我们要把它嵌入别的网页时,就可能跟目标页已有的样式产生各种冲突。于是,在 Scoped CSS 尚未普及的今天(主要是 Safari 不支持),我们就必须给所有样式加上 ac- 前缀,非常影响开发效率。还有一位同学,他想给现在使用 bootstrap 的项目里引入 TailwindCSS,也面临严重问题。

    如果有了 Scoped CSS,我们只需要这样:

    @scope (.awesome-comment) {
      .a { }
    }

    就可以非常轻松地让我们的样式只在 .awesome-coment 节点内生效,且盖过其它样式。可以大大提升我们集成别人的样式,或别人使用我们样式时的效率。相信以后这个功能会大大改善第三方框架的开发。

    更多更详细的用法,建议大家深入阅读:@scope – CSS: Cascading Style Sheets | MDN (mozilla.org)

    <textarea> 根据内容自动缩放

    这项目功能目前还在讨论之中:[css-ui] ? Allow <textarea> to be sized by contents. · Issue #7542 · w3c/csswg-drafts (github.com)。大概意思是,<textarea> 用途非常广泛,让它能够根据内容长度自动调整大小会非常有价值。不过,具体选用什么方案,目前还没有定论。

    我搜了一下,目前无论 form-sizing 还是 field-sizing 都还没有进入规范,也没有间接实现方案(比如 PostCSS),所以大家先保持关注吧。

    View Transition 视图切换动画

    View Transition 让我们的产品可以在页面切换时,复用一些页面元素,展现出漂亮的动画,让用户更加理解页面逻辑。如下面这则视频所示,当用户点击列表中的链接时,大标题、作者等信息会以动画形式,移动到页头位置;当用户回到列表页的时候,它们又会移回列表里的相应位置。

    使用 View Transition 的方法比较复杂,我暂时还没有 Demo,所以就不详细说明了,大家感兴趣的话,可以看下面两个链接:

    1. Getting started with View Transitions on multi-page apps | daverupert.com
    2. https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

    light-dark() 函数

    light-dark() 函数可以接受两个参数,分别作为 light 模式和 dark 模式下生效的属性。如以下代码所示:

    body {
      background-color: light-dark(white, black);
    }

    不过我觉得这个函数的作用不会很大,我还是喜欢集中定义和管理这些变量。

    color-mix() 函数

    相对来说,我觉得 color-mix() 函数的作用就大多了,它可以基于一个颜色,生成新的值。比如,我们先约定一批颜色,分别作为 primarysecondarysuccesserror 等,然后使用 color-mix 函数,生成一些衍生颜色,以便适用在 disabledactivehighlight 等场景下。

    使用方式也比较简单,它接受 3 个参数:混合模式,颜色1,颜色2。比如下面的代码,就是两种颜色各取一半,以 srgb 模式混合,得到新颜色。其中百分比也可以调整,比较好理解。

    /* 减淡,即混合白色 */
    color: color-mix(in srgb, var(--primary) 50%, white);
    /* 加深,即混合黑色 */
    background-color: color-mix(in srgb, var(--primary) 50%, black);

    有兴趣的同学可以深入了解:color-mix() – CSS: Cascading Style Sheets | MDN (mozilla.org)

    原生嵌套 CSS

    嵌套 CSS 相信大家在使用预处理工具的时候都会用到:

    .a {
      .b {
        .c { }
      }
    }

    因为确实有用,所以如今原生 CSS 也开始支持嵌套,可惜有些浏览器还没有支持。我建议大家先配合 TailwindCSS 使用,等到将来都支持了,再去掉 TailwindCSS 即可。

    export default {
      plugins: {
        'tailwindcss/nesting': {},
        tailwindcss: {}, // 不需要 TW 的话可以不要这行
        autoprefixer: {},
      },
    }

    详细规范请继续阅读:https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting

    CSS 容器查询

    我猜大部分同学都用过 @media query,以适配不同设备和分辨率。但我们现在已经是组件化开发的时代,很多时候,我们需要让自己的组件能够适配不同的使用场景,于是仅仅根据分辨率调整布局就不够了。万一被用在桌面显示器上,但是本身只占据一小块空间,那就无法保证用户体验。

    这个时候我们可以使用 CSS 容器查询(Container query),它的用法很简单,首先,假设我们的组件渲染出来的 HTML 是这样的:

    <div class="post">
      <div class="card">
        <h2>Card title</h2>
        <p>Card content</p>
      </div>
    </div>

    接着,我们要把目标容器标记为“容器控制上下文”(containment context)。未特意标记的容器不会产生容器查询,我猜这样可以减轻浏览器布局的计算负担。最后,使用 @container 查询,让容器在某个尺寸时,内部某些样式生效,即可:

    .post {
      container-type: size;
    }
    
    /* 默认样式 */
    .card h2 {
      font-size: 1em;
    }
    
    /* 仅在容器 >= 640px 时才生效的样式
    @container (min-width: 640px) {
      .card h2 {
        font-size: 2rem;
      }
    }

    容器查询还有一些进阶用法,大家可以深入阅读:CSS container queries – CSS: Cascading Style Sheets | MDN (mozilla.org)

    总结

    好的,先写这么多。感谢所有标准贡献者、开源软件开发者、社区参与者,我们 Web 开发者能收获现在的成就,跟所有贡献者的辛勤工作分不开。

    希望这些新特性能尽早进入实装,成为我们开发时的利器。

    如果你有什么想补充的,或者有问题想讨论一下,欢迎留下评论。