【内容梳理】<React函数式组件开发>写前自问

74 阅读3分钟

view = fn(props, state, context)

1. 是否已明确入参?

  • 入参 interfacetype 是否已经编写?哪些参数需要必填?需要外部传入哪些参数(属性、事件)?
interface IGenericProps<T> {
  value: T;
  valueProp: string; // 作为value值的key
  labelProp: string; // 作为label值的key
  onChange?: (val: T) => void;
}
  • 是否支持 ref?要对外暴露哪些方法
// 对外暴露方法
interface IForwardMethod {
  onEvent: () => void;
}

...
// 组件内部
useImperativeHandle(outerRef, () => {
  return {
    onEvent: () => console.log('触发了onEvent'),
  };
});
  • 是否需要解构?解构的优劣势?参数是否需要有默认值,默认值为啥?(注意:默认值只对undefined生效,null无效\color{red}{注意:默认值只对 undefined 生效,null 无效})(请遵循使用方便原则)
const { value, checked, onChange, element, elementType, modeList = [DefaultModeEnum.CUSTOM], onBlur } = props;
const { data, type } = element;
const { formItemSettings } = data;
const { defaultMode = DefaultModeEnum.CUSTOM } = formItemSettings || {};
  • 是否需要使用插槽?插槽是否有动态参数?是否需要传递组件引用?
// 渲染编辑态
const renderEditable = () => {
  return (
    <Input
      ref={innerRef}
      ...
      defaultValue={defaultValue}
      value={validVal}
      maxLength={maxLength}
      onBlur={handleBlur}
      onChange={handleChange}
    />
  );
};
<ToggleEditComp
  editRef={innerRef}
  readonly={readonly}
  autoToggle={autoToggle}
  editableComp={renderEditable()}
  readonlyComp={renderReadonly()}
  trigger={trigger}
/>
import DatePickerComp from '../render/DatePickerComp';
...
// 使用组件引用
<SettingsDefaultValue
  element={element}
  elementType={DatePickerComp}
  modeList={[DefaultModeEnum.CURRENT, DefaultModeEnum.CUSTOM]}
/>

2. 通用还是业务?

2.1 通用组件

  • 不应存在任何业务属性,props 都应该只服务于渲染。所有的业务判断交于外层,内部只应该接收 enumboolean;
  • 若为泛型组件,需要复写外层.d.ts 文件;否则 ts 提示错误
import React from 'react';

declare module 'react' {
  function memo<A, B>(Component: (props: A) => B): (props: A) => ReactElement | null;
  function forwardRef<T, P = {}>(
    render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
  ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}
  • 不要使用 redux 等【注入状态】来共享数据,若需要数据,一律通过 props 传递
  • 若为大组件中的小组件,希望共享数据,可以酌情使用 useContext 或者 Zustand来共享数据,让其支持多实例;

2.2 业务组件

  • 是否是路由组件,非路由组件尽量不适用 useParams 等方式获取路由参数,而是通过 props 传递;
  • 使用【状态管理】,比如 redux 时,注意缓存控制,提高性能。注意只有需要共享的数据放入 redux,不要把 redux 当仓库

3. 频繁还是不频繁?

3.1 频繁调用

  • 使用 useMemoizedFn + React.memo + react-fast-compare
  • 弹框使用 visible + useEffect 监听 value 变化
  • 考虑单例或使用状态管理缓存不变量(比如区域组件)

3.2 不频繁调用

  • 没必要使用 useMemoizedFn + React.memo
  • 弹框使用销毁模式,每一次都是重新创建,从而减少监听

4. 样式策略?

  • 使用 styled-component 还是使用 css-modules还是纯 namespace
  • 如何覆盖三方库样式
  • 动态样式,style 和 classNames

5. api 策略?

  • 抽离 api 函数,方便使用
  • 定义 req 和 res 对应的 interface,便于接口调试。也方便后续开发者理解前后端交互数据结构
interface IQueryUserReqAttr {
  pageIndex: number;
  pageLen: number;
  fuzzyField?: string;
}

interface IQueryUserDataReqAttr extends IQueryUserReqAttr {
  inUserIds?: number[]; // 只筛选userId中的数据
  notInUserIds?: number[]; // 按照userId剔除数据
}

// 获取用户信息
export async function getUserList(reqData: IQueryUserDataReqAttr): Promise<ICommonRes<IPageInfoRes<IUserSelectAttr>>> {
  const { pageIndex, pageLen, ...restData } = reqData;
  return axiosIns('/arm/sp/getUserList', {
    method: ApiMethodType.POST,
    params: {
      pageIndex,
      pageLen,
    },
    data: restData,
  });
}
  • 统一调用单例,比如 axios。以及其内部的拦截器(request 和 response)
// 默认的request拦截器
const defaultRequsetInterceptor = axiosIns.interceptors.request.use(
  (config) => {
    ...
  },
  (error) => {
    ...
  }
);

// 默认的response拦截器
const defaultResponseInterceptor = axiosIns.interceptors.response.use(
  (response) => {
    ...
  },
  (error) => {
    ...
  }
);

6. 能否复用?

  • 【上下文无关】【纯函数】的抽离 util、【有上下文或状态】【生命周期】的抽离 hooks
    • utils 中:format、解构转换
    • hooks 中:数据管理、数据获取(带 loading)
  • 组件块的复用,比如按钮组
  • 通用底层组件,减少编写重复的 UI

7. 写注释了么?

  • 防止业务复杂,自己都忘了逻辑
  • 可以快速提升他人理解的速度
  • 便于后期优化、拆分