金九银十,带你复盘大厂常问的项目难点(三)

简介: 金九银十,带你复盘大厂常问的项目难点

Element-UI 的多语言方案是怎么设计的?


Element UI 使用了 Vue 的插件 vue-i18n 实现多语言支持,具体的设计和实现过程如下:

1. 定义语言包

首先,Element UI 定义了一个 JavaScript 对象作为语言包。每种语言都有一个对应的语言包,例如:

export default {
  el: {
    colorpicker: {
      confirm: 'OK',
      clear: 'Clear'
    },
    // ...other components
  }
};

2. 加载语言包

Element UI 提供了一个 i18n 方法用于加载语言包。

import ElementUI from 'element-ui';
import locale from 'element-ui/lib/locale/lang/en';
Vue.use(ElementUI, { locale });

3. 使用语言包

Element UI 的组件会使用 $t 方法获取语言包中的文本。例如:

<template>
  <el-button>{{ $t('el.button.confirm') }}</el-button>
</template>

在这个例子中,按钮的文本会根据当前的语言包来显示。

4. 集成 vue-i18n

如果你的项目中已经使用了 vue-i18n,Element UI 会优先使用 vue-i18n 提供的 $t 方法。你可以这样配置:

import Vue from 'vue';
import VueI18n from 'vue-i18n';
import ElementUI from 'element-ui';
import enLocale from 'element-ui/lib/locale/lang/en';
import zhLocale from 'element-ui/lib/locale/lang/zh-CN';
Vue.use(VueI18n);
const messages = {
  en: {
    message: 'hello',
    ...enLocale // 或者用 Object.assign({ message: 'hello' }, enLocale)
  },
  zh: {
    message: '你好',
    ...zhLocale // 或者用 Object.assign({ message: '你好' }, zhLocale)
  }
};
const i18n = new VueI18n({
  locale: 'zh', // set locale
  messages, // set locale messages
});
Vue.use(ElementUI, {
  i18n: (key, value) => i18n.t(key, value)
});

在这个例子中,我们先加载了 vue-i18n,然后定义了两种语言的语言包(英文和中文)。最后,我们配置了 Element UI 使用 vue-i18n$t 方法。

这样,Element UI 的组件就能够根据 vue-i18n 的语言设置显示对应的文本。


组件库如何实现在线主题定制的?


1. 使用 CSS 变量定义样式

将组件的样式使用 CSS 变量定义,这样可以通过改变 CSS 变量的值来修改样式。

:root {
  --primary-color: #1890ff;
}
.btn {
  background: var(--primary-color); 
}

2. 提供主题文件进行配置

让用户可以通过导入自定义的主题文件来覆盖默认样式。

// theme.js
export default {
  '--primary-color': '#409eff'
}

3. 在线主题编辑器

提供一个在线工具,用户可以在工具中配置主题,生成主题文件。

工具会提交主题配置,服务器端接收后动态编译生成新的样式,并返回给前端。

4. 前端应用新样式

前端通过加载服务器返回的 CSS 文件来应用新的主题样式,实现样式更新而无需重新打包。

// 请求主题文件
fetchTheme(theme).then(css => {
  // 动态创建style标签,插入css
  const style = document.createElement('style');
  style.innerHTML = css;
  document.head.appendChild(style);  
})

5. 持久化主题配置

将用户主题配置持久化本地存储,这样每次访问都可以应用上次选定的主题。

组件库的类型定义应该怎样设计?

组件库的类型定义设计取决于很多因素,包括库的大小、复杂度、可能的使用场景等。

1. 定义全局类型 versus 定义组件Props类型

在组件库中,我们经常需要定义一些可以在多个组件之间共享的全局类型,以及针对特定组件的props类型。例如:

// 全局类型
export interface Size {
  width: number;
  height: number;
}
// 组件Props类型
export interface ButtonProps {
  size?: Size;
  label: string;
  onClick?: () => void;
}

2. 类型导出应该集中还是分散?

是否集中导出类型取决于组件库的大小和复杂度。对于小型库,可以在一个单独的文件中集中导出所有类型;对于大型库,可能需要将类型定义分散在各个组件文件中,然后在一个单独的文件中重新导出它们。例如:

// 在各个组件文件中定义和导出类型
// button.ts
export interface ButtonProps { /*...*/ }
// 在一个单独的文件中重新导出所有类型
// types.ts
export type { ButtonProps } from './button';

3. 如何设计类型层级关系?类型复用?

在设计类型时,应尽可能地利用 TypeScript 的类型系统来构建类型层级关系,并复用类型。例如,你可以使用类型交叉(&)和类型联合(|)来复用类型:

type SmallSize = { width: number; height: number };
type LargeSize = SmallSize & { depth: number };
type Size = SmallSize | LargeSize;

