likes
comments
collection
share

浅谈 React 组件设计思路和范式

作者站长头像
站长
· 阅读数 19

概述

截止目前为止,React 在前端的生态中,依然是非常重要的一个组成部分,工作这些年经历过大大小小的项目,看到过种种形形色色的代码 和组件构建范式。见多了 这个人啊就有想法了💡 ,量变然后质变嘛,本文将讨论一下,一些市面上非常流行的 React组件的设计范式,以及通过一个 案例,来对比和分析 Hoc模式 vs 自定义Hooks模式 鄙人才学疏浅,有不对的地方,请大家多多指点。

这个文章的来源,主要是团队内部的分享,我们的前端架构师 推崇Hoc (他认为基于这种的设计模式 会让代码最终 以一种 “洋葱 🧅 ” 一层一层的 呈现出来便于维护;但是对于这个观点 我并不是 很待见 🤣🌚他应该看不到我的文章 )

本文代码工程地址

一些观点和概念

正式开始之前我们先来看看 React的组件 以及基于其上一些 场景的组件设计模式有哪些。

注意这里我将默认同学们都有非常不错的react 使用经验哈,我在这个小节中会快速带过

React中组件的定义有哪些方式?

从官方文档来看,目前React组件的定义有下面的几种方式

  • Class 组件:使用 class 关键字定义组件,并扩展 React.Component。
  • Function 组件:使用普通的 JavaScript 函数定义组件。
  • Hooks:使用特殊的函数,称为 Hooks,在 Function 组件中管理状态和其他 React 特性。

以封装Button 为例子

import React from 'react';

// class
class Button extends React.Component {
  render() {
    return (
      <button>{this.props.children}</button>
    );
  }
}

// Function
const Button = ({children}) => {
  return (
    <button>{children}</button>
  );
};

// Hooks
const Button = ({children}) => {
  const [state,setState] = useState();
  return (
    <button>{children}</button>
  );
};

export default Button;

请注意 在上面的示例中,Function 组件和 Hooks 是相同的,因为在这个例子中,Button 组件不需要管理状态或其他 React 特性。但是,如果要添加状态或其他功能,则可以使用 Hooks 来实现。

