yinzhuoei_seho 发布的文章

在这篇文章之后,我会经常发布一些关于框架设计/架构的一些文章,因为这将作为我的读书笔记,我最近在看一些书比如《vue.js设计与实现》和《前端架构入门和微前端》;我简单介绍一下这两本书,希望对你们有所帮助,首先前端架构这本书一直是我的床头书但是目前对我的工作帮助并不大,因为它比较偏理论个人认为,如果你有耐心并且非常愿意入门前端架构,这本书是一个非常不错的入门书籍;其次就是vue.js设计这本书是最近前端圈的网红书,如果你已经使用过了vue3一段时间了,想精通/深入了解vue3,那么这本书将会带你从设计到实现理清楚vue的所有脉络!

前言

我最近在写我人生中的第一款框架,尽管没有任何含金量,而且这种低级的作品居然是出自一个有着3年开发经验的程序员之手,我还蛮不好意思的;在写这款框架我犯了很多错误和技术债,由于前期没有很好的规划功能以及模块,导致走了不少弯路,而且没有设计框架的经验,我经常会把一个功能放到编译时还是运行时而苦恼,同样我会时常考虑用户的习惯,去联想其他后端框架,导致在框架API设计上有点四不像的感觉。无论如何这款框架再丑也是自己生的,相信不久之后就会和大家见面了,所以我这篇文章将结合我设计的sword.js和vue.js给大家好好聊一聊框架中如何权衡某些事情。

什么是权衡

我们在讲比如vue.js这一类框架时,其中的每一个模块并非独立的,而是互相依赖和制约,框架作者需要有着全局的把控才能更好的扣细节做优化,拆分...那么想象一下当我们要设计一款前端视图层框架的时候,我们需要首先考虑范式,它是声明式的还是命令式的呢,再比如说如果在框架中做hmr底层实现,甚至是构建工具,webpack/rollup/esbuild?可见我们要遇到的选择都太多太多了,那么这就是“权衡”的艺术,框架中的每一个地方,或者说我们在平时写业务的时候,我们都需要去考虑更多东西,这就是权衡。

声明式和命令式

我们从原生js开始说起,如果我想要给一个dom绑定一个点击事件(我全部用伪代码写):

const e = document.querySelector("#app");
e.innerText = "foo";
e.addEventListenner('click', () => {
    alert("hello foo")
})

这就是典型的命令式代码,代码的执行方式是可预期的,因为都是由开发人员自己编写的每一步操作,但是这就遇到一个很难的问题了,当程序越来越大,我们有多个dom需要绑定点击事件,就要获取n次dom并且一一绑定,这无疑是一种痛苦。那么声明式呢,它可以解决命令式的一些什么问题呢?

<div @click="() => {}" id="app">
</div>

如果你使用过vue.js,那么你肯定写过n个这样的代码,我们只给click提供了一个函数,我们并不关心vue是如何获取dom并且绑定点击事件的,我们只需要关注结果就可以了,但是不可否定的是,在vue内部的实现中一定是命令式的,而暴露给用户却是声明式的。那么关乎性能它们谁更好,答案当然是可以预想到的,命令式的代码有着不可替代的性能:

e.innerText = 'update text';

在命令式代码中只需要写这一句就可以了,但是如果是声明式代码,我们需要找出新dom和旧dom之间的差异,然后再动态修改text(调用上面这个代码),所以由此得知,尽管声明式代码的性能不如命令式,但是为了更好的维护,我们需要做的就是权衡(既然性能有差距,我们就往可维护性上靠,并且尽可能的优化diff算法,让性能无限接近命令式代码)。

虚拟dom的性能

刚刚我们讨论了声明式和命令式的区别,那么虚拟dom如果你使用了vue.js就一定不陌生,而且它是每个面试官都喜欢问的(我也不知道为什么喜欢问,感觉没啥技术含量)。那么虚拟dom就是为了能够更好的给vue进行diff而出现的,我们要比对如下2行代码:

<div @click="() => {}" id="app">hello foo</div> // old
<div @click="() => {}" id="app">hello bar</div> // new

