2021,鬼知道我读了多少遍React源码

30,954 阅读10分钟

「时光不负,创作不停,本文正在参加2021 年终总结征文大赛

2020年11月离开待了5年的广州,来到深圳的现在的公司,当时前端团队有15人左右,但没想到的是,到了2021年的三月份已经走了9个老同事,其中有我的导师还有前端主管。特别是在主管走的那天,心情有点低落,因为团队的天花板很大部分在Leader。当时就在想跳槽就是为了找个厉害的leader、厉害的团队学到多点技术,但没想到现实却是这样。。。但啪的一下,很快啊,到了晚上下班我就调整了心态,既然天花板走了,那就自己努力提高团队的技术天花板吧

image.png

这个是当天跟朋友的聊天,当时在工位还有点迷茫,等到走到楼下就迅速调整好心态了,用发展的眼光看问题真的是yyds

技术方面

源码

  • React 源码。这是我从去年年末就跟前同事夸下的海口,说打算到2021的6月熟悉React源码,可是刚开始看就觉得源码太多太难,就搁置了。等到六月份再重新拿起来看,用了快两个月时间,一行行 debugger,到现在算是看了七八遍了,也算是对React源码比较熟悉,可见react-source-study
  • redux、react-redux 源码。redux 之前主要是看各种源码解析的文章,可谓百行源码,万行思想,且里面用到了挺多设计模式的。在看完 React 源码之后,我就花了一周把 redux 和 react-redux 源码看完了,redux 本身不难,react-redux 可能要花点时间去消化理解。且最新的 redux 等都用 ts 重写,这个表示好评,而且也用了React18的use-sync-external-store,源码里面也有对这个进行解析。可见 redux、react-redux及相关中间件源码分析

image.png

  • axios。axios 是我们一直用来请求数据的库,所以还是对它里面的实现比较感兴趣的,大概花了三四天看完,相对比较简单,但要吐槽的一点是 axios 没用 ts 写,强迫症的我用 ts 重写了。可见axios源码分析
  • react-router、react-router-dom、history。是最新的 V6 版本,也是用 ts 重写了,之前写了个 专栏 ,有兴趣的可以看看。同时我也有幸成为了 react-router 的 contributor,可见 react-router、react-router-dom、history 源码分析

image.png

技术栈

今年主要掌握了如下技术栈:

  • TS,现在能很熟练地使用
  • mobx,上家公司是用 dva,所以今年才接触 mobx,前者是声明式,后者是响应式,跟 react 和 vue 挺类似
  • react-native,从零到上线项目,也踩了很多坑,总结输出了一篇文章,不过由于涉及到业务相关,只发到公司语雀的内部

image.png

  • taro 小程序,也是从零到上线项目,其中涉及到蓝牙打印的也踩了很多坑
  • electron,主要是因为浏览器无法实现自动打印,所以通过electron实现下单后自动打印的功能
  • 其他如 webpack5、vite 和 babel。webpack年初读了 tapable(高级版的 EventEmitter,webpack底层的基础)、最近看的 loader-runner,还有一些基本配置;vite 现在都用于个人的项目,没有特别深入了解过,看什么时候公司的项目能平稳切换到vitebabel 只学了一部分,接触了一点点 AST,学的不全面,这几个就一笔带过吧~

组件库

公司的 组件库主要由我来维护,不过,我接手的时候组件库其实是很不好用,比如:

  • 之前的 Table 组件,要添加选择、subTable、固定列等功能都需要添加各种 HOC
  • Form 表单功能单一,每个 FormItem 里面还得写 value和 onChange
  • 很多组件如果不写props 如 value、data 就会报错,或者缺少泛型提示等等

sass 中后台 Table 和 Form 表单占据了绝大部分,这无疑给同事带来了很大的麻烦,为了减少同事的开发难度,我对组件库很多地方都做了改造。下面会以 Table 和 Form 作为例子,讲下我是如何改造组件库的。

1.改造Table

刚到公司的时候问同事如何使用,同事也不太清楚HOC的顺序,导致即使是老同事也可能不太清楚如何配置,那么要渲染一个Table,只能取CV以前其他同事的代码,但实际上是不知其所以然的。

image.png

这是刚到公司问同事 Table 的 HOC 如何配置

