循环引用的坑点

在组件开发中引用方式写法如下:

import Popup from '../Popup'
import List from '../List'

console.log(Popup, List) // f Popup f List

而不要使用如下写法:

import { Popup, List } from '../index' // 这样子引用会带进当前开发的组件本省
console.log(Popup, List) // undefined undefined

原因是因为下面这种写法会带进当前开发的组件本身从而导致循环引用。循环引用会导致在编译态输出的结果为 undefined。

为什么要受控模式

尽管受控模式相对非受控模式会多传入 valueonChange 属性, 但它能有效避免组件库中碰到的一些反模式的坑。

在 Dan 的 编写具有弹性的组件一文中写到如下几个原则:

  • 不要将 props 赋给 state

理由: 如果不结合 componentDidUpdate 使用会无法读取到更新的数据; 结合了 componentDidUpdate 在其进行 setState 又会进行多一次的 render。

解决方案

  • 直接在 render 中获取父组件传下来的数据; 结合 useMemo 进行优化;
  • 只使用受控组件或者只使用非受控组件;

针对一些复杂数据使用 useMemo 以及 memorize-one 进行处理。或者 hooks 中在 render 内使用 useMemo; class 中在 render 内使用 memorize-one

关于 Typescript 与 DefaultProps 的冲突

class

针对 TypeScript 中的 interface 中必填可填(是否带 ? 可选)与 React 中的 defaultProps 是不兼容的。造成的影响是 defaultProps 里若是不填写完整 TypeScript 中的 interface 属性则会标红。为了解决这个现象解决方案如下:

interface {
  mustFillIn: number,
  mayFillWithDefaultValue: number, // 可选的带默认值的不用带上问号
  mayFillWithoutDefaultValue?: number // 可选的不带默认值的带上问号
}

TypeScript 中书写 React 时可以这样:

class Divider extends React.Component<DividerProps, any> {

  static defaultProps: Pick<DividerProps, 'mayFillWithDefaultValue' | 'mayFillWithoutDefaultValue'> = {
    mayFillWithDefaultValue: 12345
  }

  render() {
    return (
      <div>demo</div>
    )
  }
}
  • Pick 针对有必传值的场景做区分
  • interface 中的 ? 针对有默认值和无默认值做区分

hooks

组件异步的设计

  • 方式一: 使用方通过返回 Promise 来告知组件已完成异步操作;
const demo = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('模拟异步接口')
      resolve()
    }, 2000)
  })
}
  • 方式二: 使用方通过调用 done() 来告知组件已完成异步操作;(组件内完成对 Promise 的封装)
const demo = (done) => {
  setTimeout(() => {
    console.log('模拟异步接口')
    done()
  }, 2000)
}

从组件设计的角度来说, 方式一对组件内部的维护更加友好; 从使用者的角度来说, 方式二的使用更为便捷, 但是重构的成本会变大。

如下是针对方式二的组件的封装方式。

// 此处 fn 为外面传入的 demo 函数
if (fn.length === 1) {
  new Promise((resolve: any) => {
    fn(resolve)
  }).then(() => {
    ...
  })
}

组件样式覆盖的问题

背景: 业务 a 使用了组件包 b 以及工具包 c, 其中 c 也依赖了旧版本的 b。此时业务 a 中会有两个组件包 b 的样式, 此时会碰到修改了组件包 b 的样式, 而样式被老版本的 b 覆盖的情况。这个时候有什么解决方法呢?

  • 方案一: 打包给样式带上版本号;
  • 方案二: 让工具包 c 把组件包 b 放在 pureDependencies 中, 业务方的 dependency 统一维护组件包 b;

业务中同时使用 Button 组件(包含 Icon 组件) 与 Icon 组件如何避免 Icon 组件的样式打入两份?

方法: src 目录下的组件中的 style 文件中建立 index.tsx 文件

开源计划

  • 完善 mobile 组件在 pc 端站点的浏览效果;
  • beast-mobile 语言包支持、为后续站点切换双语环境做准备;
  • beast-mobile 中、英文文章简版输出, 调研专栏环境社区的创建(Medium、Twitter、知乎、Github、StackOverflow、SegmentFault);

组件通用 API 设计

  • onDisabled: (record: T) => boolean
    • ListView、Checkbox 组件公用;
    • 场景: 提供给业务更加充分的控制权

link

组件

  • scss
    • 语义化样式模板
    • 关联计算逻辑
  • post-install
  • e2e 测试

  • random notes

    • 沉淀一些常⻅的测试模式, 并形成易用的 utils (mock & helper) 一些参考关键词 : date, timer, async & promise, random, user-agent, browser feature detect, console, process.env, jsdom layout
    • 科学使用 snapshot
    • jest 的 test.todo 挺不错
    • 通过写测试的过程, 反过来思考组件发现遗漏 Edge cases, 实现 是否合理 可能 react-testing-library 会更好 (enzyme 可直接访问 state, 容易写出一些没意义的测试)
  • sass 技巧
    • 沉淀 function utils, 并统一维护 • 善用 list & map 维护枚举型配置 • 善用 interpolation, 让同质化分支模版化 • 逻辑关系组织 : 如 “颜色管理方案”
  • plop
    • 将重复性繁琐工作打包
    • 善用内容型模板
      • 创建一个 组件子包 • 创建一个 React 组件 (class or functional) • 创建一个完整的 demo ⻚面 • 创建一个单独的 demo case • 将组件追加到某个合包里
  • 如何统计组件使用量

    DRY 原则: Don't Repeat Youself

Some of these CSS properties have shorthand versions that use the namespace as the property name. For these, you can write both the shorthand value and the more explicit nested versions.

.info-page {
  margin: auto {
    bottom: 10px;
    top: 2px;
  }
}

组件属性间尽量减少耦合关系

比如添加了 A 属性 B 属性在某些情况会失效。又或者 C 属性必须在 D 属性存在的时候才生效。

比如以 Upload 组件为例: 背景逻辑是: 最大上传图片数量是 2, 当上传了 2 张图片以后就不显示继续上传了。

showAdd = () => {
  const { selectable, maxLength, files } = this.props
  if (maxLength !== undefined) {
    if (files.length >= maxLength) {
      return false
    }
    return true
  }
  return selectable
}

业务方举了反例: 例如有些商品图片是标品,图片是平台自己控制的,也许不满足最大数,但是不允许商家去修改的。 配置了 maxLength={10}, 图片数量只有 3 张时, 不显示增加按钮的需求当前就不能实现。