如何用最小的性能消耗找出它们的差异呢?就是虚拟dom,我们在之前说过声明式和命令式代码天然的差距(虚拟dom更新不会比js dom api性能更好),但是事实上99%场景都很难写出绝对优化的命令式代码,但是声明式代码我们可以很轻松的写出来相对还不错的代码。我们为了了解虚拟dom,需要知道我们上述提到的js dom api是什么,要么是createElemnt或者innerHTML,所以我们就用虚拟dom对比一下这两个api的差异。

innerHTML vs 虚拟dom

innerHTML是我写jquery/jsp时的噩梦,因为在新手时期为了构建一个html字符串,我每天半夜调试屎山项目的html字符串,这个过程非常痛苦:

const html = `
<div>
    <span>innerHTML</span>
</div>
`
dom.innerHTML = html;

js新手小白都知道,dom操作的效率和js层面的计算是不能比较的,差距非常大,为了页面的展示,需要把html字符串转成dom树,然后再执行innerHTML; 反观虚拟dom创建页面需要2步:

  1. 把我们的模板代码转换成js对象
  2. 无限递归对象创建真实dom

这么一看,好像innerHTML更直接,而且html字符串转成dom树是dom层一次性且“高效”的运算,所以说虚拟dom创建页面的性能是不如innerHTML的,但是更新页面,虚拟dom的优势就显示出来了,首先innerHTML不仅会对html字符串进行运算,还会把之前的旧dom销毁,然后创建一个新的dom(恨人啊);虚拟dom只需要创建一个新的js对象再与旧的虚拟dom进行比对,哪里有变化就变更哪里!虽然说虚拟dom多了一个diff的操作,但是终究是js层面的运算是很快速的;当页面越来越大,而innerhtml必定都是全量更新,性能也会随着内容变多,和虚拟dom差距越来越大。

粗略比较三个方式的创建&更新技术

  • 性能:原生JS > 虚拟dom > innerhtml
  • 综合可维护性和性能以及心智负担权衡之下,虚拟dom是一个不错的选择。

运行时和编译时

我们作为框架的作者,希望程序是如何运行的,我们还是用vue.js举例子,刚刚我们讲了虚拟dom,但是却不知道虚拟dom这个js对象是什么样子,我们可以通过这个部分把虚拟dom重新梳理一下:

const obj = {
    tag: "div",
    children: [{
        tag: "p",
        children: "hello bar"
    }]
}

这就是一个虚拟dom对象,描述了每个node的信息以及每个子node的信息,我们如果要实现render方法,就需要对虚拟dom对象进行递归,我们简单实现一下:

const obj = {
  tag: "div",
  children: [
    {
      tag: "p",
      children: "hello bar"
    }
  ]
};

const render = (obj, root) => {
  // 创建一个父节点
  const element = document.createElement(obj.tag);
  if (typeof obj.children === "string") {
    // text节点
    element.appendChild(document.createTextNode(obj.children));
  } else if (obj.children) {
    obj.children.forEach((e) => {
      // 如果有多个子节点,就递归创建
      render(e, element);
    });
  }
  root.appendChild(element);
};

render(obj, document.body);

这样我们就完成了一个在运行时环境可以完美运行的render,用户可以使用render对页面进行创建元素,但是没有用户愿意每天写这种破数据结构的,所以就肯定要用到编译的东西帮助我们把模板语法转换成数据结构,这个时候就是编译时+运行时,所以vue大多数情况也是这样做的,通过vite/vue-cli对单组件文件进行编译。那么同理既然可以有纯运行时,那么就有纯编译时的东西,可以把我们的模板语法编译成命令式的代码,比如这样:

<div @click="() => {}" id="app">hello foo</div> // old

转换成

const e = document.querySelector("#app");
e.innerText = "foo";
e.addEventListenner('click', () => {
    alert("hello foo")
})

没有虚拟dom,没有diff,only compile!! 这也是svelte.js在做的很酷的事情。所以作为框架设计者关于运行时和编译时我们需要有自己的权衡,虽然vue.js是运行时+编译时,但是在编译时会提取内容,看看哪些内容是永远不可变哪些又是可变的,然后这部分会在运行时再次做优化。所以关于运行时和编译时,没有绝对的好也没有绝对的坏,还是看框架定位和作者自己的权衡了(佛系不引战)。

