Hook于2019年2月引入React,以提高代码的可读性。我们已经在之前的文章中讨论过React钩子,但是这次我们正在研究钩子如何与TypeScript一起工作。
在使用钩子之前,React组件曾经具有两种风格:
-
处理状态的类
-
由其道具完全定义的功能
这些的自然用法是使用类构建复杂的容器组件,并使用纯函数构建简单的表示性组件。
什么是React挂钩?
容器组件处理状态管理和对服务器的请求,然后在本文中将其称为副作用。状态将通过道具传播到容器子级。
但是随着代码的增长,功能组件倾向于转换为容器组件。
将功能组件升级为更智能的组件并不是一件痛苦的事情,但这是一项耗时且令人不愉快的任务。此外,不再严格要求区分演示者和容器。
钩子可以做到这两者,因此生成的代码更加统一,并且几乎具有所有优点。这是将局部状态添加到正在处理报价签名的小型组件中的示例。
// put signature in local state and toggle signature when signed changes
function QuotationSignature({quotation}) {
const [signed, setSigned] = useState(quotation.signed);
useEffect(() => {
fetchPost(`quotation/${quotation.number}/sign`)
}, [signed]); // effect will be fired when signed changes
return <>
<input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/>
Signature
</>
}
这有很大的好处:使用TypeScript进行编码对于Angular来说很棒,但是对React却臃肿了。但是,使用TypeScript编写React钩子是一种令人愉快的体验。
具有旧React的TypeScript
TypeScript是由Microsoft设计的,并在React开发Flow时遵循了Angular的路径,而Flow现在已经失去了吸引力。写作阵营与天真打字稿类是相当痛苦的,因为阵营开发商必须输入两个props
和state
即使许多键是一样的。
这是一个简单的域对象。我们制作一个带类型的报价应用,该应用Quotation
在带有状态和道具的某些原始组件中进行管理。将Quotation
可以创建,其关联的状态可以改变,以签署与否。
interface Quotation{
id: number
title:string;
lines:QuotationLine[]
price: number
}
interface QuotationState{
readonly quotation:Quotation;
signed: boolean
}
interface QuotationProps{
quotation:Quotation;
}
class QuotationPage extends Component<QuotationProps, QuotationState> {
// ...
}
但是,假设QuotationPage现在将向服务器询问ID:例如,公司的第678条报价。好吧,这意味着QuotationProps不知道那个重要的数字-它没有完全包装Quotation 。我们必须在QuotationProps接口中声明更多代码:
interface QuotationProps{
// ... all the attributes of Quotation but id
title:string;
lines:QuotationLine[]
price: number
}
我们将ID以外的所有属性复制为新类型。嗯 这让我想到了旧的Java,其中涉及编写大量的DTO。为了克服这个问题,我们将增加我们的TypeScript知识来绕过痛苦。
带钩子的TypeScript的好处
通过使用钩子,我们将能够摆脱以前的QuotationState接口。为此,我们将QuotationState分成状态的两个不同部分。
interface QuotationProps{
quotation:Quotation;
}
function QuotationPage({quotation}:QuotationProps){
const [quotation, setQuotation] = useState(quotation);
const [signed, setSigned] = useState(false);
}
通过分割状态,我们不必创建新接口。本地状态类型通常是通过默认状态值来推断的。
带有钩子的组件都是功能。因此,我们可以编写返回FC<P>
React库中定义的类型的相同组件。该函数显式声明其返回类型,并沿props类型进行设置。
const QuotationPage : FC<QuotationProps> = ({quotation}) => {
const [quotation, setQuotation] = useState(quotation);
const [signed, setSigned] = useState(false);
}
显然,将TypeScript与React钩子一起使用比在React类中使用它更容易。并且由于强类型是确保代码安全的宝贵安全性,因此如果新项目使用钩子,则应考虑使用TypeScript。如果要使用TypeScript,一定要使用钩子。
可以使用或不使用React来避免TypeScript的原因有很多。但是,如果您选择使用它,则也一定要使用钩子。
适用于钩子的TypeScript的特定功能
在前面的React hooks TypeScript示例中,我在QuotationProps中仍然具有number属性,但是仍然不知道该数字实际是什么。
TypeScript为我们提供了一长串的实用程序类型,其中三个可以通过减少许多接口描述的噪音来帮助我们使用React。
-
Partial<T>
:T的任何子键 -
Omit<T, 'x'>
:T的所有键,除了键x
-
Pick<T, 'x', 'y', 'z'>
:完全x, y, z
来自T
在我们的情况下,我们想Omit<Quotation, 'id'>
省略引号的ID。我们可以使用type
关键字动态创建一个新类型。
Partial<T>
并且Omit<T>
在大多数类型的语言(例如Java)中不存在,但是对于前端开发中的Forms的示例有很大帮助。它简化了打字的负担。
type QuotationProps= Omit<Quotation, id>;
function QuotationPage({quotation}:QuotationProps){
const [quotation, setQuotation] = useState(quotation);
const [signed, setSigned] = useState(false);
// ...
}
现在,我们有一个没有ID的报价单。因此,也许我们可以设计一个Quotation
和PersistedQuotation
extends
Quotation
。此外,我们将很容易解决一些反复发作if
或undefined
问题。尽管它不是完整的对象,我们是否仍应将其引用为变量?这超出了本文的范围,但是无论如何我们稍后都会提到。
但是,现在我们确定我们不会散布我们认为具有的对象number
。使用Partial<T>
并不能带来所有这些保证,因此请谨慎使用。
Pick<T, 'x'|'y'>
是动态声明类型的另一种方式,而不必声明新接口。如果是组件,只需编辑"报价"标题:
type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>
要不就:
function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}
不要判断我,我是Domain Driven Design的忠实拥护者。我并不懒惰,不想为新接口再写两行。我使用接口来精确描述域名,使用这些实用程序功能来确保本地代码正确性,从而避免噪音。读者将知道这Quotation
是规范的界面。
React钩子的其他好处
React团队始终将React视为功能框架。他们使用了类,以便组件可以处理其自己的状态,并且现在将其作为一种允许函数跟踪组件状态的技术进行了挂钩。
interface Place{
city:string,
country:string
}
const initialState:Place = {
city: 'Rosebud',
country: 'USA'
};
function reducer(state:Place, action):Partial<Place> {
switch (action.type) {
case 'city':
return { city: action.payload };
case 'country':
return { country: action.payload };
}
}
function PlaceForm() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<form>
<input type="text" name="city" onChange={(event) => {
dispatch({ type: 'city',payload: event.target.value})
}}
value={state.city} />
<input type="text" name="country" onChange={(event) => {
dispatch({type: 'country', payload: event.target.value })
}}
value={state.country} />
</form>
);
}
在这种情况下,使用Partial
是安全且不错的选择。
尽管一个函数可以执行多次,但是关联的useReducer
钩子只会被创建一次。
通过自然地从组件中提取reducer函数,可以将代码分为多个独立的函数,而不是一个类中的多个函数,所有这些都链接到该类中的状态。
显然,这对于可测试性更好---一些功能处理JSX,其他功能处理行为,其他功能处理业务逻辑,等等。
您(几乎)不再需要高阶组件。Render props模式更易于使用函数编写。
因此,阅读代码更加容易。您的代码不是类/函数/模式的流,而是函数的流。但是,由于您的功能未附加到对象,因此可能很难命名所有这些功能。
TypeScript仍然是JavaScript
JavaScript很有趣,因为您可以在任何方向撕开您的代码。使用TypeScript,您仍然可以使用keyof
对象的键进行播放。您可以使用类型并集来创建无法读取和无法维护的内容-不,我不喜欢这些。您可以使用类型别名将字符串假装为UUID。
但是您可以在没有安全的情况下执行此操作。确保您tsconfig.json
可以"strict":true
选择。在项目开始之前检查它,否则您将不得不重构几乎每一行!
关于您在代码中键入的级别存在争议。您可以键入所有内容,或让编译器推断类型。这取决于棉短绒的配置和团队的选择。
此外,您仍然可以犯运行时错误!TypeScript比Java更简单,并且避免了使用泛型的协方差/协方差问题。
在此动物/猫[https://stackblitz.com/edit/robusta-react-ts?file=index.tsx]示例中,我们具有与猫列表相同的动物列表。不幸的是,这是第一行的合同,而不是第二行。然后,将鸭子添加到"动物"列表中,因此"猫"列表为假。
interface Animal {}
interface Cat extends Animal {
meow: () => string;
}
const duck = {age: 7};
const felix = {
age: 12,
meow: () => "Meow"
};
const listOfAnimals: Animal[] = [duck];
const listOfCats: Cat[] = [felix];
function MyApp() {
const [cats , setCats] = useState<Cat[]>(listOfCats);
// Here the thing: listOfCats is declared as a Animal[]
const [animals , setAnimals] = useState<Animal[]>(listOfCats)
const [animal , setAnimal] = useState(duck)
return <div onClick={()=>{
animals.unshift(animal) // we set as first cat a duck !
setAnimals([...animals]) // dirty forceUpdate
}
}>
The first cat says {cats[0].meow()}</div>;
}
TypeScript对于泛型只有一种双变量方法,该方法很简单,可以帮助JavaScript开发人员采用。如果你正确地命名变量,你很少会添加duck
到listOfCats
。
另外,有提议增加和减少协方差和协方差的合同。
结论
我最近回到Kotlin,这是一种很好的类型化语言,正确地键入复杂的泛型非常复杂。由于鸭子输入和双变量方法的简单性,TypeScript没有这种一般性的复杂性,但是您还有其他问题。也许您并没有为使用TypeScript设计并为TypeScript设计的Angular遇到很多问题,但使用React类却遇到了这些问题。
TypeScript可能是2019年的大赢家。它获得了React,但也通过Node.js征服了后端世界,并以非常简单的声明文件键入旧库的能力。它埋藏了Flow,尽管有些原因与ReasonML一起进行。
现在,我觉得hooks + TypeScript比Angular更令人愉快和高效。