奋斗

blue Work Harder neon signage

前两天看到 V2ex 的一个帖子,提到了关于奋斗,正好也聊聊我自己关于奋斗的看法。

我其实一直都明白,我不是很聪明,我只有中人之姿(普通人的资质),因此,我不奢望能够与同龄人当中的优秀者比,因为我知道,我真的比不过,别人用一小时可以做完我需要用五个小时才能做完的事情。

但,天资不够不意味着不需要努力,对于我们每一个人来说,我们的奋斗,是为了让自己从当前的起点,进入到一个新的高度,而不是和天才做比较。哪怕我们奋斗到的新的高度,也不过是别的人起点,但至少相比于曾经的自己,我们达到了一个更高的高度,获得了更好的生活。

d2b5ca33bd970f64a6301fa75ae2eb22 37
如图所示

灵感: Agora RTC/RTM 调试工具

灵光一闪

我最近在写一些 Agora 的 RTC/ RTM 应用的教程,在开发 Demo 过程中,发现调试 Agora 的应用比较复杂,其实可以有一些辅助工具来帮助开发者更好的开发这些应用。

核心诉求

可以更加简单的调试 Agora SDK 当中的 Event

形态

  1. 独立的 Vue Componet / React Component
  2. 基于浏览器的 DevTools

基本功能

  • 支持触发事件(应该是一列 按钮,用户点击后,就会触发对应的事件)
  • 支持发送信息(应该是一个文本框 + 一个提交按钮)
    • 支持设定 Template
  • 支持查看频道内已有信息(最好还可以有筛选,这样方便只看自己关注的信息)
  • 支持查看 Client、Track等基本信息(如当前人数、当前用户列表等等)

为什么一定是独立的 Client?

在实际开发时,业务的逻辑和实际使用到的功能可能是需要多个步骤才能触达的。此外,根据应用的特性,还可能会有浏览器锁定、设备锁定等业务能力。这些能力虽然与 Agora 无关,但会影响调试时的难度,因此,有一个单独的 Client 可以用来调试 Agora 是一个不错的选择。

为什么最好是 Component / Devtools ?

在实际的开发过程中,我们可能会用到 Agora 的 Token 机制,借助这个机制,我们可以对我们的音频/视频 Channel 设定准入门槛,降低成本。但相应的,调试起来比较麻烦。

如果是 Component / Devtools ,可以通过传入 Props / 环境变量来完成 AppID 和 Token 的设定,降低调试的成本。

一种低成本的旅行方式

yellow Volkswagen van on road

今天这篇文章是我大学时喜欢的旅行方式,今天也分享出来,希望能够帮助到刚刚毕业的大学生。

你们可能面临了一个困难的开局,但困难的开局也意味着你们可以有更多的时间来锻炼自己。在这个既然努力也不一定能找到好工作的时候,不妨走出去看一看世界,思考一下人生。

我的旅行方式总结下来就是一句话:用时间换金钱

大学生的问题是比较穷,没有太多的钱,所以一个比较好的方式是牺牲自己出行的时间,换取更低成本的旅行。

总结下来大概是两点:

1. 出行:选择 T/Z 等火车出行

火车出行是我低成本旅行的一个重要选择部分。如果你可以接受多花一些时间,T、Z 开头的列车可以以一个比较低成本把你送到千里之外。

d2b5ca33bd970f64a6301fa75ae2eb22 31
深圳东至武昌 Z/T列车

以深圳至武昌为例,你只需要支付 268 ,是同样的高铁票价格的一半。

d2b5ca33bd970f64a6301fa75ae2eb22 32
深圳北至武汉 G 列车

你失去的是时间,获得的是更低成本的出行。

2. 住:选择青旅进行住宿,低成本 & 可以认识新朋友

作为一个「社牛」,我是很喜欢住青旅的,因为你可以非常低成本的在这里睡一晚。此外,还可以和住青旅的其他人一起聊天、唠嗑,是一个不错的选择。

d2b5ca33bd970f64a6301fa75ae2eb22 33
西湖边,特价只需 49 一晚

缺陷就是你需要和别人一起住,但毕竟在学校也是住宿舍,出来住青旅也没有太多难以接受的点。

总结

人生当中我们会遭遇很多困难的时候。如果当下无法改变,不如试着去过一过不同的生活,在低成本的时候过这样的生活,比我们高成本时可以更好的过这样的生活。

构建自己的应急处理机制

a9612210e0aa48ef7576a8a4b19db262

生活当中,疫情当下,我们很容易会在生活中当中遭遇一些意外事故,这个时候,我们为自己构建一套应急处理系统,可以帮助我们快速走出困境。

看个例子

以「小区被封控,但家里没电了,手机也马上就要没电怎么办?」为例,

我们可以从这句话拆解出三个问题:

  1. 小区被封控,导致我不能如常处理问题。
  2. 家里没电,导致我没办法正常生活,也没办法给手机充电。
  3. 手机没电,这意味着我可能与外界断联,无法处理任何事情。

当我们拆解出问题后,我们要做的就是给这些事情排列出优先级,并按顺序解决:

上面这些问题当中,手机没电是最严重的,因为手机没电会导致我和外界失联,失去解决其他问题的可能性。