关于sword.js所做的权衡

如果还不清楚sword.js是做什么的,你可以看看以前的文章,简单的就是说一个nodejs框架,框架中自然就是拥有运行时和编译时,一个framework-core,一个cli。在sword.js中有一个蛮好玩的功能就是,ts运行时检测,这个技术的大概的原理就是,分析ts的类型生成一份schema,然后会有一个函数去比对对象和schema是否吻合,如果匹配成功,那么就算校验通过,这个技术用到参数校验特别好,比如这样:

export interface ReqParams{
    title: string;
    name: "小红" | "小蓝"
}

const obj = {
    title: "test',
    name: "小红"
}
validate(obj, schema); // 这里的schema就是interface转的json对象

那么我在实现这个功能的时候,分2步走,第一个就是生成schema,第二个就是校验;我把生成这部分放到了cli的编译层这里,程序会自动读取每一个API下的类型,然后转成一个proto.json,在这个json中,运行时可以去校验这部分的对象是否符合要求。权衡好了运行时该做什么,编译时该做什么,就可以把2个工具的大小大大压缩。

再比如说日志模块,在开发nodejs应用的时候,我们需要core的日志,也需要cli的日志,那么如何在终端表现也是需要权衡的。

结语

今年实在是很少时间写文章,就趁着看书和写框架做一个随心记录,希望你们能看得懂(内容偏水,应该都有看得懂)

现在支持私有部署同时也支持serverless各个平台的nodejs框架多么?答案是否定的,在2022年越来越多的企业会选择serverless来部署自己的应用,因为它足够轻巧省去了运维的成本,编写一个API可能只需要几行代码,越来越多的平台也推出了自己的云数据库以及云消息队列,我们前端开发编写后端api不再是一个头疼的事情。所以serverless是未来,我将在4月份之前完成一款具有插件机制的框架,它会在web服务器中运行也可以在serverless环境运行,在serverless中我会优先支持unicloud,我将使用这款框架参加2022年的dcloud插件大赛。

简单聊聊这款框架,我为什么要创建一款nodejs框架?

为什么要造轮子

在sword团队中我们使用unicloud构建应用程序,采用了CQRS,我们所有的写操作都由unicloud的云函数完成,但是你会发现在unicloud社区优秀的框架有很多,它们提供了url处理以及逻辑的分发,还有一些特色的框架也提供了诸如上传,和unicloud的部分特性封装,在我看来,这样的框架没有真正解决开发者的问题,比如:

  • 我想现在不想用unicloud,我想用传统服务器运行函数
  • 我想和云平台解耦,我希望我的云函数的特性和功能实现和某一平台无关
  • 我想使用ts开发
  • 我想有IDE强力支持
  • 我想使用一些开箱即用的方案,比如说hook,又比如HMR
  • 我想使用ES开发nodejs程序,使用先进的技术对程序进行捆绑(treeshaking...)

如果要满足上面的特性,那么只有midway.js了,midway.js很酷,但是它并没有unicloud的faas插件,而且我希望框架能够让sword团队更好的构建程序,简单就是说,我们想把控这个事情,而不是阿里;所以我们造一个轮子,来解决这些事情。

框架开发进度

我会在3月底前完成第一步的计划,即web服务器可以部署我们框架捆绑的应用,框架简单包含3部分

  • framework runtime
  • framework cli
  • framework type

我po一下我们的仓库:

作为一个没有学过C/C++的开发者(比如说我),在初步学习Rust中需要了解一些JavaScript中不存在的东西,那么所有权就是Rust中的核心功能之一。我需要一篇文章记录这一次的学习,在真正内容开始之前我需要描述一些基础知识,在最后也会简单看看”引用和借用“,”slice类型“这些与之相关的Rust概念。因为是Rust初学者,请大家阅读本篇文章带着自己的思考,因为每个人的思考方式和理解都不一样,所以可能会导致某些错误...。

参考资料:

预热

