背景
在上一篇文章中,我在团队推行TypeScript的过程中,并非一帆风顺,也是有队友反馈说ts太难、太麻烦、耽误开发,js一把梭多好。诚然,在业务代码中使用ts一定是会占用一些开发时间,我不是一味的追求新技术,而是评估了ts在项目中的成本和收益后,再决定是否引入使用。下面,就来看看队友所说的ts的问题都有哪些?
组件参数和函数参数的关联关系
现在有一个组件,里面用到了一个自定义hook,代码如下
// ChatApp.tsx
const ChatApp = ({microAppId,chatMode,defaultInputType}) => {
const chatValue = useGetChatValue({microAppId,chatMode});
// ...一些逻辑
}
// useGetChatValue.ts
const useGetChatValue = ({microAppId,chatMode})=>{
// ...一些逻辑
}
可以看到,ChatApp接受了三个参数,其中有两个是为了给useGetChatValue用的,这时候我们的ts类型要怎么写?业务同学是这样写的:
// ChatApp.tsx
interface IChatAppProps {
microAppId: number;
chatMode?: EChartMode;
defaultInputType?: EInputType[];
}
const ChatApp = ({microAppId,chatMode,defaultInputType}:IChatAppProps) => {
const chatValue = useGetChatValue({microAppId,chatMode});
// ...一些逻辑
}
// useGetChatValue.ts
interface IUseGetChatValueProps {
microAppId: number;
chatMode?: EChartMode;
}
const useGetChatValue = ({microAppId,chatMode}:IUseGetChatValueProps)=>{
// ...一些逻辑
}
这样写会有什么样的问题呢?就是当useGetChatValue的入参有改动的时候,两边的类型都要跟着调整,这也就是为什么这位队友觉得麻烦、累的原因。在快节奏的开发中,这样的代码就成了我们团队中普遍的现象。
那有聪明的开发“深知”复用的精髓,他做了精简:
// ChatApp.tsx
export interface IUseGetChatValueProps {
microAppId: number;
chatMode?: EChartMode;
}
interface IChatAppProps extends IUseGetChatValueProps {
defaultInputType?: EInputType[];
}
const ChatApp = ({microAppId,chatMode,defaultInputType}:IChatAppProps) => {
const chatValue = useGetChatValue({microAppId,chatMode});
// ...一些逻辑
}
// useGetChatValue.ts
import { IUseGetChatValueProps } from './ChatApp'
const useGetChatValue = ({microAppId,chatMode}:IUseGetChatValueProps)=>{
// ...一些逻辑
}
这样子就不需要两边改了,看似完美,但真的对吗?
这里需要引申出一个概念:溯源
不管是数据还是类型,源头始终只有一个。我们仔细看这个代码,microAppId和chatMode这两个字段是谁在用?是useGetChatValue,ChatApp接收这两个参数的目的是为了传给useGetChatValue,源头是useGetChatValue。所以我们把IUseGetChatValueProps定义在ChatApp组件中,再从useGetChatValue去引用IUseGetChatValueProps,这样就造成了引用逻辑混乱。所以应该这么写:
// ChatApp.tsx
import { IUseGetChatValueProps } from './useGetChatValue'
interface IChatAppProps extends IUseGetChatValueProps {
defaultInputType?: EInputType[];
}
const ChatApp = ({microAppId,chatMode,defaultInputType}:IChatAppProps) => {
const chatValue = useGetChatValue({microAppId,chatMode});
// ...一些逻辑
}
// useGetChatValue.ts
export interface IUseGetChatValueProps {
microAppId: number;
chatMode?: EChartMode;
}
const useGetChatValue = ({microAppId,chatMode}:IUseGetChatValueProps)=>{
// ...一些逻辑
}
如果碰到IChatAppProps只需要接收部分IUseGetChatValueProps的属性该怎么办?
// ChatApp.tsx
import { IUseGetChatValueProps } from './useGetChatValue'
interface IChatAppProps extends IUseGetChatValueProps {
defaultInputType?: EInputType[];
}
const ChatApp = ({microAppId,chatMode,defaultInputType}:IChatAppProps) => {
const [answerStatus, setAnswerStatus] = useState(EAnswerStatus.UN_ANSWER);
const chatValue = useGetChatValue({microAppId,chatMode,answerStatus});
// ...一些逻辑
}
// useGetChatValue.ts
export interface IUseGetChatValueProps {
microAppId: number;
chatMode?: EChartMode;
answerStatus: EAnswerStatus;
}
const useGetChatValue = ({microAppId,chatMode}:IUseGetChatValueProps)=>{
// ...一些逻辑
}
这时候useGetChatValue新增了一个参数answerStatus,answerStatus是在ChatApp定义的一个state,很明显,这时候answerStatus就不应该从IChatAppProps接收了。
那我们现在深得溯源的精髓,知道要从IUseGetChatValueProps获取类型,那现在要怎么写呢?这时候引申出了TS的工具类型,eg:Omit,Pick,Partial,这几个是比较常用的,其他的有兴趣可以自行查询。
// ChatApp.tsx
import { IUseGetChatValueProps } from './useGetChatValue'
// 有两种写法:
// 一:Pick,采集部分字段,以下写法相当于从IUseGetChatValueProps中采取了microAppId和chatMode的类型
interface IChatAppProps extends Pick<IUseGetChatValueProps,'microAppId'|'chatMode'> {
defaultInputType?: EInputType[];
}
// 二:Omit,剔除部分字段,以下写法相当于从IUseGetChatValueProps中剔除了answerStatus,保留了microAppId和chatMode的类型
interface IChatAppProps extends Omit<IUseGetChatValueProps,'answerStatus'> {
defaultInputType?: EInputType[];
}
const ChatApp = ({microAppId,chatMode,defaultInputType}:IChatAppProps) => {
const [answerStatus, setAnswerStatus] = useState(EAnswerStatus.UN_ANSWER);
const chatValue = useGetChatValue({microAppId,chatMode,answerStatus});
// ...一些逻辑
}
// useGetChatValue.ts
export interface IUseGetChatValueProps {
microAppId: number;
chatMode?: EChartMode;
answerStatus: EAnswerStatus;
}
const useGetChatValue = ({microAppId,chatMode}:IUseGetChatValueProps)=>{
// ...一些逻辑
}
代码看上去好像变得复杂了,但只要将溯源的理念铭记于心,一切都水到渠成。
现在回过头来看,为什么队友觉得TS麻烦,难,耽误事
TS不熟练,这无可避免,不管什么技术都是从入门到精通的,这是一个过程- 在快节奏的开发中,忽略了源的重要性,所以在定义类型之前,要先思考源在哪里,这是重点
- 认为用了
TS,每个地方都要时刻关注数据类型,心累。其实这涉及到我们在业务系统中对TS应该应用到什么样的一个程度,这个可以在之后的文章中来讨论下
总结
TS不是什么洪水猛兽,而是类型安全和类型推导的一大利器,我们应该善用它。
搞清楚溯源很重要!!!