其次,比较严重的是家里没电。这个是影响了我后续生活质量的问题。

至于小区被封控,这是我不可控的因素,因此,不考虑解决它,而是试图在这个大背景下寻求解决方案。

而从优先级来看,我们则需要按顺序处理这些事情:

  1. 手机没电:可以去邻居家/物业充个电,先解决自己会失联的问题。
  2. 家里没电:在物业咨询,在当下,是否有方案可以为家里交上电费。

拆解方法论

从例子中拆解方法论的话,我们应该处理问题的策略是:

  1. 找到我们的问题都有哪些?很多时候,导致我们陷入困境的问题不是一个,而是多个问题交织在一起(一个问题往往直接解决就好了)
  2. 为问题排出重要、紧急的优先级:我们需要为问题设定优先级,根据一个事情发生的最坏后果、是否有可替代方案等方式排序,找到最重要最紧急的问题。
  3. 按照优先级解决问题:当我们排出问题的优先级,下一步要做的就是解决问题。

总结

在生活中,我们常常会 get into troubles,这个时候,静下心来,梳理问题,寻求解决方案,而不是停留在原地,任由恐慌的情绪如潮水般涌上心头才是一个更好的方案。

在 WordPress 中实现同一个 CSS 类在不同页面展示不同的效果

programming language

在使用 WordPress 开发时,某些时候你会遇到在同一个类在不同页面的时候,希望有不同表现的诉求。在这种情况下,一个比较简单 or 比较 Tricky 的技巧是通过 Body 层的类选择器来实现。

在 WordPress 进行渲染时,会自动在页面的 <body> 上加入 postid-[id] 的类,对比本文就是 postid-5888

d2b5ca33bd970f64a6301fa75ae2eb22 29
比如本文的 class

类似的,如果是一个 Page,也会生成 page-id-[id] 的类。

d2b5ca33bd970f64a6301fa75ae2eb22 30
页面生成的类是 page-id-[id],和 post 规则不完全相同

因此,如果你需要某个页面的元素样式和其他页面元素样式不同,但页面结构保持一致,一个比较好的方式是通过这些页面类选择器来实现。以你要自定义 btn 样式为例。则可以这样实现:

// 标准页面的样式
.btn{
   // your styles
}

// 在文章 ID 为 5888 的文章上的样式
.postid-5888 .btn{
  // your styles
}
// 在页面 ID 为 84 的文章上的样式
.page-id-84 .btn{
  // your styles
}

Code language: JavaScript (javascript)

总结

借助上面的方式可以实现同一个类在不同的页面展示不同的样式。但我个人觉得,这样的实现方式可能对于 Debug 不太方便,尽可能少用。但如果你需要用的话,那一定要注意好组织代码,避免后续维护成本提升。

此外,除了通过修改 style.css 来实现,你可以考虑将 Page 单独的样式梳理在一个 page.css 中进行维护,从而降低维护难度和成本。
WordPress 当中也有一些插件来实现某个页面自定义的 CSS 能力,将这些样式放在插件中来实现也是一个不错的选择:Post/Page specific custom CSS

Reference

做共赢的事情

灵光一闪

这篇文章算是为什么选择品牌广告而不是联盟广告的一个延伸。

和一般博客主来说,我的选择显得与众不同,我没有选择一个广告联盟来达成自己的收益的目标。

主要有以下几个原因:

1. 品牌广告更可控,调性更稳定

作为一个技术 & 生活博主,我的读者要么是纯粹来看我的生活的,要么是看我的技术文章的,要么是先看技术文章再来看我的生活碎碎念的。虽然大家来看的理由不一定相同,但至少大家是有基本的辨别能力和素养的。

对于这样的读者而言,你推送诸如 XX蓝月的广告,可能意义不大,甚至会影响阅读体验。因此,从这个角度而言,我并不希望我的读者来到我的博客会看到一些不符合我博客调性内容的广告。

2. 品牌广告可筛选,有效筛选出无效产品

对我来说,虽然有钱赚挺好的,但不代表着我一定会接受某家的广告。品牌广告是一个双选的过程,我可以和我喜欢的品牌商去探讨赞助的费用和收益。当我们达成一致以后再进行投放。

这样,我就可以为我的读者筛选出我自己真正在使用的产品。对于我的读者更好。

3. 品牌广告相比于联盟广告,可以更容易达成共赢

品牌广告的内容是经过我洽谈、使用过的产品,因此在产品质量上有保障。这对于读者来说,他们接收到的信息不是粗制滥造的产品,对他们来说不算一件坏事;对于我来说,我的文章阅读体验没有太多的下降,因此也算一个可以接受的结果。而对于品牌方来说,他的广告投放是有明确的筛选的,定向投放,也会更好。

从上面的这些角度来看,品牌广告的优势还是大于联盟广告的。不过,品牌广告也有自己的问题

  1. 你需要自己维护客户:品牌广告和联盟广告不同,没有人帮你洽谈广告,很有可能今年他投了广告,明年他就不投了。这时你就需要自己去洽谈新的客户。
  2. 收入不稳定,存在空窗期:品牌广告存在洽谈和商讨的过程,就很容易出现广告的空窗期。空窗期意味这段时间没有收入。对于一些站点来说,是不可接受的。
  3. 对小站点不友好:我的博客写了 8 年,有据可查的就有 6 年,对于很多刚刚开始的小站点而言,他们是很难接受到品牌广告的。所以这些也是一些限制。