上面提到的问题我肯定是无法容忍的,就在某天,我突然想起下面代码中嵌套的各种 HOC 好像在哪里看过?

const KeyboardVirtualTable = diyTableHoc(
    keyboardTableHoc(fixColumnsTableHoc(editTableHoc(TableVitualized)))
 )
 
 // 可不可以转换为下面这样?
 const KeyboardVirtualTable = applyMiddleware([
 diyTableHoc, 
 keyboardTableHoc, 
 fixColumnsTableHoc, 
 editTableHoc
 ])(TableVitualized)

啊!上面代码的各种HOC不就类似中间件吗!!

要让我记各种HOC是不可能的,这辈子都不可能的,所以就在某个周末,我把上面的想法实现了:

// util.ts
// 洋葱模型
const compose = (...fns: HocMiddleware[]): HocMiddleware => {
  fns = [...fns]
  if (fns.length === 0) {
    return (node: ReactNode) => node
  }
  return fns.reduce((res, cur) => (...args: any) => res(cur(...args)))
}
// 中间件
const applyMiddleware = (...middlewares: HocMiddleware[]) => (node: ReactNode) => {
  return compose(...middlewares)(node)
}

export { applyMiddleware }

上面参考了redux实现了 composeapplyMiddleware,然后通过各种 isXXX prop来配置 Table 的功能:

function Table<D extends object = any>({
  isDiy,
  isBatchSelect,
  isExpand,
  isSort,
  isEdit,
  isSub,
  isKeyboard,
  isSelect,
  isIndex,
  ...res
}: TableProps<D>) {
  const Table = useMemo(() => {
    // 按需引入
    let keyboardTableXHOC
    if (isKeyboard) {
      keyboardTableXHOC = require('@gm-pc/keyboard').keyboardTableXHOC
    }
    // 配置中间件
    const hocMiddles = [
      isExpand && expandTableXHOC,
      isBatchSelect && batchActionSelectTableXHOC,
      isSelect && selectTableXHOC,
      isIndex && indexTableXHOC,
      isDiy && diyTableXHOC,
      isSort && sortableTableXHOC,
      isEdit && editTableXHOC,
      isSub && subTableXHOC,
      isKeyboard && keyboardTableXHOC,
    ].filter(Boolean) as HocMiddleware[]

    const TempTable = applyMiddleware(...hocMiddles)(BaseTable)
    return TempTable as typeof BaseTable
  }, [
    isDiy,
    isBatchSelect,
    isExpand,
    isSort,
    isEdit,
    isSub,
    isKeyboard,
    isSelect,
    isIndex,
  ])

  const tableProps = (res as unknown) as TableProps<D>
  return <Table {...tableProps} />
}

好了,现在终于不用再去记各种HOC和其顺序了,实现完之后,按捺不住内心的激动,就把上面的成果跟团队的成员分享了:

image.png

当然,给Table还加了泛型、表头排序等等功能,具体可看 gm-pc 1.2.3重大更新之TableX

2.ControlledForm<K>(受控表单)

原先表单组件都得传对应的 value 和 onChange,无法实现如统一管理、表单项联动等功能,导致代码相当地冗余,对前端开发者相当不友好,因此亟待受控表单来解决这一问题。

image.png

这里要注意,上面所说的 受控表单 实际意思是让 Form 自动处理 value 和 onChange,而不是开发者自己手写,因为原先的 Form 已经被占用了,所以我就另外取了个组件名。功能是参照antd的,即通过一个在 Form 里面使用 Context,然后:

  • 通过在ControlledForm中的ControlledFormContext上下文 Provide 对应的表单值及修改表单值的方法(providerValues)、然后在ControlledFormItem中通过useContext获取到上下文的值
  • ControlledFormItem中的组件无需传 value 和 onChange,源码根据 valuePropName?: 'value' | 'checked' | 'selected' | 'date'trigger?: string(组件修改值的回调函数名,如 onChange)劫持组件,给组件加上对应的 value和 trigger 函数,通过 React.cloneElement 克隆新的组件