程序员编写的所有程序都必须管理其使用计算机内存的方式,比如说JavaScript这一类的语言具有垃圾回收机制(GC)它可以不断寻找不再使用的内存,因此不需要开发者手动干预;在另外一些语言中,我们需要亲自分配和释放内存;但是在Rust中就利用了所有权的概念管理内存,编译器在编译阶段会根据规则进行检查,同样的也不需要程序员手动干预内存,这些有关内存的工作都交给了所有权和编译器。所以我们学习Rust中的所有权的时候,准确说应该是学习编译器是通过什么“规则”来进行检查的,这个规则对我们更重要。

堆栈

我们在写JavaScript的时候,通常业务开发我们不需要考虑堆栈,但是在Rust中我们需要考虑一个值是在堆上还是在栈上,这和所有权息息相关,所以我们先简单回顾一下堆和栈的基础知识。堆和栈都是代码运行时可使用的的内存,它们是不相同的。大家都知道栈是先进后出,像服务生手中一个一个托盘一样,最先放入的托盘一般都会最晚从上方取出;栈中所有数据都占用固定的大小,程序在编译时都会看值大小是否可能会变化,如果是则需要存储到堆中,因为堆中所有的数据都是凌乱的,需要分配器去在堆中区域开辟一块空间,标记为已使用并且返回指针,这个过程也叫做在堆上分配内存

数据直接入栈当然也比在堆上分配内存更快,入栈直接放到栈顶即可,入堆的话分配器不仅要查看内存,分配内存还要记录以便为下一次分配做准备;访问堆上的数据也比访问栈中的数据要慢(因为指针),而因为处理器缓存的原因,跳转越少就越快,当访问的数据彼此都是栈中那自然快,但是彼此数据一个在栈中一个在堆中那自然会稍慢一点。那么所有权将会帮助我们处理堆中的重复数据,无用的数据以及跟踪哪些代码在使用堆中数据,总的来说所有权的存在就是为了管理堆数据。

所有权规则

  • 每一个值都有一个被称为其所有者的变量
  • 值在任一时刻只有一个所有者
  • 当所有者(变量)离开作用域的时候就会被丢弃

感觉目前理解这几个规则还比较晦涩,但是我们可以随着笔记深入慢慢地理解

作用域

所有权规则中既然提到了作用域,那我们简单看看作用域,其实和JavaScript相差不大,我们非常容易理解

fn main(){ // a在这里无效
    let a = "hello seho" // a有效
} // 作用域结束了,a无效

看起来很简单不是么?和JS一样?

我们此时会发现hello seho这个值是通过字面量硬编码创建了一个字符串并且绑定到a中,这种情况下a将会在栈中,因为它有着固定的大小,那么当作用域结束之后,a将会直接出栈。

内存和分配

对于以上的hello seho这种值,在我们的实际业务中并不常见,我们大多数会存在一个大小不定的内容,而且会在程序的运行时增大或者减小,那么为了支持这种需求只能在堆中开辟出一块内存,意味着程序需要在运行时去创建这块的区域,而且当我们处理完内容,需要把内存还给内存分配器。

第一步很好说,我们调用String::from这种方法就可以在堆中创建一块区域,那么第二步怎么还呢?

在C中是需要手动的释放内存的,但是程序终究是人写的,有时候忘记还了,有时候过早还了,有时候重复还了,都会造成系统bug。在有GC的语言中,GC会记录并且清除不使用的内容从而释放内存,所以这也就是我为啥喜欢JS的原因了,真的太爽了....

我们把上面的代码改造一下

fn main(){ // a在这里无效
    let a = String::from(""hello seho"") // a有效
} // 作用域结束了,a无效

在作用域结束之后,rust会调用一个特殊函数drop从而释放内存,在stirng内部会实现这个drop函数...

移动

我们在JS中写过很多这样的代码

let a = 1;
let b = a;

很显然,它在栈中创建了2个变量, a和b,他们的值都是1

在rust中也一样,但是如果我使用String::from创建变量呢,在堆中和栈中会有不同么?

let a = String::from("hello seho");
let b = a;

很显然,a的值是hello seho,b“复制”了a的指针,它们都存在栈中,指针指向了堆中的hello seho。

诶,难道说,rust的表现和js一样?在我们前面提到了所有权的规则之一,当变量离开了作用域将会自动释放,但是此时a和b都指向同一堆中内容,此时不是造成了多次释放的问题?答案是肯定的,在我们之前就说到过,没有释放,过早释放,多次释放都会对程序造成影响,所以rust在针对我们上面这种代码的时候,作出了一个处理即当a被“复制”到了b身上,此时a不再生效,程序会在编译期间报错。

