1

一次模拟面试总结

前端 1 3904 0
发表于: 2022-05-12 17:24:52

简介: 暂无~

基础建设/前端工程化流

业务

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

基建

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

规范

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

  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。

构建部署/版本控制

主要基于 git 和 jenkins。

开发

  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: initValue });

  const setLocalStorageValue = (val) => {
    try {
      if (val !== undefined) {
        window.localStorage.setItem(key, JSON.stringify(val));
        setState({ key, value: val });
        return;
      }
      window.localStorage.removeItem(key);
      setState({ key, value: initValue });
    } catch (error) {
      console.log(error);
    }
  };

  const getLocalStorageValue = () => {
    const cache = window.localStorage.getItem(key);
    return cache;
  };

  useEffect(() => {
    const cache = getLocalStorageValue();
    if (cache === null) {
      setLocalStorageValue(initValue);
    } else {
      window.localStorage.setItem(key, cache);
      setState({ key, value: JSON.parse(cache) });
    }
  }, []);

  const parse = () => {
    try {
      return state.value;
    } catch (error) {
      console.log(error);
    }
    return 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,因此,肯定也是找不到这个图片的。

组件库方案

主要说下 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 方案进行处理

SVGR

可以快速将svg生成react组件

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

svgviewer

可以快速将svg生成react组件

https://www.svgviewer.dev/

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

一些svg资源

  1. https://heroicons.com/
  2. https://github.com/FortAwesome/Font-Awesome
React hooks 面试记录 CI/CD

最后更新于:2022-05-12 21:29:04

欢迎评论留言~
0/400
支持markdown
Comments | 1 条留言
按时间
按热度
- 管理员 超级管理员
3 年前

useLocalStorageState这个hook一开始写的太烂了,没有兼容性,后面改了下还能用。

0 点赞
回复
广东省 - 广州市 Mac OS 10.15.7
已加载所有留言~