总结

品牌广告是一个共赢的事情,而联盟广告在未经筛选的情况下,是一个很难做到共赢的事情。对于博客主来说,很难说某一个选择就是最好的。放在 6 年后,我选择品牌广告,但放在写博客的第二年,我可能就会选择联盟广告。

根据自己的需求选择你的广告方案,并坚持的执行下去。

找我帮你做事,如何收费?

1 US dollar banknote close-up photography

本文主要是用于有上门的客户找到我时,方便说明我的计费模式和思路。

TL;DR

我的计费模式为按时间计费,我会使用 Clockify 为你的项目进行时间记录,并由 Clockify 来完成费用的计算。我目前的每小时的单价是 $300/h(按时价折合人民币)

完成一个项目或项目累计时间超过 10 个小时后,我会想你发送计费清单(计费清单参考下方文件),你需要按照计费清单支付我的费用(如果计费清单中的某一项你任务有问题,我们可以重新讨论 & 评估)

为什么不做计件计费?

计件计费的好处是项目从开始的时候,预算就是明确的,你的支出是明确的。但在我看来,计件计费的问题是将你我放在了一个对立面。

可以简单思考,如果某个项目按计时制是 10 小时,那么按计件计费的情况下会出现三种情况:

  1. 我预估时报了超出 10 小时的计件报价:这个时候你会觉得我是不是在咋唬你,这个事情为什么会这么贵。而对于我来说,时间超出,剩下的都是赚的。你不满意,我满意。
  2. 我预估时报了刚好 10 小时的计价报价:这个时候是最完美的情况,你不会觉得我白嫖,对我来说,没有多花时间, 也可以接受。你满意,我满意。
  3. 我预估时报了不足 10 小时的计价报价:这个时候对于你来说是划算的,但对于我来说,我会觉得自己亏了。这个时候可能会在一些可有可无的细节上选择干脆不搞。或随便搞搞就行。你觉得你赚了?但你真的赚了么?你满意,我不满意。

可以看到,按计价计费的时候,有三分之一的概率让你我双方都满意。三分之二的概率让你我中有一方不满意。

此外,这里还有两个隐含的前提 :

  1. 项目的需求不做改动。但不做改动并不是一件好事,如果我们在项目开发过程中发现前期的需求设计有问题,需要加功能,做就要另外加钱,不做,产品无法满足要求。
  2. 项目的时间预估十分准确:项目的时间预估是需要配合着一个非常完善的 PRD 来进行的。但你应该不是那种十分完善的 PRD (完善是指考虑到所有可能的情况,一个 PRD 可能十几二十页 PDF 的那种)。在 PRD 不完善的情况下,预估时间是无法做到准确的。只能按照超出项目时间来评估(延展阅读:侯世达定律)。

计时计费对你我的好处

前面说了计件计费的劣势,那我们来聊聊计时计费对你我的好处:

  1. 计费透明:我在做每一项工作的时候,都会记录在 Clockify 当中,让你明确每一个步骤我在哪一天花费了多少时间。你可以根据我所花费的时间来支付费用,而不是支付一个固定的价格。对于你、我来说,都不是一件坏事。
  2. 为项目好的方向进发:因为我们不再是对立面,那么我们可以向着项目最优解的方向进发。如果某个细节可以让你的产品得到更好的体验,我就会选择帮你加上这个细节。让你的项目体验做到最好。

当然,计时计费也有坏处:

  1. 预算不明确:计时计费只是给出一个大致的预算,实际计费是按照所消耗的时间来计费的,因此,对于强预算需求的项目来说,这可能是个问题。

总结

如果你希望计件计费,那可以不用受累联系我,对于计件计费的项目,我一概是拒绝的。而如果是计时计费,且可以接受我的报价,那么欢迎你通过微信、邮箱等方式联系我。

什么是我眼中好的开发者产品的文档?(一)

b29692084bbb

我自己作为开发者使用过很多的开发产品,也看过不少的文档。最近频繁受邀针对不同的产品的文档提出建议,单独写这样一篇文章来说明一下我觉得什么是好的文档。一方面,可以帮助更多的开发者产品变得更好,另一方面,也可以用于自省,我自己在设计产品时是否会有类似的问题。

不过也需要注意,这篇文档仅涉及 「Guide」和「API Documentation」的部分,对于更多的 Changelog、Example、Tools 、 SDK 则没有涉及,这部分留待后续再写。

本文当中参考了包括:Notion 开发者文档微信小程序开发者文档声网 Agora 开发者文档WordPress 开发者文档飞书开发者文档等开发者产品。

API Documentation vs Guide

其实不少的产品文档写的都是 API Reference ,而不是 Guide,二者在实际的使用意义上是有所不同的。

  • Guide 帮助开发者快速上手一件事,从 0 开始,完成一件事。这是「用户视角」
  • API Reference 则是告诉开发者你能使用我的产品做什么事情。这是「平台视角」

