专栏名称: 寒东设计师
前端工程师
目录
相关文章推荐
于小戈  ·  前顶流小花,被爆做三?! ·  昨天  
莓辣MAYLOVE  ·  为什么说和喜欢的人睡觉是大补? ·  2 天前  
槽值  ·  杀疯了的“三亚平替”,挤满东北人 ·  2 天前  
槽值  ·  网易沸点工作室多岗位实习生招聘中 ·  2 天前  
于小戈  ·  某影后,新恋情太争气! ·  3 天前  
51好读  ›  专栏  ›  寒东设计师

如何编写更好的React组件

寒东设计师  · 掘金  ·  · 2019-07-18 02:27

正文

阅读 111

如何编写更好的React组件

前言

不知道有没有同学跟我一样,学习了很多 react 源码,却还是写不出更优雅的代码。我们知道了 dom-diff 原理,了解 setState 是如何更新状态的,然后呢?还是会写出难以维护的代码甚至bug一堆。

很多时候你写出bug不是因为你不懂 dom-diff ,而是因为你的属性和状态胡乱命名。

很多时候你的代码难以维护,也不是因为你不懂 dom-diff ,而是你的组件划分太不合理了。

所以我开始读一些应用层框架的源码,也希望把学习的过程和大家分享。

为什么是antd-design-mobile

antd-design-mobile 是一个不错的学习项目,可以学习到如何更好的使用 React TypeScript less ,也可以学到优秀的团队是如何思考 可维护性 可扩展性 组件化 的。

antd-design-mobile 项目集成了一些编译工具,并且把各个组件分散在不通的项目中,看起来比较累。所以我创建了一个相对简单且更适合阅读的项目(不含动画且重新设计了Ui),看原项目吃力的同学可以看这个:

项目地址

github.com/alive1541/d…

预览地址

http://39.100.100.217

预览二维码

因为没有绑定域名,微信打开会有问题,可以使用支付宝等其他应用

源码分析——以<Modal>为例

项目结构

入口

先看看入口文件里是什么内容

可以看到,除了这个提示按需引入的警告之外,其实就是导出了当前目录下的所有组件。下面我以Modal组件为例子继续分析。

<Modal>组件解析

先看下 Modal 组件整体的结构:

接下来详细看一下各个组件内部的细节:

Modal组件

Modal 组件是根组件,也就是默认导出的组件。下图从下往上看,文件导出的是 Modal 组件,它继承成了ModalComponent抽象类,并传入了ModalProps泛型接口。

如果对TypeScript不太了解可以看 www.tslang.cn/docs/handbo… ,通读一下“手册指南”这一章基本就可以看懂Ts代码了

从接口定义上可以看出,这个组件允许接受 prefixCls transitionName 等属性,并且需要挂载三个静态方法 alert prompt operation

组件内部有 cls 和一个 renderFooterButton 方法,其中 cls 结合 prefixCls 处理了类名(prefixCls允许用户自定义前缀),以方便用户统一处理自定义的样式。而 renderFooterButton 方法用来渲染弹窗中的按钮。

接下来就是 alert prompt operation 三个静态方法(官网中有相应的使用方法)。他们的作用是通过方法唤出 Modal 组件,以alert为例:

import { Modal } from 'antd-mobile';

const alert = Modal.alert;

const App = () => (
    <Button
      onClick={() =>
        alert('Delete', 'Are you sure???', [
          { text: 'Cancel', onPress: () => console.log('cancel') },
          { text: 'Ok', onPress: () => console.log('ok') },
        ])
      }
    >
      confirm
    </Button>
)

复制代码

从接口中还能看到, alert prompt operation 这三个方法都有一个函数类型的返回值 close ,这个方法的作用是关闭当前 Modal 。逻辑也比较简单,这个静态方法也比较简单,就是创建一个div元素然后插入到body中,再把Modal组件插入这个 div 中,最后导出一个移除这个 div 的close方法。核心代码如下:

export default function operation(){
    ...
    
    const div = document.createElement("div");
    document.body.appendChild(div);
    
    ReactDOM.render(
        <Modal
          visible
          operation
          transparent
          prefixCls={prefixCls}
          onClose={close}
          footer={footer}
          className="d-modal-operation"
          platform={platform}
          wrapProps={{ onTouchStart: onWrapTouchStart }}
        />, 
        div
    );
    
    function close() {
        ReactDOM.unmountComponentAtNode(div);  //销毁指定容器内的所有React节点
        if (div && div.parentNode) {
          div.parentNode.removeChild(div);
        }
    }
    
    return { close }
}
 
复制代码

DialogWraaper

这个组件被放在了 react-component 这个库里,这个库是一个基础组件库,antd和antd mobile这两个项目都依赖了它。
这层组件很轻,并没有处理逻辑,只是创建了一个portal,同时对react16以下的版本做了兼容,兼容写法是这样的:

...

const IS_REACT_16 = !!(ReactDOM as any).createPortal;
componentDidUpdate() {
    if (!IS_REACT_16) {
      this.renderDialog();
    }
}
renderDialog() {
    ReactDOM.unstable_renderSubtreeIntoContainer(
      this,
      this.getComponent(),
      this.getContainer()
    );
}
render() {
    const { visible } = this.props;
    if (IS_REACT_16 && visible ) {
      return ReactDOM.createPortal(this.getComponent(), this.getContainer());
    }
    return null as any;
 }
复制代码

Dialog

这个组件主要做了三件事,1、渲染遮罩 2、渲染弹框 3、处理关闭事件 这里渲染了弹出框的元素,代码如下:

...
onMaskClick = (e: any) => {
    if (e.target === e.currentTarget) {
    //e.target和e.currentTarget的区别参考https://www.jianshu.com/p/1dd668ccc97a
      this.close(e);
    }
};
close = (e: any) => {
    if (this.props.onClose) {
      this.props.onClose(e);
    }
};
render() {
    const { props } = this;
    const { prefixCls, maskClosable } = props;
    return (
      <div>
        {this.getMaskElement()}   //渲染遮罩
        <div
          className={`${prefixCls}-wrap ${props.wrapClassName || ""}`}
          onClick={maskClosable ? this.onMaskClick : undefined}
          {...props.wrapProps}
        >
          {this.getDialogElement()}   //渲染弹框
        </div>
      </div>
    );
  }
复制代码

getMaskElement getDialogElement 两个方法渲染元素时,元素外层包裹了 LazyRender 组件,意思很好理解,就是避免不必要的渲染。代码也很简单,就是在 shouldComponentUpdate 中做是否更新的判断。

export default class LazyRender extends React.Component<lazyRenderProps, any> {
  shouldComponentUpdate(nextProps: lazyRenderProps) {
    return






请到「今天看啥」查看全文