如此看来,就避免了多次释放的问题,而“复制”也不是真正的复制,而是移动

那么如果真的想克隆一个一摸一样的值,可以调用clone方法,而我们在上面写了一个整型的例子,为什么没有调用clone也可以被克隆,是因为它们本身就是栈中的数据,在栈中的拷贝是快速的,不需要通过移动这种机制来实现拷贝

到此为止,我们应该能理解所有权到底是什么了,我们在本篇笔记中学习了所有权的概念和规则,以及复习了堆栈基本知识,还有rust的内存分配。我们在本篇笔记中大量的使用了js这门语言作为参照对象,如果你不熟悉js的话,下次笔记我会将语言的比较去掉,这样应该会更加容易理解,有问题发评论区,吴彦祖和迪丽热巴发的评论我肯定都会回复

store-persistedstate-killer

EN / 中文

杀手级别的持久化状态管理库


  • 可以为多个库提供持久化服务 (vuex, pinia)
  • 支持 TypeScript
  • 支持 预定义存储驱动 (localstorage, sessionstorage) 以及自定义驱动
  • 支持相对安全的存储环境(非明文)
  • ‍灵活的配置且没有副作用
  • 对开发友好的状态变更 Log
  • 持久化加强功能 (重命名...)

安装

npm i store-persistedstate-killer

快速使用

// main.ts
// pinia平台
import { plugins as killer, config } from 'store-persistedstate-killer'

createApp(App)
  .use(
    createPinia().use((context) => {
      killer.pinia.init(context)
      killer.pinia.use(context)
    })
  )
  .mount('#app')

Demo

Edit objective-sun-1wmt7

目标

  1. 用状态管理接管你的 storage,从此无需担心类型,像操作 store 一样操作 storage 即可
  2. 前端存储不再明文

killer 做的事情

设计

每一个平台的插件你可以单独引入它们,比如你是 pinia 平台,那你仅仅这样引入就可以了

createApp(App)
  .use(
    createPinia().use((context) => {
      killer.pinia.init(context)
      killer.pinia.use(context)
    })
  )
  .mount('#app')

killer 中每一个插件都包含2个部分, 一个就是 init,一个是 use

init

在应用初始化时,把我们 storage 内容同步到 store 中; 如若发现 store 有,但是 storage 没有的 state,也会执行一次同步。这个过程是双向的。在文档上方就有一个 killer的概要图,我们如果站在状态管理的视角下,可以理解 storage 为远端,双方的交流就可以当作pushpull

use

use 是 killer 的核心功能,它可以监听 state 的变更以及 patch 操作,它可以实时地把 state 同步给 storage


如你所见,如果你的业务中,仅仅需要监听 state 然后同步到 storage 这个需求,你也可以仅使用 use 这个插件

如果想看到更多有关平台插件的文档,你可以移步具体的文档中(就在下方)

支持的平台/库

PlatformLibDoc
pinia2
vuex4/5

核心

killer 为各个平台的插件提供了多个核心,使它们能够正常运转,每一个核心主要负责一个业务,比如说配置,加密,存储

配置

killer 本身自带一个开箱即用的配置,你如果有特殊的需要,可以去自定义它们。在此之前你需要了解各个插件的工作原理,我们以 pinia 举例子。pinia 由一个一个 store 组成,store 由 state,getters,action 组成,所以 killer 仅仅是在useStore()之后才运行的插件,killer 接管了 store 的 state,使之能够持久化到本地存储中;那么在持久化的过程中,我们可能需要做一些重命名, 加密数据等工作...

配置名含义类型默认建议
exclude排除指定的仓库名string[ ][ ]
include包含指定的仓库名string[ ][ ]
prefix缓存的key前缀stringpersistedstate-killer-建议传入有效的字符串
iv加密需要用的iv变量string''可以为空
isDev是否是开发环境booleanprocess.env.NODE_ENV === 'development'如果为false将自动加密
storageDriver插件预定义的存储驱动defineStorageDriverdefineStorageDriver('localStorage')支持传入localStorage和sessionStorage
store对仓库进行详细配置Partial<Record<K, StoreConfig>>没有默认配置
defineStorage自定义存储驱动setItem, getItem, removeItem, iteration没有默认配置如果预定义存储驱动defineStorageDriver没有满足你的需求,可以使用这个方法定义新的驱动