一个好的文档应该是二者兼备的,这样才能一方面降低开发者的进入门槛(Guide 负责),另一方面, 可以让开发者可以知晓能力的范畴,帮助开发者尽可能拓展的应用边界,创造出美好的体验和新的世界。

一个好的 Guide 应该是什么样的?

这里我们以 Notion 的文档为例:

1. 一个好的 Guide 应该尽可能的显眼 & 好找

作为一个新的开发者,进入一个新的开发者平台时时迷茫的:“我应该做什么?”、“我应该看什么?“

这时一个明确的「Guide」、「Get started」可以帮助我们快速找到一个开始的锚点,这个锚点会成为开发者在这个平台中开始进行的下一步。

d2b5ca33bd970f64a6301fa75ae2eb22 11
Notion 开发者文档首页中 Guides 的入口
d2b5ca33bd970f64a6301fa75ae2eb22 12
微信小程序开发者文档中的指南的入口

2. 一个好的 Guide 应该有明确的步骤描述 & TOC

开发者在进入一个新的平台时,需要的是「快速跑完流程,以熟悉平台的各项基本功能」,而不是需要了解到所有的能力(如果开发者已经非常熟悉你的产品,其实根本不会看 Guide,直接去对应的 API Documentation 查看实现了)。

一个明确的步骤描述和 TOC 可以帮助开发者降低心理压力,并让用户找到自己所在的位置,进行下一步的推进。步骤的名称也非常的重要,一个清晰明确的步骤,可以帮助开发者快速明确自己要做什么事情,不会产生疑惑。

d2b5ca33bd970f64a6301fa75ae2eb22 13
Notion 文档当中对于步骤描述
d2b5ca33bd970f64a6301fa75ae2eb22 16
声网文档中关于步骤的描述

此外,也需要注意,步骤不建议太多,可以移除掉那些非核心的步骤,重要的是帮助用户跑通开发流程

3. 一个好的 Guide 应该是场景相关的

开发者在使用产品进行产品开发时,会有明确的预期,我要做什么事情。但产品需求和平台的能力是不同的。我们很难将产品需求和平台能力直接挂钩,这时就需要开发者盲人摸象般在整个平台上搜索和查看,找到适合自己的文档。这个时候,如果有一个场景相关的 Guide,可以帮助开发者快速找到适合自己的场景,并进行文档的细分。

在这些文档中,你的目的是帮助开发者快速了解在你平台上某个方向的能力、核心概念和如何组合你所提供的能力,帮助开发者快速实现自己的业务诉求。

d2b5ca33bd970f64a6301fa75ae2eb22 15
Notion 文档中的场景化文档
d2b5ca33bd970f64a6301fa75ae2eb22 18
微信开发者平台的场景化介绍

一个好的 API Documentation 应该是什么样的?

如果说 Guide 是开发者进入一个平台的时候最基础的教程文档。API Documentation 则是一个开发平台中最为核心的部分了,开发者每天都需要与 API Documentation 打交道,以完成一项工作,如果 API Documentation 做的不好,那对于开发者来说,简直就是一个灾难。

1. 一个好的 API Documentation 应该是组织合理的

API Documentation 当中往往包含了大量的信息,那么合理的拆分不同的 API 的模块,可以帮助开发者无需遍历所有的 API ,而是直接按照模块逐级查找自己所需的 API 即可,可以有效的提升查找的效率。

d2b5ca33bd970f64a6301fa75ae2eb22 19
Notion 文档当中按照业务模块拆分的 API Documentation

2. 一个好的 API Documentation 应该具备所涉及到的各项数据结构的说明

对于复杂的 API 接口来说,参数/返回值往往不仅仅是一个简单的 Integer 、String ,还会涉及到一些更加复杂的结构化数据的定义。

一种选择是将这种复杂的结构化数据抽象出来,成为一个新的类型;另一种选择是每次都解释一遍。显然,根据软件工程的 “DRY” 原则,我们应当将其抽象出来。在将对应的数据结构抽象出来后,需要注意的是,将其放在一个明确的位置进行展示和说明。原则上,这些结构的说明应该先于具体的接口说明。

d2b5ca33bd970f64a6301fa75ae2eb22 20
Notion 文档中的 Database Object 的位置说明
d2b5ca33bd970f64a6301fa75ae2eb22 21
WordPress 文档中关于返回类的定义描述
d2b5ca33bd970f64a6301fa75ae2eb22 22
WordPress 中在返回值中说明的错误类的入口

3. 一个好的 API Documentation 应该提供相应的 Sample Code

对于开发者来说,Talk is cheap, show me code。而在开发领域也是同样的。你提供的 Sample Code (甚至是在线的调用测试),都可以帮助开发者更好的理解相关的能力和开发逻辑。

所有的细节,都在 Sample Code 中一览无余。

d2b5ca33bd970f64a6301fa75ae2eb22 23
Notion API Documentation 中生成代码的部分

4. 一个好的 API Documentation 应该可以提供上下游关系

在 WordPress 文档中,有一个我非常喜欢的功能就是 Related 。Related 内部分为 Uses Used By,分别介绍了某个函数都是用了哪些函数来完成自己的功能和哪些函数使用本函数完成自己的功能。

