51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

使用React Hooks和TypeScript

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现在已经失去了吸引力。写作阵营与天真打字稿类是相当痛苦的,因为阵营开发商必须输入两个propsstate即使许多键是一样的。

这是一个简单的域对象。我们制作一个带类型的报价应用,该应用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


2.png

在我们的情况下,我们想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的报价单。因此,也许我们可以设计一个QuotationPersistedQuotation extends Quotation。此外,我们将很容易解决一些反复发作ifundefined问题。尽管它不是完整的对象,我们是否仍应将其引用为变量?这超出了本文的范围,但是无论如何我们稍后都会提到。

但是,现在我们确定我们不会散布我们认为具有的对象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开发人员采用。如果你正确地命名变量,你很少会添加ducklistOfCats

另外,有提议增加和减少协方差和协方差的合同。

结论

我最近回到Kotlin,这是一种很好的类型化语言,正确地键入复杂的泛型非常复杂。由于鸭子输入和双变量方法的简单性,TypeScript没有这种一般性的复杂性,但是您还有其他问题。也许您并没有为使用TypeScript设计并为TypeScript设计的Angular遇到很多问题,但使用React类却遇到了这些问题。

TypeScript可能是2019年的大赢家。它获得了React,但也通过Node.js征服了后端世界,并以非常简单的声明文件键入旧库的能力。它埋藏了Flow,尽管有些原因与ReasonML一起进行。

现在,我觉得hooks + TypeScript比Angular更令人愉快和高效。

赞(0)
未经允许不得转载:工具盒子 » 使用React Hooks和TypeScript