function ControlledFormItem<T = any>(props: ControlFormItemProps<T>) {
  const {
    name = '',
    ...restProps
  } = props

  const {
    ...
  } = useContext(ControlledFormContext)
  const childProps = {
    ...children.props,
  }
  // 如果有提供name
  if (name) {
    // 拦截加上value
    childProps[valuePropName] = values?.[name]
    const triggers = new Set<string>([trigger])
    if (!Array.isArray(children)) {
      triggers.forEach((eventName) => {
        childProps[eventName] = (args: any) => {
          // 触发更新
          if (onChange) {
            const newValue = onChange(name, args)
            if (onFieldChange && resetFields && getFieldsValue && setFieldsValue) {
              onFieldChange(newValue, { resetFields, getFieldsValue, setFieldsValue })
            }
          }
        }
      })
    }
  }
  ...

  return (
    <FormItem {...restProps}>
      {cloneElement(children, childProps)}
    </FormItem>
  )
}

现在就无需手动受控组件了,不用再写各种冗余的 vlaue 和 onChange

image.png

当然,上面说的列表和表单肯定少不了各种泛型的实现:

image.png

image.png

这里就不一一说明的,感兴趣的可以看下:gm-pc 1.2.3重大更新gm-pc 1.3.3 表单验证

非技术方面

1.看完了毛选一到八卷

学到了用发展的眼光看问题;没有调查没有发言权;实践论、矛盾论;革命乐观主义精神;认识世界,改造世界等等。毛选确实是一部宝藏书,但第一遍只是从头到尾看完,打算今后的几十年都多看看,领悟其中的精髓。

image.png

image.png

2.看完了红楼梦

image.png

image.png

然后还花了一段时间去搜集红楼梦的一些资料,如背景、解读、每个人的评价等等。

image.png

image.png

image.png

3.重温了鲁迅的一些小说

如《呐喊》、《狂人日记》、《孔乙己》、《药》、《风波》、《故乡》、《阿Q正传》等等。

image.png

4.重温了挪威的森林小说和电影

《挪威的森林》是很喜欢的一部小说,是大二看的,今年重新回味,不过电影感觉拍的不怎样。

image.png

image.png

image.png

image.png

5.看完了100集中国通史

image.png

大概是从三月份看到十二月,每天早上会看一点,看完感觉古代每个朝代的兴亡都是那么的类似,而且其中的历史人物如果能活得够久,那其成功的概率也相对较大。

6.长征、解放战争三大战役、朝鲜战争

在B站和知乎找一些上面提到的历史资料,背景,人物等等。我一个前同事说“我身边的朋友就你一人会去研究这些战争”,哈哈。当然,我所了解的只是一些皮毛而已,只是对这些比较感兴趣罢了。

作为面试官

作为一个过来者,之前一直是作为面试者的角色,也感受过一些面试官的颐指气使,所以以前就在想,如果以后做面试官了,无论面试者怎样,都应本着相互尊重的态度,毕竟人家大老远跑来面试,不是来受委屈的。

面试过程中如果不会或答错,我也会指出为何错误(有时间的情况下),也让人家面完有所收获。最后面完,我都会跟对方说,感谢抽出时间来面试,毕竟浪费别人时间无异于谋财害命,每个人的时间都是挺宝贵的。这样对方也对我们公司比较有好感,毕竟面试官的态度怎样决定了被面试者对公司的第一印象。

以下是一些面试者的评价,看到还是挺开心的:

image.png

image.png

2022年的展望

  • 继续深入工程化方面,做工具链,深入学习 babel, AST,研究下 vite 等
  • React 搞的差不多了,明年希望研究下 Vue,目前只停留在会用的阶段
  • 成为React、Vue的 contributor
  • 学习技术需要结合业务,服务业务,希望更深入了解公司业务,这样开发才比较游刃有余
  • 涉猎下three.js,还有node框架nest或者egg还是midway?

最后

2021认识了很多大佬,其中不少比我年轻,他们是那么的年轻、厉害还努力,和他们相比,我真是菜鸡一个。但我相信只要每天进步一点点,终究只是时间问题,希望有一天能成为像他们那样的人~

感谢大家看到这样,最后,通过《实践论》最后一段来结束本文:

通过实践而发现真理,又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识,又从理性认识而能动地指导革命实践,改造主观世界和客观世界。实践、认识、再实践、再认识,这种形式,循环往复以至无穷,而实践和认识之每一循环的内容,都比较地进到了高一级的程度。这就是辩证唯物论的全部认识论,这就是辩证唯物论的知行统一观。

2022, 继续努力,加油!各位!!