d2b5ca33bd970f64a6301fa75ae2eb22 24
WordPress 文档中 Related 的部分说明
d2b5ca33bd970f64a6301fa75ae2eb22 28
声网文档中关于 API 上下游的描述

伴随着 Uses 还提供了这个函数的源码(不过这个对于平台类型的产品不能直接照抄),这样我可以非常清晰的参考这个函数的 Uses 和源码,以了解这个函数是如何实现自己的功能的。这样当我需要的时候,就可以非常方便的基于这个函数,改造出一个我自己使用的函数。

而 Used By ,则提供了其他的函数是如何使用这个函数的。对于一些我比较陌生的函数,可以直接参考其他函数的用法。从某种意义上来看,这是比测试用例更加全面的用法的说明,因为这是在“生产环境”下的用法。

我们在开源世界如果没有文档,会看测试用例,那么在 WordPress 当中,我会看的是 Used By。

5. 一个好的 API Documentation 可以提供用户之间的沟通渠道

我在 WordPress 开发者文档当中,还会常用到的一个功能是 —— User Contributed Notes。这个功能为开发者提供了一个基于函数的共建笔记。开发者可以自发的在其中撰写自己针对这个函数的开发经验。

d2b5ca33bd970f64a6301fa75ae2eb22 25
WordPress 文档中 User Contributed Notes

当我在不知道某个函数应该怎么使用的时候,我往往会去 User Contributed Notes 去找找看,看看别人是如何使用某一个函数的。官方的文档往往无法跳出「我有什么」的思路,而用户的共建笔记则可以共享出开发者使用某个函数的「奇技淫巧」。这些「奇技淫巧」让开发者的产品显得与众不同,也可以进一步的扩大产品的范畴。

总结

一个好的开发者产品文档是什么样的我很难定义,但至少上述的这些点,确实让我使用这些平台的产品在开发应用和业务的时候变得更加坚定。希望我的这些笔记,可以帮助到你,让你也可以涉及出一个好的开发者文档。

如何缓解焦虑

a man holds his head while sitting on a sofa

我常年处在焦虑的状态中,焦虑行将踏错,焦虑无法达成自己的预期,焦虑无法达成他人的预期。这些焦虑久而久之,会让我拖延症发错,将所有的事情都拖到一个无法挽回的境地。然后再破罐子破摔,重头再来。

次数多了,也开始寻求一些方式和方法来缓解自己的压力。

我最近的缓解压力的方式是重新装修自己的办公环境,让自己可以更加舒服的办公,一个全新的、干净的办公环境会让我感到轻松。

整理后的办公环境
如何缓解焦虑

自定义 Bootstrap 5 的风格,实现自定义风格页面开发

f30a202d97fcb737a80ade314ebdb8e0 1

和更加自定义化的 TailwindCSS 相比,Bootstrap 显然更具备主题和模板页面开发的潜力和更易于进行模板页面的开发 —— 毕竟各种样式都已经封装好了,你可以直接使用诸如 btn 来定义一个 Button。

我有在想,提供一个基于 TailwindCSS 的 StarterKit 来快速基于 TailwindCSS 开发出一套自己的样式库,但仔细想想,其实意义不大。因为可选项太多了,想要自定义化其实没那么容易。

白宦成

而在开发 Bootstrap 的时候,大家往往会遇到的一个问题的是 —— Bootstrap 开发出来的页面千篇一律,一千个人有一千个哈姆雷特,但一千个 Bootstrap 页面却长的一模一样。而这背后的原因,主要是因为大部分人使用的是标准版的 Bootstrap —— 也就是官方所定义的样式。大家都使用一样的样式,自然就使得开发出来的页面显得雷同。

不过,其实 Bootstrap 的风格自定义并不困难。

前情提要

其实大家不去做自定义风格开发的原因倒是可以理解 —— 锅都要给 Node Sass,在 Bootstrap 5 以前,Bootstrap 使用 Node Sass 来进行样式的构建,但 Node Sass 是一个基于 C++ 编写的拓展,这使得 Node Sass 的安装十分麻烦,加上特有的网络环境,使得 Node Sass 的安装十次有九次都是无法成功安装的,大家自然也不愿意使用 Node Sass 构建的项目(包括我自己,都会刻意选择使用 Less 构建的项目)。

不过,从 Bootstrap 5 开始,Bootstrap 项目开始使用 Dart Sass 进行样式风格的开发。这使得 Bootstrap 的构建不会像过去那么痛苦,因此也可以更好的进行风格的自定义。对于如今还在使用 Bootstrap 进行项目构建的同学来说,无疑是个好消息。

自定义 Bootstrap 5 的风格

关于如何在你的项目中引入 Boostrap 的 Sass 来满足自定义的需求,可以参考官方所给出的 Customize Sass 的说明,官方提供了包括 WebpackParcel 等的接入说明,如果你连基本的配置都懒得做,官方还提供了一个空白项目,方便你 Fork 后再修改。

以我自己使用的 Next.js 为例,配置起来也比较简单:

1. 安装相关依赖

在 Next.js 项目根目录安装所需依赖

yarn add bootstrap
yarn add -d sass sass-loader

2. 创建 Scss 文件

styles 目录下创建一个新的 globals.scss 文件,并在其中加入如下代码

