模拟面试220511. 两年广州期望14

本次的候选人两年工作经验,坐标广州,在公司内做过许多基础建设与工程化的工作,比如

  1. 基于 webpack 构建的前端项目模板
  2. 基于 pipeline 版本发布的基础建设

候选人期望薪资为 14k+,个人感觉他偏于保守,综合面试下来感觉不错,其中不乏不少亮点:

  1. 他们使用了 ahooks 作为 hook 库,在使用过程中也会看其源码,在面试过程中对自己常使用过的 hook 源码能答得上来
  2. 针对两年开发的同学,对于 svg 图标库的开发极具挑战
  3. 一些基础设施的建设了解颇多,如 CI,一般了解这些表明对技术热情比较高

本次面试记录,不仅包含了山月自己所做的一些面试记录,也包含了候选人的面经,可见 一次模拟面试总结,再推荐下候选人的博客 https://www.hsslive.cn/

视频版: 两年前端,广州求职,要价 14k,依我说,小伙子可以多要点,涉及组件建设、工程化以及丰富的项目经验

以下是简历大致内容:

简历

山月的面试问题记录

  1. ahooks
    1. useLocalstorage ✅
    2. useUpdate
    3. 如何实现 useUpdate ✅
      1. https://github.com/alibaba/hooks/tree/master/packages/hooks/src/useUpdate
    4. useUpdateEffect
  2. 组件二次封装
    1. 时间选择器
    2. 树形选择器
    3. 上传组件
      1. 占用内存
      2. svga -> 动画
      3. https://svga.io/
      4. lottie
  3. 基础建设以及前端工程化流程建设完善
    1. 版本控制
      1. 快速回滚 ✅
      2. changelog
      3. package 是哪个 -> standard-version/release-it/np ❎
    2. jenkins
  4. 发包的工具
    1. standard-version/release-it/np ❎
    2. https://www.npmtrends.com/release-it-vs-standard-version-vs-np
  5. 业务-直播-活动
    1. 外企?
  6. svgo / svg ✅
    1. path-xml -> dom ?
    2. https://react-svgr.com/playground/
    3. https://www.svgviewer.dev/
  7. 如何更好地使用 svg
    1. Font-Awesome: https://github.com/FortAwesome/Font-Awesome
    2. Heroions: https://github.com/tailwindlabs/heroicons

候选人的面经总结

候选人对本次模拟面试也进行了总结,原地址: https://www.hsslive.cn/article/93

感谢up,这次面试获益良多,一开始没有get到up想表达的点,后面才反应过来,没想到svg还能有这么好用的在线工具svgr和svgviewer这些,也是因为有写过这个图标库的经验,再看这些工具,确实大概能知其所以然。

业务

主要是针对公司业务进行各种二次封装或者编写组件,各家公司业务不一样,而且感觉我的业务也没什么亮点的,因此不谈。

基建

主要也是根据业务,封装以及抽离各种组件、各种常用业务方法,统一放到私有库里面以提高开发效率,业务类也不谈(私库发包其实还是有的说的,但我没权限发包)。

规范

规范是每家公司都有的,简单说下

  1. lint-staged,执行一些操作,如:prettier、eslint。
  2. husky,git 钩子。
    1. pre-commit,在 git commit 前执行 lint-staged。
    2. commit-msg,在 git commit 后执行 commitlint。
  3. commitizen,执行 git cz 的时候可规范进行提交 commit。
  4. standard-version,更新 changeLog.md 以及 package.json 的 version。

开发

  1. 开发完成后,提交代码到远程仓库,远程仓库有三个基本分支(其他的都是功能分支),分别是 beta、preview、master。
  2. 一般本地开发完成会把自己的功能分支合并到 beta 分支提交上去测试。
  3. 测试没问题了就合并到主分支,然后执行release命令生成修改版本号以及生成changeLog。
  4. 最后push到远程仓库。

构建部署

  1. 主要基于jenkins构建。
  2. master 分支可以发布三个环境:beta、preview、prod 环境,非 master 只能发布 beta、preview 环境。
  3. master 分支需要根据 tag 进行发布(可以根据tag进行快速回滚),非 master 分支会根据分支进行发布。
  4. 公司项目很多,构建很频繁,而且构建的项目的时候非常的占用cpu和内存,因此公司有一个单独的服务器用来做jenkins构建
  5. 在构建的时候,会先执行构建的项目里面的构建脚本,构建完成后,将构建好的文件压缩,会通过scp命令上传到公司的cdn服务器
  6. 然后添加对应的nginx配置即可。

上面的东西其实每家公司都有,但是不要停留在他们原本就有了,自己只需要在此基础上写业务就好了,这样其实还不如不说。