4. 类型定义要充分还是精简?

类型定义应尽可能精简,同时提供足够的信息来描述类型的形状和行为。避免使用 anyunknown 类型,除非有特别的理由。例如:

// 不好的类型定义
interface ButtonProps {
  [key: string]: any;  // 这不提供任何有关props的信息
}
// 好的类型定义
interface ButtonProps {
  size?: Size;
  label: string;
  onClick?: () => void;
}

总的来说,设计好的类型定义可以提高代码的可读性和可维护性,同时减少运行时错误。


组件库的渐进升级策略应该怎么设计?


组件库的渐进升级策略通常会涉及到版本控制、向下兼容性、废弃通知以及旧版本的兼容性等多个方面。这种策略的主要目的是在保持库的稳定性和功能性的同时,尽可能地减少对用户的影响。

1. 版本控制策略

组件库通常遵循语义化版本 (SemVer) 规范进行版本控制。在语义化版本中,每个版本号都由三部分组成:主版本号、次版本号和补丁版本号。

例如,版本号为 1.2.3 表示主版本号为 1,次版本号为 2,补丁版本号为 3。

  • 主版本号(Major): 当你做了不兼容的 API 修改
  • 次版本号(Minor): 当你做了向下兼容的功能性新增
  • 补丁版本号(Patch): 当你做了向下兼容的问题修复

2. 向下兼容处理

向下兼容性是指在升级组件库时,保证新版本不会破坏旧版本的功能。例如,如果新版本的一个组件删除了一个属性,而这个属性在旧版本中是必需的,那么这个变化就不是向下兼容的。

在进行不向下兼容的变化时,应在主版本号上进行增加,以警告用户可能需要修改他们的代码。

3. 功能被废弃怎么通知用户升级?

当一个功能或者组件被废弃时,应在库的文档、更新日志以及相关的 API 文档中明确注明。在代码中,可以通过添加警告或者错误信息来提醒用户:

function deprecatedFunction() {
  console.warn('Warning: deprecatedFunction is deprecated and will be removed in the next major version.');
  // 功能的原始实现
}

4. 兼容旧版本的方案

兼容旧版本的策略取决于特定的需求和资源。一种常见的策略是在主版本升级后,继续维护旧版本的一个分支,以便在必要时进行修复和改进。例如,如果当前版本是 2.x.x,那么可以维护一个 1.x.x 的分支。

在实践中,以上的策略和方法可能需要根据具体的情况进行调整。一个好的渐进升级策略应能够平衡新功能的引入、旧功能的废弃以及向下兼容性的维护。


组件库的按需加载实现中存在哪些潜在问题,如何解决?


按需加载(也称为代码拆分)是现代前端开发中常见的一种优化手段,可以有效地减少应用的初始加载时间。对于组件库来说,它使用户只加载和使用他们真正需要的组件,而不是加载整个库。


babel-plugin-import


Babel 插件: 使用如 babel-plugin-import 的 Babel 插件可以在编译时将导入整个库的语句转换为仅导入使用的组件。

```javascript
import { Button } from 'your-ui-lib';
// 在编译时,babel-plugin-import 将上面的语句转换为以下语句:
// import Button from 'your-ui-lib/button';
```


tree-shaking


Webpack、Rollup 等工具都已经支持了 Tree shaking。在项目的配置中开启 Tree shaking,然后使用 ES Modules 的导入导出语法,即可实现按需加载。

但是在使用 Tree shaking 的时候,有一个需要特别注意的地方,就是“副作用(side effects)”。

有些模块的代码可能会在导入时执行一些副作用,例如改变全局变量、改变导入模块的状态等。这种情况下,即使模块中的部分导出没有被使用,由于其副作用,也不能被 Tree shaking 移除。否则,可能会导致程序运行出错。

例如,在 CSS in JS 的库中,可能存在这样的代码:

import './styles.css'; // 有副作用,改变了全局的样式

在这种情况下,你需要在 package.json 中显式地指定模块的副作用,以防止它们被错误地移除:

{
  "name": "your-library",
  "sideEffects": [
    "./src/styles.css"
  ]
}

如果你的库没有任何副作用,你可以将 sideEffects 设置为 false

{
  "name": "your-library",
  "sideEffects": false
}


样式如何实现真正的按需加载?避免样式重复打包?


image.png