// custom variables
@import "../node_modules/bootstrap/scss/bootstrap";
// custom css code 
Code language: JavaScript (javascript)

其中 custom variables 的部分是让你用来加入自定义的变量的,比如自定义 Primary 的颜色、自定义 Padding 等一系列操作,都需要在 Bootstrap 的引入前定义,这样才能确保你的引入变量会生效。

而后面的 custome css code 则是可以用来定义一些你自己编写的,没有被覆盖在 Bootstrap 内部的类。

3. 在项目中引入创建好的 scss 文件

在项目的 pages/_app.js 引入我们刚刚创建好的 scss 文件,从而确保应用在启动的时候可以自动构建 Bootstrap ,并引入至项目中。

import '../styles/globals.css'
import '../styles/globals.scss' // 这一行是新增的
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
Code language: JavaScript (javascript)

4. 在项目中使用 Bootstrap 类进行开发

当你完成上述的配置,就可以在自己的项目中引入相应的类来进行开发了。

一些小技巧

使用 Themestr.app 进行风格的快速设定

Themestr 是一个帮助你快速生成一套Bootstrap5自定义风格的工具网站。

d2b5ca33bd970f64a6301fa75ae2eb22 2

在 Themestr 中,提供了 UI Builder、Themer 和 Customizer 三个不同的工具,其中:

UI Builder 提供了一个简单的 4 步选择器,让你定义了基本的颜色、字体、ICON 和 按钮风格,从而形成一套独特的 Bootstrap UI。

d2b5ca33bd970f64a6301fa75ae2eb22 3

Themer 则提供了更加全面的样式的设定和预览功能,你可以在右侧的选择界面简单的配置样式,并在左侧自动刷新的页面中查看你的配置所实现出来的效果,方便你快速找到看到你的配置所能展现出的效果,简单且直观。

d2b5ca33bd970f64a6301fa75ae2eb22 4

Customizer 则提供了全面的 Bootstrap 的变量,方便你快速找到你要修改的变量,并帮助你生成对应的 Sass 配置文件,方便你在此修改变量,并在本地进行开发。

d2b5ca33bd970f64a6301fa75ae2eb22 5

对于不同的场景,Themestr 提供了不同的工具来帮助你快速开发,是一个非常不错的 Bootstrap 主题开发的辅助工具。

如何找到需要修改的变量

Bootstrap 在文档中针对每一个 Component 都提供了相应的 Sass Variables 的说明,你只需要在文档中找到你要修改的组件, 并在你自己的 global.scss 中修改对应的变量,就可以实现自定义。

d2b5ca33bd970f64a6301fa75ae2eb22 6

如何找到特定类的源码

在进行 Bootstrap 开发的时候,如果有些类你找不到样式的时候,一个很好的方式是开启开发环境,并在开发环境中可以看到对应的 scss ,并在对应的 scss 当中,点击链接跳转到对应的 scss 定义,即可看到对应的样式的源码。

d2b5ca33bd970f64a6301fa75ae2eb22 7
d2b5ca33bd970f64a6301fa75ae2eb22 9

在跳转后,有些时候你发现似乎并不是直接定义的样式,比如下图,则说明这样的类是通过 map 来批量生成的,你就需要修改对应的 map 来完成自定义。

d2b5ca33bd970f64a6301fa75ae2eb22 8

如何自定义主题颜色、字体大小、间距等批量生成的类

在 Bootstrap 中存在一些批量生成的类,比如 theme-colorfont-sizefont-weight 还有 paddingmargin 等等。

这些类是 Bootstrap 使用 @each 来生成的,而生成的依据,则是 Bootstrap 的各种 map 来完成的。因此,你想要进行定义,则需要修改对应的 Map。你可以使用 map-merge 来向 map 中添加新的选项

// Create your own map
$custom-colors: (
  "custom-color": #900
);

// Merge the maps
$theme-colors: map-merge($theme-colors, $custom-colors);
Code language: PHP (php)

而如果你想要移除一个选项,则可以使用 map-remove 方法来完成

// Required
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root";

$theme-colors: map-remove($theme-colors, "info", "light", "dark");

// Optional
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
// etc
Code language: JavaScript (javascript)

关于 ASoul 事件的一些思考

7423a3c685ae859e7d5283b7e1b33c4c

我自己其实不太了解 ASoul,也没有特别的关注,毕竟作为一个社牛,不太会通过虚拟人来获取情绪价值。但我确实之前听说过 VTuber,在我的下意识以为,VTuber 的声音应该是由 AI 来完成生成,工程师建模,PM/运营来负责脚本的产出(类似微软小冰)。

不过,现实可能是 ASoul 这样的项目其实是由中之人来完成声音的录制,更接近于传统的声优的角色。对于资本来说,中之人就是一个耗材,但对于喜爱 VTuber 的人来说,中之人是非常重要的。

当一个 VTuber 之后是一个独立的人的时候,其实能贡献的当然不只是声音的问题,还可以贡献出更多的情绪价值。而这些情绪价值,正是一个 VTuber 的独特的价值。

这些东西是完全无法通过 AI 生成的么?可能是可以生成的,但暂时可能无法生成。随着技术的进步,逐步拆解出人的各种情绪和诉求,再由 AI 来完成生成,实现真正意义上的 VTuber

