React组件组合不香吗?

407 阅读2分钟

前言

受控组件需要主动维护一个内部的state状态,但是有些时候value和onChange的维护十分麻烦,尤其是需要维护多套状态值的情况下,比如form表单,tabs选项卡等等。对于他们的每一个单项的组件来说,完全没有必要单独维护自己的state,所以为什么不试试组件组合了,亲~

举个栗子

一个受控的Tabs组件可能是这么写的。。。

interface TabBarProps {
  /**
   * 标签页
   * @type {string[]}
   * @memberof TabBarProps
   */
  data: string[]
  /**
   * 选中
   * @type {number}
   * @memberof TabBarProps
   */
  selectedIndex: number
  /**
   * 改变事件 如果selectedIndex 未改变不触发
   * @memberof TabBarProps
   */
  onChange?: (index: number) => void
}
...
 render() {
    const { data, selectedIndex } = this.props;
    return <View className={styles.content}>
        {data.map((value, index) =>
          <View key={value + index} className={classNames
          (styles.item, selectedIndex === index ? styles.selected : {})}
            onClick={() => this.onClick(index)}>
            {value}
          </View>)}
      </View>
  }
  private onClick(index) {
    const { selectedIndex, onChange } = this.props;
    if (selectedIndex !== index) {
      onChange && onChange(index);
    }
  }

对于上述组件来说,selectedIndex需要自己维护吗,在开发中我们确实需要知道selectedIndex的变化来请求数据或者其他的操作,但是点击切换的动作我们希望由组件自己完成。其次tabs组件的自由度是非常高的,我们希望不因为一些UI上的变动去修改组件,换言之,组件只帮你处理逻辑问题。

Tab+TabItem 的组合使用

//Tab
export interface TabProps extends React.PropsWithChildren<object> {
    /**
     * change事件
     */
    onChange?: (index: number) => void
}

export default memo(function Index(props: TabProps) {
    const [activeIndex, setActiveIndex] = useState(0);
    function tabsChange(index: number) {
        setActiveIndex(index)
        props.onChange && props.onChange(index)
    };
    const newChildren = React.Children.map(props.children, (child: any, index) => {
        if (child.type) {
            return React.cloneElement(child, {
                active: activeIndex === index,
                onClick: () => tabsChange(index)
            })
        } else {
            return child
        }
    })
    
    return (
        <div>
            {newChildren}
        </div>
    )
})
//TabItem
export default memo(function Index(props: any) {
    const { active, onClick, children } = props;
    const style = {
        color: active ? '#0156A9' : '#000'
    }

    return (
        <div style={style} onClick={onClick} >
            {children}
        </div>
    )
})

对于TabItem组件或者Tab的子组件来说,只需要具备active属性和onClick事件即可,所有的状态逻辑都提取在Tab组件中,子组件是纯傻瓜式组件,通过React.Children.map来对props.children进行遍历。
所以组合组件的使用更加灵活,不会因为UI上的变动去修改组件,同时Tab组件本身也维护了自己的state,通过onChange事件将变化的index传递出去。

<Tab>
    <TabItem>第一</TabItem>
    <TabItem>第二</TabItem>
    <TabItem>第三</TabItem>
</Tab>

总结

虽然大部分时候都推荐使用受控组件,但是也需要避免产生不必要的state,相较于Context,React.children提供了一种不打破组件props传递的很好的实践方式。