样式和逻辑分离 样式和逻辑结合 样式和逻辑关联
开发打包流程 中等 简单 复杂
输出文件 JS 文件和 CSS 文件 JS 文件 JS 文件和 CSS 文件
使用方法 分别引入 JS 和 CSS 只引入 JS 只引入 JS
按需加载 需要额外支持 支持 支持
性能影响 带额外 runtime,可能有影响
SSR 支持 需要额外支持(部分方案不支持) 支持(可能需要使用者调整配置)
支持写法 常规 CSS / 零运行时 CSS in JS 常规 CSS / CSS in JS 常规 CSS / 零运行时 CSS in JS
关键样式提取 自行处理 支持 自行处理


样式和逻辑分离


这种方案中,组件的CSS和JS在代码层面上是分离的,开发时写在不同的文件里。在打包时生成独立的逻辑文件和样式文件。

优点:

  • 适用面广,可以支持不同的框架和技术栈。
  • 支持SSR,样式处理留给使用者。
  • 可以直接提供源码,便于主题定制。

缺点:

适合需要高适用性和灵活性的组件库。


样式和逻辑结合


这种方案将CSS和JS打包在一起,输出单一的JS文件。主要有两种实现形式:

  1. CSS in JS:样式以对象或字符串形式存在在JS中。
  2. 将CSS打包进JS:通过构建工具,将CSS文件内容注入到JS中。

优点:

  • 使用简单,只需要引入JS即可。
  • 天然支持按需加载。

缺点:

  • 需要额外的runtime,可能影响性能。
  • 难以利用浏览器缓存。
  • SSR需要框架额外支持。


样式和逻辑关联


这种方案下,虽然CSS和JS在源码层分离,但组件内会直接引用样式,且输出文件中保留import语句。

优点:

  • 使用简单,只引入JS即可。
  • 支持按需加载。

缺点:

  • 对构建和SSR都有一定要求。
  • 样式编译复杂。


设计一个组件库的 CI/CD 和发布流程。


可以参考antd

当你设计一个组件库的 CI/CD 和发布流程时,可以考虑以下步骤:


1. 分支管理:


开发者在开发新特性或修复 bug 时,应该在新的分支(通常称为 feature 分支)上进行开发。完成开发后,提交一个 pull request 到 mainmaster 分支,并进行代码审查。

git checkout -b feature/new-component
# 开发过程...
git add .
git commit -m "Add new component"
git push origin feature/new-component


2. 代码检查:


使用如 ESLint、Stylelint 等工具进行代码检查,使用 Jest 等工具进行单元测试和覆盖率检查。这些步骤可以在提交代码时或者 pull request 的过程中自动进行。

例如,可以在 package.json 中添加如下 scripts:

{
  "scripts": {
    "lint": "eslint --ext .js,.jsx,.ts,.tsx src",
    "test": "jest"
  }
}

并在 CI/CD 工具中(如 GitHub Actions、Jenkins 等)配置相应的任务:

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '14'
      - name: Install dependencies
        run: npm ci
      - name: Run lint
        run: npm run lint
      - name: Run tests
        run: npm run test


3. 版本管理:


在合并代码并发布新版本前,需要确认新的版本号,并生成相应的 changelog。可以使用如 standard-version 这样的工具自动化这个过程。

npx standard-version


4. 构建:


使用如 Webpack、Rollup 等工具进行构建,生成可以在不同环境(如浏览器、Node.js)下使用的代码。

npm run build


5. 发布:


将构建好的代码发布到 npm,同时更新文档网站。

npm publish


6. 部署:


部署到github pages或者自建服务


如何实现button按钮


image.png