情分和本分

A group of friends at a coffee shop

我们在生活中,需要注意情分和本分的区别。不要错把本分当情分,把情分当本分。

举个例子来说,

假设我当前的房子快要到期,我找了一个新的空房间。为了减少租期的重叠,我与房东达成了一致,在两周后入住。

但因为我自己的原因,不得已需要提前入住,这个时候我和房东沟通,说我是否可以提前入住?房东同意我提前入住。

在这个事情当中,房东同意我入住是情分,房东不同意我入住是本分,因为我们的合同签署的入住日期是两周后,即使不允许我入住,我必须出去住酒店,也是因为合同签署的确实是两周后,这也是合法合理的。

在生活中我们需要感谢别人的情分,感谢他人的帮助。但同时也要明白,别人遵循本分做事并没有什么不对,人家只是遵照合同办事,没有过错。

2022 年 4 月月度总结

summary

Objective 1:持续获取现金流,并构建未来收益的现金牛

KR1:投资收益达到 20000 元

长期投资:本月持续定投指数基金。

投机:本月开始进行 Crypto 领域的买入,目前在买入的是 NFT 。NFT 的市场还是比较有意思的,波动比想象中的要小一些(也可能是因为我一进来就碰到了一个还行的项目)。目前两张图片都还是有浮盈的,不过首先于最近的股币双杀,没有太大的变化可以预期。好在我买的时候都很便宜,也不指望这一笔赚多少钱,挂了个单就丢那里了。

不过在购买 NFT 的时候,也发现了很多机会(也可能是坑),目前 NFT 领域的工具链并没有我们想象中的那么全面和丰富,里面有不少的空间可以去做一些东西。后续我应该也会去

KR2 :单篇稿费突破 6000 元

这方面还没有变化。感觉还需要拆解一下,如何提升稿费。

阅读量?但看起来像是广告费。需要量化一下。感觉自己这个 KR 定义的有问题。

KR3 :达成年度预算,支出不超预算

目前支出预算略超,五月要考虑节约一点了。

KR4 :构建软件类现金牛业务,预期产生收益 10000 元人民币m

软件类业务本月没有太大的进展,主要是状态不好,盯盘的花费的精力较多,5月优先这部分 KR。

Objective 2:提升生活基础设施,构建未来生活好基础

赚钱的目的是生活,不能为了赚钱而赚钱,更应该关注自己的生活。因此,为了生活,设定下如下两个 Key Result:

KR1:前往 6 个城市旅行

四月份去泡了个温泉,体验了一把室外温泉。没有远行,主要还是收到了疫情的影响,比较难。

KR2:进行 20 次文娱活动

四月看了一场话剧,圆了自己的一个心愿。

KR3:借助智能化设备,缩减在家务相关事务上耗费的时间

暂无进展

Objective 3 :开拓视野,打造多元行业人才

KR1:写 15 篇书评

原本说想在少数派写原则系列的书评,结果也鸽了。。。唉。。。

KR2:输出关于加密货币的 Newsletter 12 封

继续 0 进展。为什么不敢写?应该还是怕自己的产出这不符合自己的预期?有偶像包袱?

KR3 :完成计划中的三本图书的写作

暂无进展。

OneInStack 中 Redis 的使用注意事项

0dbb4980acb58d4396e9a2055bf2176e

在使用 OneInStack 时,有一些事项需要注意,不然可能会导致你的 Redis 在使用过程中出现问题。

1. Redis 默认配置内存是 122 MB

OneInStack 中 Redis 的默认内存配置是比较小的,只有 122MB ,对于一些大型应用来说,是肯定不够的,因此,在实际使用过程中,还是最好将其修改为一个更大的值。

配置文件路径: /usr/local/redis/etc/redis.conf

需要修改的项目 maxmemory 122000000,将 122000000 修改为 256000000 即可将 Redis 使用的内存设置为 256 MB,从而扩大了整体可用的内存的大小,应对大型数据库也游刃有余。

2. Redis 的逐出机制为 noeviction

OneInStack 中 Redis 的默认逐出机制是 noeviction,即内存满后不逐出,写入缓存报错,读缓存不受影响。

对于将 Redis 作为数据库的场景而言,这么干是正确的。但对于做缓存的场景,则需要修改逐出机制,比如可以将逐出机制修改为 allkeys-lru,即在全部的key中淘汰最近最少使用的key。

在配置文件( /usr/local/redis/etc/redis.conf)中加入一行配置即可

maxmemory-policy allkeys-lru

被种草 & 断舍离

brown short coated dog on brown wooden table

我算是一个被「消费主义」洗脑的人,很喜欢买各种各样的东西,比如:

  • 我家中有三台电脑:一台 mac mini 2012 (主力使用);一台 mac book pro 16寸(出差使用);一台 NUC(用来做一些 Windows Only 的事情)
  • 我家中有多个水壶:一个京造的手冲壶(用来冲挂耳);一个小熊的蒸茶壶(用来蒸煮我的各种茶叶);一个大的烧水壶;

我买了很多东西,其中一部分是我自己生活需求的延展所带来的,另外的一部分,则是来自于被各种视频/朋友推荐。

