文章

React父子组件实现:Form(Item)/Tab(Item)

React父子组件实现:Form(Item)/Tab(Item)

Form(Item)

参考:https://juejin.cn/book/6945998773818490884/section/6950659615675645990

示例demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export default  () => {
    const form =  React.useRef(null)
    const submit =()=>{
        /* 表单提交 */
        form.current.submitForm((formValue)=>{
            console.log(formValue)
        })
    }
    const reset = ()=>{
        /* 表单重置 */
        form.current.resetForm()
    }
    return <div className='box' >
        <Form ref={ form } >
            <FormItem name="name" label="我是"  >
                <Input   />
            </FormItem>
            <FormItem name="mes" label="我想对大家说"  >
                <Input   />
            </FormItem>
        </Form>
        <div className="btns" >
            <button className="searchbtn"  onClick={ submit } >提交</button>
            <button className="concellbtn" onClick={ reset } >重置</button>
        </div>
    </div>
}

Form

Form 目标:

  • 基于class component实现
  • 负责管理 form store(表单数据存储对象)
  • 能够识别 children 上的 FormItem 组件,并且只渲染它们
  • 内置一些方法,外部能通过 ref 拿到,并且进行表单提交、验证等操作

实现:

  • 内置一个object,或者搞个专门的class都行
  • 通过FormItem的displayName或者其它标识,来判断是否为 FormItem;

    并且重写 children 节点,只拿出来 FormItem,并且将改变 FormItem 绑定的表单字段的值的方法传给 FormItem。

  • 基于 class 实现的,外界使用时ref可以直接拿到 class 上的方法。内部支持表单提交、验证等操作即可。

FormItem

FormItem 目标:

  • 基于class component实现
  • 能识别内置的 Input / Checkbox / Radio 等组件,并且在状态改变时,将改变传给 Form

实现:

  • 给 Input / Checkbox / Radio 等组件挂上 dispalyName 标识属性

Tab(Item)

示例

1
2
3
4
5
6
7
8
9
10
11
<TabButtonList
  onChange={value => {
	  setTab(value);
	  submit();
}}
  activeKey={tab}
>
  <TabButton key={TAB_TYPE.ALL}>{TAB_MAP[TAB_TYPE.ALL]}</TabButton>
  <TabButton key={TAB_TYPE.INWORK}>{TAB_MAP[TAB_TYPE.INWORK]}</TabButton>
  <TabButton key={TAB_TYPE.HASWORK}>{TAB_MAP[TAB_TYPE.HASWORK]}</TabButton>
</TabButtonList>

区别

和Form、FormItem相比,行为没有那么复杂,同时也不需要像FormItem那样,内部还得判断Input、Select等组件。

TabItem实现

  1. 响应点击选中样式
  2. 相应点击事件
  3. 需要通过 displayName 挂入标识,方便父级 TabButtonList 判断

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React from 'react';
import classnames from 'classnames';
import styles from './index.module.scss';
import { TabButtonProps, TabButtonListProps } from './types';

const TYPE_NAME = {
  tabButton: '__tab_button',
} as const;

/**
 * 用于标签切换的按钮
 */
export const TabButton = React.memo((props: TabButtonProps) => {
  const { children, clicked } = props;

  /**
   * 渲染子元素
   * @returns
   */
  const renderChildren = () => {
    if (typeof children === 'function') {
      return children();
    }
    return children;
  };

  return (
    <div
      className={classnames(styles.tabButton, {
      [styles.active]: clicked,
    })}
      onClick={(props as any)?.onClick || (() => {})}
    >
      {renderChildren()}
    </div>
  );
});
TabButton.displayName = TYPE_NAME.tabButton;

TabButtonList

  1. 遍历所有的子元素,挑选 TabButton 组件,跳过其他组件
  2. 根据外面传入的 activeKey :判定当前的 TabButton 组件是否为选中状态,并且选中组件跳过 click 回调

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * 标签切换的按钮容器。可自动识别底层的切换按钮组件。
 */
export const TabButtonList = React.memo((props: TabButtonListProps) => {
  const { className, children, activeKey, onChange, style } = props;
  /**
   * 动态计算子组件
   */
  const childList: ReturnType<typeof React.cloneElement>[] = [];
  React.Children.forEach(children, (child: any) => {
    if (child.type.displayName === TYPE_NAME.tabButton) { // 判断是否为 TabButton 组件
      const { props: { children: cc }, key: cKey } = child; // 读取子组件的key,以及从props上读取子组件的children
      const childCopy = React.cloneElement(child, {
        key: cKey,
        clicked: cKey === activeKey,
        onClick: () => {
          if (typeof onChange === 'function' && cKey !== activeKey) { // 对于已选中的标签,不触发click回调
            onChange(cKey);
          }
        },
      }, cc);
      childList.push(childCopy);
    }
  });

  return (
    <div className={className} style={style}>
      {childList}
    </div>
  );
});
本文由作者按照 CC BY 4.0 进行授权