import React, { CSSProperties, FC, MouseEvent, ReactNode } from 'react';
interface ButtonProps {
  lock?: boolean;
  classNames?: Record<string, string>;
  danger?: boolean;
  disabled?: boolean;
  ghost?: boolean;
  href?: string;
  htmlType?: 'button' | 'submit' | 'reset';
  icon?: ReactNode;
  loading?: boolean | { delay: number };
  shape?: 'default' | 'circle' | 'round';
  size?: 'large' | 'middle' | 'small';
  styles?: Record<string, CSSProperties>;
  target?: string;
  type?: 'primary' | 'dashed' | 'link' | 'text' | 'default';
  onClick?: (event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
  children?: ReactNode;
}
const Button: FC<ButtonProps> = ({
  lock,
  classNames,
  danger,
  disabled,
  ghost,
  href,
  htmlType = 'button',
  icon,
  loading,
  shape,
  size,
  styles,
  target,
  type = 'default',
  onClick,
  children
}) => {
  const baseClassName = 'button';
  const className = [
    baseClassName,
    type && `${baseClassName}--${type}`,
    size && `${baseClassName}--${size}`,
    shape && `${baseClassName}--${shape}`,
    disabled && `${baseClassName}--disabled`,
    danger && `${baseClassName}--danger`,
    ghost && `${baseClassName}--ghost`,
    loading && `${baseClassName}--loading`,
    lock && `${baseClassName}--lock`,
  ].filter(Boolean).join(' ');
  const handleClick = (e: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
    if (disabled) {
      e.preventDefault();
    } else if (onClick) {
      onClick(e);
    }
  };
  return href ? (
    <a
      className={className}
      href={href}
      target={target}
      onClick={handleClick}
    >
      {children}
    </a>
  ) : (
    <button
      className={className}
      type={htmlType}
      disabled={disabled}
      onClick={handleClick}
    >
      {children}
    </button>
  );
};
export default Button;


如何实现modal组件


image.png

interface IModalProps {
  afterClose?: () => void;
  bodyStyle?: CSSProperties;
  cancelButtonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
  cancelText?: ReactNode;
  centered?: boolean;
  closeIcon?: boolean | ReactNode;
  confirmLoading?: boolean;
  destroyOnClose?: boolean;
  focusTriggerAfterClose?: boolean;
  footer?: ReactNode;
  forceRender?: boolean;
  getContainer?: HTMLElement | (() => HTMLElement) | string | false;
  keyboard?: boolean;
  mask?: boolean;
  maskClosable?: boolean;
  maskStyle?: CSSProperties;
  modalRender?: (node: ReactNode) => ReactNode;
  okButtonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
  okText?: ReactNode;
  okType?: string;
  style?: CSSProperties;
  title?: ReactNode;
  open?: boolean;
  width?: string | number;
  wrapClassName?: string;
  zIndex?: number;
  onCancel?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  onOk?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  afterOpenChange?: (open: boolean) => void;
}
const Modal: React.FC<IModalProps> = ({
  children,
  title = '',
  onCancel,
  onOk,
  open = false,
  mask = true,
}) => {
  return (
    <>
      {mask && <div className="modal-mask" style={{display: open ? 'block' : 'none'}}></div>}
      {open && (
        <div className="modal" style={{display: 'block'}}>
          <h2 className="modal-title">{title}</h2>
          <div className="modal-body">{children}</div>
          <div className="modal-footer">
            <button className="modal-footer-cancel" onClick={onCancel}>
              Cancel
            </button>
            <button className="modal-footer-ok" onClick={onOk}>
              OK
            </button>
          </div>
        </div>
      )}
    </>
  );
};
Modal.info = function(props: IModalProps) {
  const div = document.createElement('div');
  document.body.appendChild(div);
  function remove() {
    ReactDOM.unmountComponentAtNode(div);
    document.body.removeChild(div);
  }
  function onCancel(e: React.MouseEvent<HTMLButtonElement>) {
    if (props.onCancel) {
      props.onCancel(e);
    }
    remove();
  }
  function onOk(e: React.MouseEvent<HTMLButtonElement>) {
    if (props.onOk) {
      props.onOk(e);
    }
    remove();
  }
  ReactDOM.render(
    <Modal {...props} onCancel={onCancel} onOk={onOk} open={true} />,
    div
  );
};

目录
相关文章
|
4月前
|
消息中间件 缓存 Java
面试官:你的项目有哪些难点?
面试官:你的项目有哪些难点?
291 2
|
Java 调度
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day13。
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day13。
84 0
|
数据采集 缓存 前端开发
金九银十,带你复盘大厂常问的项目难点(二)
金九银十,带你复盘大厂常问的项目难点
343 0
|
移动开发 前端开发 JavaScript
金九银十,带你复盘大厂常问的项目难点(一)
金九银十,带你复盘大厂常问的项目难点
287 0
|
前端开发 JavaScript 小程序
金九银十,带你复盘大厂常问的项目难点(四)
金九银十,带你复盘大厂常问的项目难点
107 0
|
设计模式 算法 Java
内卷严重~面试八股文层出不穷!唯2023版Java复盘手册有复盘之路
最近有不少小伙伴表示内卷实在是太严重了,不少程序员都有辞退失业或跳槽的想法,今天给大家分享的这份手册可以快速帮大家找到正确思路,无论你是失业还是跳槽都推荐你看一看,这份手册涵盖了市面上90%的Java面试内容,十分全面! 不到最后一刻千万不要放弃,也不要灰心,哪怕到十一月还没有拿到offer也没关系,殊不知等到年底补录的时候也是一个非常容易进大厂拿offer的机会。
|
Java 索引
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day14
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day14
143 0
|
SQL 搜索推荐 Java
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day08
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day08
140 0
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day08
|
Java 程序员
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09
126 0
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09
|
缓存 Oracle NoSQL
【面试1v1实景模拟】Spring事务经典面试场景,全方位解读面试官心理,助你面试入坑~
【面试1v1实景模拟】Spring事务经典面试场景,全方位解读面试官心理,助你面试入坑~
163 0
下一篇
DataWorks