最好就是除了业务,其他的规范和版本控制这些自己都实现一遍,然后可以说自己实现过知道怎么做,而不是只是单纯的在此基础上只负责写业务。

Ahooks

面试一般直接说最核心的实现,不考虑边缘情况以及性能(除非面试官问)

useUpdateEffect

useUpdateEffect 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行。

项目中用到了,但是我自己没用过,个人感觉比较鸡肋,就没看源码。

useDebounce

用来处理防抖值的 Hook。

useThrottle

用来处理节流值的 Hook。

实现 useUpdate

https://github.com/alibaba/hooks/tree/master/packages/hooks/src/useUpdate

主要利用了 js 里面的{}不等于{},在使用这个 hooks 的时候,直接调用导出的 useUpdate(),即可对整个函数式组件进行重新渲染。

import { useState } from "react";
 
const useUpdate = () => {
  const [, setState] = useState({});
  return () => setState({});
};
 
export default useUpdate;

实现 useLocalStorageState

https://github.com/alibaba/hooks/blob/HEAD/packages/hooks/src/useLocalStorageState/index.ts

主要就是使用 useState 的时候同时把数据缓存到 localStorage 里,主要实现就是对 localStorage 进行了一层 useState 包装,然后导出一个 state 和设置 state 回调函数,在调用这个回调函数的时候,同时设置 localStorage.setItem 以及更新内部的 useState。以此达到statte和localStorage同步。

这个 hook 官方做了非常多的边缘处理,这里的实现只是核心思路。

import { useState, useEffect } from "react";
 
const useLocalStorageState = (key, initValue) => {
  const [state, setState] = useState({ key, value: JSON.stringify(initValue) });
 
  const setLocalStorageValue = (value) => {
    try {
      if (value !== undefined) {
        window.localStorage.setItem(key, value);
        setState({ key, value: value });
        return;
      }
      window.localStorage.removeItem(key);
    } catch (error) {
      console.log(error);
    }
  };
 
  const getLocalStorageValue = () => {
    const cache = window.localStorage.getItem(key);
    return cache;
  };
 
  useEffect(() => {
    const cache = getLocalStorageValue() || state!.value;
    if (cache !== null) {
      setLocalStorageValue(cache);
    }
  }, []);
 
  const parse = () => {
    try {
      return JSON.parse(state?.value || initValue);
    } catch (error) {
      console.log(error);
    }
    return JSON.parse(initValue);
  };
 
  // 这个parse是最简单的容错处理,虽然不太优雅
  return [parse(), setLocalStorageValue];
};
 
export default useLocalStorageState;

svg方案之为什么用svg

  1. 和图片对比,svg更灵活,以及因为是矢量图不会因为大小导致模糊。
  2. 搭建组件库的时候,如果使用了图片这种静态资源,除非将图片打成base64,否则直接额外生成图片,然后其他文件通过路径引这个图片使用,这样的话一定是有路径问题的:
    1. 因为这个组件文件只能是根据相对路径或者绝对路径引这个图片,假设这个路径是../../img/xxx.jpg或者/img/xxx.jpg。这个路径如果在类似vuecli这种脚手架的环境,开发的服务器默认的8080端口,
    2. 如果是/img/xxx.jpg,对于vuecli来说就是在静态目录里面找,也就是默认的public目录里面找/img/xxx.jpg,但是肯定没有这个图片。
    3. 如果是../../img/xxx.jpg,这个我没试过,感觉应该会把public目录作为/,会在public的外层找xxx.jpg,因此,肯定也是找不到这个图片的。

svg icons 的组件库方案

主要说下 ant-design-icons 的方案,主要源码:

  1. https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-svg
  2. https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-vue

注意:

  1. 这里不说具体的实现方式,因为官方组件库实现过程是非常多的,这里只说了核心的实现思路,不管过程如何多,其实要做的事情都是一样的。
  2. 因为这个组件库最终实现的效果是将 svg 文件转成开箱即用的 vue/react/angular 组件文件,它的主要作用是根据 svg 文件输出相对应组件文件,然后发布到 npm 上面,给其他库使用的。因此这些事情并不能浏览器环境做(至少不合适),而是在 node 环境做的。