基于以上三种基础的组件定义方式,我们常见的 组件设计模式 如下

  • Render props 模式
  • Function as Child Components(FACC
  • Hoc模式
  • 自定义hooks 模式(Hooks的衍生)
  • Context API
  • State reducer (不常用)
  • State reducer + Context API (常用)

下面是以封装 Toggle 组件为例子

/** 
Render props 它是一种以函数作为参数传递数据的方法,
允许组件在组件树中进行共享数据,而不需要将该状态传递到组件的层次结构中。
例如,您可以使用一个组件来实现计数器状态,然后使用render props传递状态和方法。
*/
import React, { Component } from 'react';

class Toggle extends Component {
  state = {
    on: false,
  };

  toggle = () => {
    this.setState(({ on }) => ({ on: !on }));
  };

  render() {
    return this.props.render({
      on: this.state.on,
      toggle: this.toggle,
    });
  }
}

const MyComponent = () => (
  <Toggle render={({ on, toggle }) => (
    <div>
      {on ? 'The button is on' : 'The button is off'}
      <button onClick={toggle}>Toggle</button>
    </div>
  )} />
);


/*
FACC模式  当有了hooks 这些代码 完全没必要
(FACC)是一种React组件设计模式,它允许父组件将一个函数作为子组件传递,
这个函数将返回组件需要呈现的内容。这样做的目的是为了让子组件具有更多的灵活性
,并且能够方便地适应不同的场景。
*/
import React, { Component } from "react";

class Toggle extends Component {
  state = {
    on: false,
  };

  toggle = () => {
    this.setState((state) => ({ on: !state.on }));
  };

  render() {
    return this.props.children({ on: this.state.on, toggle: this.toggle });
  }
}

export default Toggle;


/** Hoc
高阶组件是一个函数,接收一个组件并返回一个新组件,
它可以重用和扩展现有组件的功能。例如,您可以使用
高阶组件来实现一个组件的认证,在渲染之前检查用户是否已登录。
*/
import React from 'react';

const withToggle = (Component) => (props) => {
  const [on, setOn] = React.useState(false);
  const toggle = () => setOn(!on);

  return <Component on={on} toggle={toggle} {...props} />;
};

const MyComponent = (props) => (
  <div>
    {props.on ? 'The button is on' : 'The button is off'}
    <button onClick={props.toggle}>Toggle</button>
  </div>
);

const EnhancedMyComponent = withToggle(MyComponent);

/* 自定义hooks
通过创建自定义Hooks,可以使代码更简洁,同时使功能更易复用。
*/
import React, { useState } from 'react';

function useToggle(initialValue = false) {
  const [on, setOn] = useState(initialValue);
  const toggle = () => setOn(!on);

  return [on, toggle];
}

const MyComponent = () => {
  const [on, toggle] = useToggle();

  return (
    <div>
      {on ? 'The button is on' : 'The button is off'}
      <button onClick={toggle}>Toggle</button>
    </div>
  );
};


/* Context API 模式
通过使用Context API,可以在应用程序的不同部分共享状态,而不必通过额外的props传递。
*/
import React, { useState, createContext } from "react";

const ToggleContext = createContext();

const ToggleProvider = ({ children }) => {
  const [on, setOn] = useState(false);

  const toggle = () => setOn(!on);

  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
};

const useToggle = () => {
  const context = React.useContext(ToggleContext);
  if (!context) {
    throw new Error("useToggle must be used within a ToggleProvider");
  }
  return context;
};

export { ToggleProvider, useToggle };

/* 
state reducer 
这玩意提供了 一中非常简化的redux 实现
*/
import React, { useReducer } from "react";

const toggleReducer = (state, action) => {
  switch (action.type) {
    case "toggle":
      return { on: !state.on };
    case "reset":
      return { on: false };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};

const Toggle = ({ children }) => {
  const [state, dispatch] = useReducer(toggleReducer, { on: false });

  return children({
    on: state.on,
    toggle: () => dispatch({ type: "toggle" }),
    reset: () => dispatch({ type: "reset" }),
  });
};

export default Toggle;


/*
下面的这种
结合 context + state reducer 模式非常的常见 
通过结合Context API和State Reducer模式,您可以实现全局状态管理,
并且可以方便地访问和更新状态,而不需要通过props传递。
*/

// 1. 首先,您需要创建一个Context来存储当前的状态,并且定义一个reducer来更新状态
import React, { useReducer, createContext } from 'react';

const ToggleContext = createContext();

function toggleReducer(state, action) {
  switch (action.type) {
    case 'toggle':
      return { on: !state.on };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function ToggleProvider({ children }) {
  const [state, dispatch] = useReducer(toggleReducer, { on: false });
  const value = { state, dispatch };
  return <ToggleContext.Provider value={value}>{children}</ToggleContext.Provider>;
}


// 2. 然后,您可以创建一个组件,使用Context API和useReducer hooks来访问和更新状态:
import React, { useContext } from 'react';

function ToggleButton() {
  const { state, dispatch } = useContext(ToggleContext);
  return (
    <button onClick={() => dispatch({ type: 'toggle' })}>
      {state.on ? 'Turn off' : 'Turn on'}
    </button>
  );
}

function ToggleDisplay() {
  const { state } = useContext(ToggleContext);
  return <div>The toggle is {state.on ? 'on' : 'off'}</div>;
}

// 3. 最后,您可以将这些组件作为整个应用程序的一部分使用:
function App() {
  return (
    <ToggleProvider>
      <ToggleButton />
      <ToggleDisplay />
    </ToggleProvider>
  );
}

好了,上述就是一些基础的一些概念和模式的介绍了,下面的例子 主要是 想通过一个 案例 ,来分析 和对比 自定义hooks 和 hoc 模式的之间的 实际上手体验和最终的代码效果。

实际例子

接下来我们将围绕构建一个Button以及维护其迭代的案例 来实践一下

用Hoc的模式来构建

1.基础功能

作为一个最基础的button 首先你得像模像样

export interface InterButtonProps {
  children?: React.ReactNode;
  type?: 'primary' | 'ghost' | 'link' | 'wanning';
  onClick?: () => void;
  [key: string]: any;
}

const Button: FC<InterButtonProps> = (props) => {
  const { onClick = () => {}, children, type = 'ghost' } = props;

  const csnames = cn('me-button', {
    ghost: type === 'ghost',
    primary: type === 'primary',
    link: type === 'link',
    wanning: type === 'wanning',
  });

  return (
    <button className={csnames} onClick={onClick}>
      {children}
    </button>
  );
};

2.加loaidng效果


// 2.新增一个 loading 效果
interface InterWithLadingFunc<T> {
  (loadingObj: { loading?: boolean; loadingText?: string }): (
    InnerComponent: T
  ) => FC<T>;
}
const withLoading: InterWithLadingFunc<any> =
  (loadingObj) => (InnerComponent: any) => {
    const ComponentWithLoading: React.FC<any> = (props) => {
      const { loading = false, loadingText = 'loading...' } = loadingObj;
      // 直接计算高度 去做一个mask
      const [style, setStyle] = useState<Record<string, any>>({});
      const _loaidngWrapRef = useRef<HTMLDivElement>(null);

      useLayoutEffect(() => {
        if (!_loaidngWrapRef.current) {
          return;
        }
        const wh = getDomWH(
          _loaidngWrapRef.current.lastChild as unknown as HTMLElement
        );

        setStyle({
          width: wh.w,
          height: wh.h,
        });
      }, []);

      return (
        <div
          ref={_loaidngWrapRef}
          className={cn({
            loading: loading,
          })}
          style={style}
        >
          <div
            className={cn('loading-content', {
              'no-loading': !loading,
            })}
          >
            <span>{loadingText}</span>
          </div>
          <InnerComponent {...props} />
        </div>
      );
    };

    ComponentWithLoading.displayName = 'withLoadingHocButton';
    return ComponentWithLoading;
  };


3.加tracking功能

interface InterWithTracking {
  (trackingFunc: Function): (InnerComponent: any) => FC;
}
const withTracking: InterWithTracking = (trackingFunc) => (InnerComponent) => {
  const ComponentWithTracking: React.FC<any> = (props) => {
    return (
      <>
        {/* 直接在上面代一层 */}
        <InnerComponent
          {...props}
          onClick={(e: any) => {
            trackingFunc();
            props.onClick && props.onClick(e);
          }}
        />
      </>
    );
  };
  ComponentWithTracking.displayName = 'withTrackingHocButton';

  return ComponentWithTracking;
};

4.加一个hover弹出poper功能

interface InterwWtHoverMenue {
  (items: Array<string>, cb: (item: string) => void): (
    InnerComponent: any
  ) => FC;
}
const witHoverMenue: InterwWtHoverMenue = (items, cb) => (InnerComponent) => {
  const ComponentWithHoverMenue: React.FC<any> = (props) => {
    // 解下来如果什么都不加 那么所有的功能都没有问题 ,但我们需要加hover功能
    const [hover, setHover] = useState(false);
    const onMouseEnterHandle = () => {
      setHover(true);
    };
    const onMouseLeaveHandle = () => {
      setHover(false);
    };

    return (
      <Popper
        isShow={hover}
        setIsShow={setHover}
        container={() => document.querySelector('#root') || document.body}
        anchorEl={(propsInner) => {
          return (
            <div
              style={{ position: 'absolute' }}
              {...propsInner}
              onMouseEnter={debounce(onMouseEnterHandle)}
              onMouseLeave={debounce(onMouseLeaveHandle)}
            >
              <InnerComponent {...props} />
            </div>
          );
        }}
      >
        {/* 遵循MUI 规范设计的 popper 和antd 不一样 需要区分哈 */}
        {(propsInner) => {
          return (
            <div {...propsInner}>
              {items.map((item, idx) => (
                <h2 key={idx} onClick={() => cb(item)}>
                  {item}
                </h2>
              ))}
            </div>
          );
        }}
      </Popper>
    );
  };
  ComponentWithHoverMenue.displayName = 'ComponentWithHocHoverMenue';

  return ComponentWithHoverMenue;
};

5.加一个hover弹出poper功能点击弹出modal 功能

 如果按照 上面的逻辑 你的代码也许会变成这样的风格,
 (注意 这里有两种实现方式 
 
  1. flowRight 的形式 。你无法拿到 其它 hoc 中的数据 以及代理它们的其中任何一个。
  通过在父组件分发 状态共享给其它的hoc(可以通过props 
  也可以不通过 props 这里为了简单 我还是 通过props) 传递 rxjs subject 【可读性一般】

  2. 构造高阶 fucntion 扩展其它hoc 【可读性差】

// 方法1 father 要传递 rxjs / state 进行有控制
interface InterWithMenueModal {
  ($menueEvent: Subject<InterMenueEvent$>): (InnerComponent: any) => FC;
}
const withMenueModal: InterWithMenueModal =
  ($menueEvent) => (InnerComponent) => {
    // hoc 之间的数据传递 也只能是 hoc1 => father => hoc2 | hoc3
    const ComponentWithMenueModal: FC<any> = (props) => {
      const [modalShow, setModalShow] = useState(false);
      const [modalInfo, setModalInfo] = useState('');

      useEffect(() => {
        let $subject: any;
        if ($menueEvent) {
          $subject = $menueEvent.subscribe((value) => {
            switch (value.type) {
              case 'openModal':
                setModalShow(true);
                setModalInfo(value.payload);
                break;

              default:
                break;
            }
          });
        }
        return () => {
          $subject && $subject.unsubscribe();
        };
      }, []);
      return (
        <>
          <Modal isShow={modalShow} setModalShow={setModalShow}>
            <div>
              <h2>withMenueModal = {modalInfo}</h2>
            </div>
          </Modal>
          <InnerComponent {...props} />
        </>
      );
    };

    ComponentWithMenueModal.displayName = 'ComponentWithMenueModal';
    return ComponentWithMenueModal;
  };

// 方法2 包一下 hoc => hoc => InnerComponent
const withMenueModal2 = (HocBase: any) => (items: Array<string>, cb: any) => {
  return (InnerComponent: any) => {
    const Enhancerhoc: FC = (props) => {
      const [modalShow, setModalShow] = useState(false);
      const [modalInfo, setModalInfo] = useState('');

      const HocRBase = () => {
        const R = HocBase(items, (item: string) => {
          cb(item);
          setModalShow(true);
          setModalInfo(item);
        })(InnerComponent);
        return <R {...props} />;
      };

      return (
        <>
          <HocRBase />
          <Modal isShow={modalShow} setModalShow={setModalShow}>
            <div>
              <h2>withMenueModal = {modalInfo}</h2>
            </div>
          </Modal>
        </>
      );
    };
    Enhancerhoc.displayName = 'Enhancerhoc';
    return Enhancerhoc;
  };
};

细细想来 如果说 是在class 时代,这种Hoc 确实是一个很ok的设计思路 我举一个例子,比如loading 这个功能 如果一起没有hooks 那么它就会长这样,但 ... 我们既然有了hooks 那么,我们是否可以 舍弃用 这种 令人丧心病狂的 组件构建模式呢?


const withLoadingClass = (loadingObj: any) => (InnerComponent: any) => {
  class ComponentWithLoadingClass extends React.Component<
    any,
    Record<string, any>
  > {
    constructor(porps: any) {
      super(porps);
      this.state = {
        style: {},
      };
    }

    _loaidngWrapRef = createRef<HTMLDivElement>();

    componentDidMount() {
      if (!this._loaidngWrapRef.current) {
        return;
      }
      const wh = getDomWH(
        this._loaidngWrapRef.current.lastChild as unknown as HTMLElement
      );

      this.setStyle({
        width: wh.w,
        height: wh.h,
      });
    }

    setStyle = (value: any) => {
      this.setState({
        style: value,
      });
    };

    render() {
      const { state, _loaidngWrapRef, props } = this;
      const { style } = state;
      const { loading, loadingText } = loadingObj;
      return (
        <div
          ref={_loaidngWrapRef}
          className={cn({
            loading: loading,
          })}
          style={style}
        >
          <div
            className={cn('loading-content', {
              'no-loading': !loading,
            })}
          >
            <span>{loadingText}</span>
          </div>
          <InnerComponent {...props} />
        </div>
      );
    }
  }

  return ComponentWithLoadingClass;
};

使用的时候

import { useMemo, useRef, useState } from 'react';
import Button, {
  // withLoading,
  withTracking,
  witHoverMenue,
  // withMenueModal,
  withMenueModal2,
  withLoadingClass,
} from './Design/FnDesign';
import { flowRight } from 'lodash';
import { Subject } from 'rxjs';
import Popper from './Component/Popper';
import Modal from './Component/Modal';

export interface InterMenueEvent$ {
  type: 'openModal';
  payload: string;
}
function App() {
  const [loading, setLoading] = useState(false);
  const [modalShow, setModalShow] = useState(false);
  const _$menueEventRef = useRef<Subject<InterMenueEvent$>>(new Subject());

  const onHandleButton = () => {
    alert('App');
  };

  const tracking = () => {
    alert('tracking');
  };

  const enhancerFunc = flowRight([
    // withLoadingClass({
    //   loading: loading,
    //   loadingText: 'nihao...',
    // }),
    withTracking(tracking),
    // witHoverMenue(
    //   ['item1', 'item2', 'item3', 'item4', 'item5'],
    //   (item: any) => {
    //     console.log('meue cb =>', item);
    //     console.log(_$menueEventRef.current);
    //     _$menueEventRef.current &&
    //       _$menueEventRef.current.next({
    //         type: 'openModal',
    //         payload: item,
    //       });
    //   }
    // ),
    // withMenueModal(_$menueEventRef.current!!),
    withMenueModal2(witHoverMenue)(
      ['item1', 'item2', 'item3', 'item4', 'item5'],
      (item: any) => {
        console.log(item);
      }
    ),
    withLoadingClass({
      // 注意顺序问题 这个 mask 要作为最顶层
      loading: loading,
      loadingText: 'nihao...',
    }),
  ]);

  const Button1 = useMemo(() => enhancerFunc(Button), [loading]);

  const startLoading = () => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
    }, 2000);
  };

  const openModal = () => {
    setModalShow(true);
  };

  return (
    <div className="App">
      <button onClick={startLoading}>开始loading</button>
      <button onClick={startLoading}>开始loading</button>
      <button onClick={startLoading}>开始loading</button>
      <button onClick={startLoading}>开始loading</button>
      <button onClick={openModal}>打开modal</button>
      <Popper
        container={() => document.querySelector('#root') || document.body}
        anchorEl={(props) => {
          return <button {...props}>打开</button>;
        }}
      >
        {(props) => {
          return <h2>66666</h2>;
        }}
      </Popper>
      {/* <Button type="primary" onClick={onHandleButton}>
        Click
      </Button> */}
      <Button1 type="primary" onClick={onHandleButton}>
        Click
      </Button1>

      <Modal isShow={modalShow} setModalShow={setModalShow}>
        <div>
          <h2>Modal --</h2>
        </div>
      </Modal>
    </div>
  );
}

export default App;
// @types/classnames

用自定义Hooks 来构建

1.基础功能 作为一个最基础的button 首先你得像模像样

export interface InterButtonProps {
  children?: React.ReactNode;
  type?: 'primary' | 'ghost' | 'link' | 'wanning';
  onClick?: () => void;
  [key: string]: any;
}

const Button: FC<InterButtonProps> = (props) => {
  const { onClick = () => {}, children, type = 'ghost' } = props;

  const csnames = cn('me-button', {
    ghost: type === 'ghost',
    primary: type === 'primary',
    link: type === 'link',
    wanning: type === 'wanning',
  });

  return (
    <button className={csnames} onClick={onClick}>
      {children}
    </button>
  );
};

2.加loaidng效果

const LoadingMask: React.FC<InterLoadingMaskPorps> = (props) => {
  const { _loaidngWrapRef, loading, loadingText, style } = useLoading(props);

  return (
    <div
      ref={_loaidngWrapRef}
      className={cn({
        loading: loading,
      })}
      style={style}
    >
      <div
        className={cn('loading-content', {
          'no-loading': !loading,
        })}
      >
        <span>{loadingText}</span>
      </div>
      {props.children}
    </div>
  );
};

import { useLayoutEffect, useRef, useState } from 'react';
import { getDomWH } from '../../utils';

export interface InterLoadingMaskPorps {
  loading: boolean;
  loadingText: string;
  children?: React.ReactNode;
}

const useLoading = (props: InterLoadingMaskPorps) => {
  const { loading = false, loadingText = 'loading...' } = props;

  // 直接计算高度 去做一个mask
  const [style, setStyle] = useState<Record<string, any>>({});
  const _loaidngWrapRef = useRef<HTMLDivElement>(null);

  useLayoutEffect(() => {
    if (!_loaidngWrapRef.current) {
      return;
    }
    const wh = getDomWH(
      _loaidngWrapRef.current.lastChild as unknown as HTMLElement
    );

    setStyle({
      width: wh.w,
      height: wh.h,
    });
  }, []);

  return {
    loading,
    loadingText,
    style,
    _loaidngWrapRef,
  };
};

export default useLoading;

3.加tracking功能

tracking 不涉及 任何UI 我们可以直接构造一个 高阶fn , 详细请看 useTracking

const useTracking = (fn: any, trackingParams: Function) => {
  return () => {
    console.log();
    trackingParams && trackingParams();
    fn();
  };
};

export { useTracking };

4.加一个hover弹出poper功能

export interface InterHoverMenue {
  children?: React.ReactNode;
  items: Array<string>;
  cb: (item: string) => void;
}

const HoverMenue: React.FC<InterHoverMenue> = (props) => {
  const { cb, items } = props;
  const { hover, setHover, onMouseEnterHandle, onMouseLeaveHandle } =
    useHoverMenue();

  return (
    <Popper
      isShow={hover}
      setIsShow={setHover}
      container={() => document.querySelector('#root') || document.body}
      anchorEl={(propsInner) => {
        return (
          <div
            style={{ position: 'absolute' }}
            {...propsInner}
            onMouseEnter={debounce(onMouseEnterHandle)}
            onMouseLeave={debounce(onMouseLeaveHandle)}
          >
            {props.children}
          </div>
        );
      }}
    >
      {/* 遵循MUI 规范设计的 popper 和antd 不一样 需要区分哈 */}
      {(propsInner) => {
        return (
          <div {...propsInner}>
            {items.map((item, idx) => (
              <h2 key={idx} onClick={() => cb(item)}>
                {item}
              </h2>
            ))}
          </div>
        );
      }}
    </Popper>
  );
};

import { useState } from 'react';


const useHoverMenue = () => {
  // 解下来如果什么都不加 那么所有的功能都没有问题 ,但我们需要加hover功能
  const [hover, setHover] = useState(false);
  const onMouseEnterHandle = () => {
    setHover(true);
  };
  const onMouseLeaveHandle = () => {
    setHover(false);
  };

  return {
    hover,
    setHover,
    onMouseEnterHandle,
    onMouseLeaveHandle,
  };
};

export { useHoverMenue };


5.加一个hover弹出poper功能点击弹出modal 功能

interface InterMenueOpenModalProps {
  $menueEvent: Subject<InterMenueEvent$>;
  children?: React.ReactNode;
}

const MenueOpenModal: FC<InterMenueOpenModalProps> = (props) => {
  const { modalInfo, modalShow, setModalShow } = useMenueOpenModal(
    props.$menueEvent
  );
  return (
    <>
      <Modal isShow={modalShow} setModalShow={setModalShow}>
        <div>
          <h2>withMenueModal = {modalInfo}</h2>
        </div>
      </Modal>
      {props.children}
    </>
  );
};

import { useEffect, useState } from 'react';
import { Subject } from 'rxjs';
import { InterMenueEvent$ } from '../../AppHooks';

const useMenueOpenModal = ($menueEvent: Subject<InterMenueEvent$>) => {
  const [modalShow, setModalShow] = useState(false);
  const [modalInfo, setModalInfo] = useState('');

  useEffect(() => {
    let $subject: any;
    if ($menueEvent) {
      $subject = $menueEvent.subscribe((value) => {
        switch (value.type) {
          case 'openModal':
            setModalShow(true);
            setModalInfo(value.payload);
            break;

          default:
            break;
        }
      });
    }
    return () => {
      $subject && $subject.unsubscribe();
    };
  }, []);

  return {
    modalShow,
    modalInfo,
    setModalShow
  };
};

export default useMenueOpenModal;

总结一下

在上述的例子中我们 可以进一步挖掘

更甚者 可以把所有的hooks 丢到 顶层去,但是这样传递对后续优化 会带来不利所以 需要综合看待和设计 分层,尽力的把 业务逻辑 的UI 表现分离,这样非常方便后续的 重构/迁移

也许有的同学说,照你的说法,我是否可以再 with...把这些hoc中抽离出来呢 我想当然可以,但是我想我们讨论错方向了,综合上述来看,我认为这 两种设计思路上的区别主要在于 Hoc 的套娃 和 自定义Hooks套娃的区别 形如 以下两种设计思路的博弈


/**
 <A porps>
  <B porps>
    <C porps>
     <D porps>
      <E props></E>
     </D>
    </C>
  </B>
 </A>

 vs

 cosnt EnhancerFunc = flowRight(A(porps), B(porps) , C(porps), fn(D)(porps) )(E) 
  / flowRight(A(porps), B(porps) , C(porps), D(porps) )(E) 
  
  // 只传递最底层组件需要的数据 就可以了
  <EnhancerFunc {E...props}/>

 */

综合来看

HOC 是通过将一个组件封装在另一个组件中,从而实现组件复用。它可以在不更改组件代码的情况下为组件提供额外的功能,例如访问上下文、状态管理、异步请求等。但是,由于 HOC 的复杂性(比如前面的例子 flowRight([fn1,fn2,fn4(fn3)(),]) ),代码难以维护和理解,也容易产生混乱的结构。

自定义 Hooks 是 React 在 16.8 版本中推出的一种新的组件复用方式。它通过使用函数实现状态管理和副作用处理,从而减少代码的复杂度。在自定义 Hooks 中,状态和副作用可以以独立的方式组织,方便阅读和理解。同时,由于自定义 Hooks 是函数,它们可以很方便地测试和复用。

因此,对比 HOC 和自定义 Hooks,自定义 Hooks 更简洁、可读性更高、可复用性更强。但是,如果需要为组件提供复杂的功能,则可能需要使用 HOC。总体而言,选择 HOC 还是自定义 Hooks 取决于具体的需求和实现情况。

不过我个人依然主推自定义Hooks 🤣 (死不悔改),但是在团队中写代码还得老实点 Hoc🥲 比如最近我在给摸个 button 加 HoverMenueModal 就是.....

参考文献