我买的制冰机便是一个典型的例子:

我是在津津乐道的节目当中听到大家推荐居家好物的时候,提到了制冰机,以及制冰机带来的畅快感,就买了制冰机。

而我购买产品的逻辑倒是也十分简单:我不太会听陌生人的推荐,而更相信一些有着持续关系和持续口碑的人的推荐。在我看来,靠谱的人推荐的产品大多是靠谱的,不靠谱的人推荐的产品是不可信的。

最近,又快要到搬家了。准备继续寻找一个更加低成本生活的方式。届时,又是一次断舍离。

当我的东西变少的时候,确实,生活变得更快乐一些(就像我们想要去露营,体验的一方面是大自然的美妙,另一方面,则是对于逼仄生活的反抗。)

MySQLDump 导出部分数据

f30a202d97fcb737a80ade314ebdb8e0

MySQLDump 是非常常用的数据库导出工具。不过,大部分时候,我们使用的都是 mysqldump -uuser -p database_name > database_name.sql

这样可以提供数据导出功能,但导出的数据会是全表数据,我们在导出数据的时候,如果希望导出部分数据,就需要借助于其中的 --where Flag,来实现导出部分数据.

此外,由于导出的数据是部分数据,因此,就涉及到需要指定导出时的表名,才能实现导出相应的数据,需要满足如下格式

mysqldump [OPTIONS] database [tables]
Code language: CSS (css)

举个例子来说,假设我们需要导出数据库 wordpress 的 wp-options 表中的 ID 小于 20 的数据,则需要执行如下命令:

mysqldump --where="id < 20" wordpress wp-options
Code language: JavaScript (javascript)

上述命令就实现了从数据库中导出符合特定要求的数据。

Next.js 实现动态引入,实现对非 SSR 组件的支持

CAPTCHA

在使用 Next.js 进行开发的时候,会遇到某些组件是不提供 SSR 支持的(很正常,毕竟 SSR 是一个相对比较小众的应用场景。此外,某些组件会依赖浏览器环境的上下文),在这种情况下,我们需要一种方式来实现对非 SSR 组件的支持。

一般而言, 有两种方式,一种是使用 useEffects 来控制组件的载入,从而实现只在客户端层面加载组件,服务端渲染的时候就不再加载该组件。

另外一种方式是可以考虑借助于 Next.js 自带的 Dynamic Import 的特性,来完成组件的引入。

在使用层面比较简单,引入组件的方式从直接引入改为最外层套一层 dynamic 函数即可

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponentWithNoSSR />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home
Code language: JavaScript (javascript)

总结

这个问题的解决让我再次激起了对于 Next.js 的兴趣。过去因为不确定如何解决在浏览器和客户端环境下的问题,对于 Next.js 有些畏难;如今问题已解决,可以继续放心的使用 Next.js 了。

参考阅读

https://nextjs.org/docs/advanced-features/dynamic-import

那些影响你前端开发体验的问题(1)

code 1076536 640

最近在研究和体验一些 GitHub 上的前端项目,遇到了一些让人体验不佳的点, 这里梳理一些我遇到的,一方面是留存,记录那些让我体验不好的事情。另一方面警示自己在开发前端项目的时候应该注意一下开发体验,确保不会让别人在开发的时候也遇到这些问题。

1. 项目应该有文档

这是不少 GitHub 上面项目的普遍问题。现有的文档要么是以 Framework 生成的 Readme 为主,要么直接没有文档。

但没有文档对于后来者的开发其实非常不友好,比如几个常见问题:

  1. 如何启动一个 Server & 如何设置后台 API 的 Prefix?
  2. 项目的组织结构是什么样的?
  3. 涉及到的第三方服务应该如何配置?

上面这些文档是帮助后来者快速使用你的项目所必需的(换句话说,如果你要做开源项目,想要打造开源社区,这也是必要的)。

2. 项目控制算法复杂度

我在开一些前端项目的时候,明显会感到浏览器的响应变慢了,我以为是我的 Mac Mini 2012 性能不行。但我打开自己的博客,依然如丝般顺滑,我就明白了。问题不在于我的电脑性能跟不上,而是这个前端页面加入了大量的有用或无用的代码,使得 CPU 需要大量的计算。

虽然进入 SPA 的时代以后,我们开始将算力的使用从服务端往客户端转移,但也建议各位工程师控制自己项目的复杂度,不要一开网页,风扇就呼呼的转。浏览体验不好,还凸显自己的技术不行。

3. 使用 debuuger 调试,而不只是 console.log

在编译型语言当中,使用 Debugger 来进行调试是一项基本技能,因为需要在运行时去看不同的变量的值,以此来完成调试。

d2b5ca33bd970f64a6301fa75ae2eb22
爆炸的 Console

但在前端领域,因为 console.log 的过于好用,大家开始习惯于使用 console.log 来打印变量,但对于大型项目来说,大量的 console.log 也会让你的项目输出许多无意义的内容,让整个项目的调试和开发受到阻碍。

总结

最近看项目,总结了三条自己觉得影响体验的点,后续随着看的项目的深入,会逐渐总结出更多影响体验的点,引以为鉴。