步骤一:svg 文件转成 js 文件

  1. 因为是跑在 node 环境,因此操作文件都是基于流(stream),所有使用了 through2 进行流操作。

  2. svg 文件里面有一些无用的属性,首先使用 svgo 对 svg 文件进行了优化(optimizing)

  3. 将优化好的 svg,使用 parseXML 这个库进行 dom 解析

  4. 将解析好的 dom 数据,通过固定的格式(比如 ejs 模板)输出成 js 文件,如:

    1. ejs 模板
    // ejs模板,iconname是svg名称,iconcontent是解析好的dom数据
    const <%= iconname %> = <%= iconcontent %>;
    
    export default <%= iconname %>;
    1. 上面的模板通过解析最终会生成:
    // 这个文件是由billd-ui-icons/build-tools/svgo/template/icon-svg/asn.ejs自动生成的,请勿手动修改!
    const AccountBookFilled = {
      type: "document",
      children: [
        {
          type: "element",
          isRootNode: true,
          name: "svg",
          attributes: { class: "icon", viewBox: "0 0 1024 1024" },
          children: [
            {
              type: "element",
              name: "path",
              attributes: {
                d: "M880 省略。。。。",
              },
              children: [],
            },
          ],
        },
      ],
      name: "account-book",
      theme: "filled",
    };
     
    export default AccountBookFilled;

步骤二:生成 vue 组件文件

有了保存 svg 的 dom 数据的 js 文件之后,还是一样的办法,通过固定的格式(比如 ejs 模板)输出成 jsx 文件

ejs模板:

// ejs模板,iconname是svg名称,iconcontent是解析好的dom数据
// 这个文件是由build-tools/svgo/template/icon-vue/icon-vue.ejs自动生成的,请勿手动修改!

import <%= iconname %>Svg from '@huangshuisheng/icons-svg/lib/asn/<%= iconname %>';
import BilldIcon from '../billdIcon';

export default {
  name: '<%= iconname %>Svg',
  displayName: '<%= iconname %>Svg',
  functional: true,
  props: {},
  render(h, ctx) {
    return (
      <BilldIcon
        innerSvgProps={<%= iconname %>Svg.children[0].attributes}
        children={<%= iconname %>Svg.children[0].children}></BilldIcon>
    );
  },
};

上面的模板通过解析最终会生成:

// 这个文件是由build-tools/svgo/template/icon-vue/icon-vue.ejs自动生成的,请勿手动修改!
 
import AccountBookFilledSvg from "@huangshuisheng/icons-svg/lib/asn/AccountBookFilled";
import BilldIcon from "../billdIcon";
 
export default {
  name: "AccountBookFilledSvg",
  displayName: "AccountBookFilledSvg",
  functional: true,
  props: {},
  render(h, ctx) {
    return (
      <BilldIcon
        innerSvgProps={AccountBookFilledSvg.children[0].attributes}
        children={AccountBookFilledSvg.children[0].children}
      ></BilldIcon>
    );
  },
};

步骤三:编译 jsx 文件

这个没什么好说的,因为是使用的 vue 的 jsx 方式,可以直接使用 babel 进行编译,具体看:

  1. vue 处理 jsx:https://github.com/vuejs/babel-plugin-jsx
  2. gulp-typescript:https://github.com/ivogabe/gulp-typescript

非核心

参考了:babel插件:https://github.com/ant-design/antd-tools/blob/master/lib/replaceLib.js

Babel 插件本质上就是编写各种 visitor 去访问 AST 上的节点。当遇到对应类型的节点,visitor 就会做出相应的处理,从而将原本的代码 transform 成最终的代码。

这里因为最终会构建两个版本,一个es,一个lib,但是构建的版本都是通过同一个ejs模板生成的jsx,因此他们引用的路径都是同一个lib,因此需要我在编译es版本的时候使用了这个babel插件,添加了ImportDeclaration和ExportNamedDeclaration两个方法,即当解析到import或者export的时候,我用正则匹配将lib给替换成es,即:

import AccountBookFilledSvg from "@huangshuisheng/icons-svg/lib/asn/AccountBookFilled";
// 替换成:
import AccountBookFilledSvg from "@huangshuisheng/icons-svg/es/asn/AccountBookFilled";

虽然这个babel插件做的事情就是这么简单,但是能比只会用babel以及babel插件好很多,最起码知道babel插件做的事情。

总结

其实 ant-design-icons 官方的组件库核心的实现就是上面的三个步骤,看似做的事情不多,但是过程是比较复杂,只是我为了简洁省略了很多东西,期间最重要的是设计能力以及工程相关的知识。

因为组件库的方案适合大批量的构建 svg 组件,但是平常开发中,可能只需要一两个 svg 组件,而且,可能对这个 svg 有一些定制化的要求,这时候可以使用一些更方便的 svg 方案进行处理

一些svg资源

  1. https://heroicons.com/
  2. https://github.com/FortAwesome/Font-Awesome

SVGR

可以快速将svg生成react组件

https://react-svgr.com/playground/

svgviewer

可以快速将svg生成react组件

https://www.svgviewer.dev/

https://www.svgviewer.dev/svg-to-react-jsx