你的工程中的自定义配置可能就像这样:

import { plugins as killer, config } from 'store-persistedstate-killer'

createApp(App)
  .use(Router)
  .use(
    createPinia().use((context) => {
      config.defineConfig<'main'>({
        exclude: ['zhangsan'],
        include: ['main', 'test'],
        isDev: true,
        storageKey: 'seho',
        store: {
          main: {
            state: {
              hello: {
                rename: 'wuyu',
              }
            }
          }
        }
      })
      killer.pinia.init(context)
      killer.pinia.use(context)
    })
  )
  .mount('#app')

你可以看到, killer 提倡使用 ts 来构建插件,我们可以给 defineConfig 传入一个联合类型,声明需要对哪几个 store 进行操作,此时如果你在编写 include 和 store 配置时,将会有非常棒的类型提示。

ApiDescType
defineConfig注入配置doc

加密

前端的加密难道没有必要么?确实有人这么说,但是当我们把状态管理的数据明文暴露到 localstorage 中确实不是很好,尽管我们现在都这么做 。我们需要一款易用的加密,不仅可以给 killer 中内部使用,而且还可以暴露给用户,让用户可以加密 api,交换特殊信息?killer 内部使用了crypto-js,默认使用了浏览器ua -> base64, 同时你也可以根
据业务需要指定 key 和 iv。

import { crypto } from 'store-persistedstate-killer'

const _crypto = new crypto()
const message = 'hello, messagehello, messagehello, messagehello, messagehello, messagehello, messagehello, message'
const encryptData = _crypto.encrypt(message)
if (encryptData) {
  const decrypt = _crypto.decrypt(encryptData)
  console.log('解密结果', decrypt)
} else {
  throw Error('加密错误')
}

我们可以给构造函数传递一个 ctx

const _crypto = new crypto({
  iv: 'asdasdasdasdasdasdasdasd',
  key: 'sssaasdasdasdas234234s'
})

查看加密模块的类型声明

ApiDescType
encrypt加密` (data: string) => string \null`
decrypt解密` (data: string) => string \null`

往期回顾

2020年终总结

前言

每年都会写年终总结,目的就是为了3 5年后从博客中找出每一年的年终总结,可以一目了然看到成长,这种感觉是非常幸福的。今年真的收获巨大,因为完成了我职业生涯中很多第一次;或许前几年初入圈子有些许迷茫吧,虽然目前我对我今后几年的发展抱有很大期望,但是如果要达到我的最终目标,付出的时间和精力将会成倍上增。这个目标是什么,后面会有聊到。往年我写年终总结的时候主要是3个核心概念:疫情,心境成长,技术成长,今年我打算多增加几个板块,而且还会传一些图片上来,也算是一份宝贵经历。

先听首歌吧,边听边看

疫情

今年已经临近尾声,没想到西安疫情爆发,仅次于当年的武汉,不知道过年还能不能回家。我现在在家办公中,在家办公很舒服,但是有一点非常不好,我的生物钟全部被打乱了,每天11点睡,自然醒已经早上8点了;如果按照往常工作日,我应该是6点或者不到6点就起床了,但是这样也问题不大,省去了大量通勤时间,尽管我睡眠时间长,但是仍然有学习时间。西安疫情的事件上了很多次热搜,ZF的种种蜜汁操作,还有一码通小程序平均每周崩一次,应急预案也没有这次丢人真的丢大发了。

封城之后,时隔一周看到了政府的救济菜:白萝卜,大白菜,土豆,洋葱,但是不知道下一次送菜是什么时候...

疫情中不得不提的就是,我和几个小伙伴搞了一个核酸地图的小程序,可以清楚的看到自己身边有多少个核酸检测点,上线之后流量暴增,上了纸媒,也上了热搜,接受了采访,这一段经历真的是非常难忘。

IMG_0349.PNG

IMG_0350.PNG

小组开会的随手截图

WechatIMG3527.jpeg

