Typescript tip: 避免 Optional Properties

766 阅读1分钟

Optional Properties 存在的问题

在日常开发中经常会遇到这样的场景,给定一个默认值为空对象,在之后渲染的时候或者从接口里拿数据,再设置一下,类似如下例子:

const Context = React.createContext({})

function C(){
    const value = useMemo(()=>({
        handle:() => {},
        state:'xxx'
    }),[])
    return (
        <Context.Provider value={value}>
        ...
        </Context.Provider>
    )
}

这种情况下大多数同学可能会定义 类型是这样的

type ContextType = {
    handle?: () => void;
    state?: string;
}
const Context = React.createContext<ContextType>({})

但是这样的写法就无法发挥 typescript 强大的类型推断能力,在使用的时候必须针对于每个值判空,比如

const { handle, state } = useContext(Context)
if(state) {
    handle?.()
}

这里其实 state 已经为存在了,那么 handle 必然也是存在的,所以这样的类型定义是存在瑕疵的

使用简单的联合类型和其存在的问题

这里其实我们的类型只有 两种情况

{}
// 和
type ContextType = {
    handle: () => void;
    state: string;
}

如果我们这样定义 context 的类型

type ContextType = {} | {
  handle: () => void;
  state: string;
}

const Context = React.createContext<ContextType>({});

会出现报错,因为不能直接使用解构

image.png

需要先类型收缩一下

image.png

定义相同的可选结构, 值类型为 undefined 的类型

解决办法是定义一个具体 相同的可选结构, 值类型为 undefined 的类型,再与其联合

type TrulyContextType = {
  handle: () => void;
  state: string;
}
type TransformValueUndefined<T extends Record<string, any>> = Partial<{
  [P in keyof T]: undefined;
}>;

type ContextType = TransformValueUndefined<TrulyContextType> | TrulyContextType
const Context = React.createContext<ContextType>({});

这样的话直接用解构, 并且当 state 存在是 handle 也存在

image.png

完整代码

type TrulyContextType = {
  handle: () => void;
  state: string;
}
type TransformValueUndefined<T extends Record<string, any>> = Partial<{
  [P in keyof T]: undefined;
}>;

type ContextType = TransformValueUndefined<TrulyContextType> | TrulyContextType
const Context = React.createContext<ContextType>({});

function Parent() {
  const value = useMemo(
    () => ({
      handle: () => {},
      state: "xxx"
    }),
    []
  );
  return <Context.Provider value={value}></Context.Provider>;
}

function Children() {
  const { state, handle} = useContext(Context);
  if(state){
    handle();
  }
  return null;
}

此外

解构的写法的类型收缩需要 ts 4.4

image.png

补充例子(react 组件同时接收两个参数,或者都不接收)

地址:stackblitz.com/edit/react-…

image.png

image.png