随后当小区居家隔离,西安的临时检测点也就没了,这个程序在一段时间帮助了很多人,虽然现在它没有用处,但是它也曾出现过...

在家办公很爽,尤其是和姐姐们一起住,自己在屋里写代码,饭不用担心,只洗碗就够了哈哈,总之疫情居家生活还是蛮舒服的,舒服是相对的,相信大家也听说过西安有些小伙伴都饿晕了,也有出门买馒头被抓的,也有医院门口因为核酸流产的,也有老父亲因为送医不及时心脏病故,这样对比起来,我真的算幸运的了。


回顾老剧

跑男排面:

IMG_0090.JPG

薛仁贵传奇,真老剧了:

i45iqmbggfuik29.jpeg

怪侠一枝梅:

8c0f65f348ff435f91150b2a760608ee.png

庆余年:

v2-e3c1be7492a0a5b9b2eafc7504467820_1440w.jpeg

越狱1-5:

143892174951732900_a700x398.jpeg


推荐一些最近看的资源

不光有b站视频,还有最近一年写的比较好的文章,还有看的一些书

书籍

视频

文章


未来几年的打算

熟悉我的朋友们都知道,我毕业之后的工资很低很低,曾经只有1.2k只够养活自己,2年过去了,我的薪资差不多是翻了10倍多,听起来感觉很不错,但其实远远达不到我心中的高度,2022年将是我的第三年,在写这篇文章的前一天晚上,和朋友小马哥聊天,改变了一些原先计划:

WX20220105-152052.png

我决定还是2022年安稳过一年吧,因为2022年太多计划了,比如无止境的考试还有驾照,我希望安稳度过,而不是逞一时之快去拿所谓的15 16k的高薪offer。我希望2022年产出极高,水平提升极快,能够奠定相对扎实的基础去实现后面的事情。

我个人非常希望纸贵这个公司是我在中国近几年最后一家公司,这里的小伙伴很nice,等待疫情结束(几年后),如果我的本科以及雅思学习旅途顺利,我大概率将会申请国外的在职研究生,这也算圆了我的学习梦,也算是变相脱离内卷,也希望以后的职业发展将会在其他国家。

2022年非技术目标:

  • 一切考试顺利,英语学习顺利,驾照考试顺利

关于技术

今年说实话没什么开源的作品,大部分都是下半年开始做的,vite的流行意味着前端构建已经变天了,webpack纵然还有用武之地,但是中小新项目基本都会选择vite作为构建工具。vite的出现进一步的带动了esbuild等新兴工具的热度,使用go,rust等语言,借助语言特性可以使编译,检查等工作变得更快更靠谱。

在下半年,来到新团队,就开始搞模板搞新技术栈,开源了一套模板和项目管理工具

WX20220105-180205.png

也写了相关文章介绍了几个模板 基于vite的模板

同样的,写了一个较为完整的命令行工具

1641377080467.jpg

在来新团队时候,也做了一个试验性的东西就是低代码雏形,解决了模板代码生成的问题

WX20220105-180602.png

尽管这个项目不维护了哈哈: https://github.com/seho-code-life/code-template-generation-web

同样的,今年还给antdv以及esbuild-node-tsc(基于esbuild的tsc编译工具)贡献了一部分代码,而且还出了一款教程关于tsrpc(typescript的rpc框架)。

临近年底,也写了一个关于状态管理持久化的库,它是一个支持多平台的持久化插件,马上就发布了

WX20220105-181006.png

https://github.com/1018715564/store-persistedstate-killer

而且在公司内部做了2次分享,第一次是关于前端基建,第二次是关于全栈开发,第二次的经历录制成视频了换一个方式构建全栈应用

顺便说一句,slidev是真好用,用熟悉的语法构建ppt,真的省心不少,建议开发者以后使用slidev写ppt

今年技术这块没有取的突破进展,只是对rust和ts有了新的领悟,以后会在文章体现。明年团队有flutter的工作,到时候我也会分享一些flutter的小文。

2022年技术相关目标:

  • 学习rust和wasm
  • 学习flutter
  • vue3全家桶源码分析
  • 算法
  • ts水平提高
  • js基础和http基础

拜拜咯,2022年终总结见~