www.toptal.com/react/react…

React hooks were introduced in February 2019 to improve code readability. This article explores how to work with TypeScript.

Before hooks, there were two styles of React components:

  • Classes for handling state
  • A Functional component defined entirely by its props

A common usage is for the former to build complex Container components, while the latter is responsible for simpler Presentational components.

React Hooks?

The container component is responsible for state management, as well as remote requests referred to in this article as “side Effects.” The state is propagated to the child components via props.

But as the code grows, functional components also tend to replace class components as containers.

Upgrading a functional component to a container with a lot of state is not painful, but time-consuming. In addition, a strict distinction between so-called containers and presentation components is less valued.

Hooks do a good job of keeping code generic and having almost all the benefits. Here is an example of adding a local state to a component that handles quote signing:

// Places the signature in a local state and toggles the signature when the signature state changes
function QuotationSignature({quotation}) {
   const [signed, setSigned] = useState(quotation.signed);
   useEffect((a)= > {
       fetchPost(`quotation/${quotation.number}/sign`)
   }, [signed]); // Side effects will be triggered when the signature status changes

   return<> <input type="checkbox" checked={signed} onChange={() => {setSigned(! signed)}}/> Signature </> }Copy the code

There’s another good thing to be said — though React has been criticized for being clunky and hard to use compared to TypeScript’s silky coding in Angular; But using TypeScript with React hooks becomes a pleasant experience.

TypeScript in old React

TypeScript was designed by Microsoft and followed the Angular path, while Flow developed by React was in decline. Writing native TypeScript in React class components is a pain because React developers have to type both props and state, even though many of their properties are the same.

In the old way, you first have a Quotation type, which manages the state and props of some CRUD components. And in its associated state, creates a Quotation type property, as well as indicating the signed or unsigned status.

interface QuotationLine {
  price: number
  quantity: number
}

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> {
	 // ...
}
Copy the code

But consider the situation we face when creating a new quote, where QuotationPage has not successfully requested an ID from the server: The QuotationProps defined previously will not know this key numeric value — incomplete data cannot be accurately matched by the Quotation type. We might have to declare more code in the QuotationProps interface:

interface QuotationProps{
   // All attributes in Quotation except ID:
   title: string;
   lines: QuotationLine[]
   price: number
}
Copy the code

Let’s copy all the attributes except the ID and make a new type. This is… It reminded me of my fear in Java of having to write a whole bunch of Dtos.

To overcome this pain, we need to catch up on TypeScript.

TypeScript combines the benefits of hooks

By using hooks, we can discard the previous QuotationState — we can split it into two different parts:

// ...

interface QuotationProps{
   quotation: Quotation;
}
function QuotationPage({quotation}:QuotationProps) {
   // Two useXXX functions split two attributes in the previous interface
   const [quotation, setQuotation] = useState(quotation);
   const [signed, setSigned] = useState(false);
   
   // ...
}
Copy the code

By splitting the state, you eliminate the need to explicitly create a new interface. Local state types often derive default state values.

Because the hooks component is a function, you can write the same component function that returns the react. FC type. Such a function explicitly declares the return type of its functional component and specifies the props type.

const QuotationPage: FC<QuotationProps> = ({quotation}) = > {
   const [quotation, setQuotation] = useState(quotation);
   const [signed, setSigned] = useState(false);
   
   // ...
}
Copy the code

It is obviously easier to use TypeScript in React hooks than in class components. And because strong typing is a powerful safeguard for code security, you should consider using TypeScript if your new project uses hooks.

TypeScript feature adapted to hooks

In the previous React hooks TypeScript example, there was some confusion about how and which properties were used in the QuotationProps interface.

TypeScript actually provides a number of “tool methods” to effectively “de-noise” interfaces in React.

  • Partial<T>: any subset of all keys of type T
  • Omit<T, 'x'>: in addition toxAll keys not of type T
  • Pick<T, 'x', 'y', 'z'>: Pick explicitly from type Tx, y, z

In our use case, the form of Omit

may be used to exclude ID from the Quotation type. Combine the type keyword with the backhand to throw a new type.
,>

Partial

and Omit

do not exist in most strongly typed languages such as Java, but are often useful in various ways in front-end development. They simplify the type definition burden.

type QuotationProps = Omit<Quotation, id>;
function QuotationPage({quotation}: QuotationProps){
   const [quotation, setQuotation] = useState(quotation);
   const [signed, setSigned] = useState(false);
   
   // ...
}
Copy the code

Of course, extends might also make it easier to distinguish persistent types and so on:

interface Quote{
   title: string;
   lines: QuotationLine[]
   price: number
}

interface PersistedQuote extends Quote{
  id: number;
}
Copy the code

This also simplifies the common if or undefined problem when dealing with related attributes.

Be careful with Partial

, it offers little or no protection.

Pick < T > ‘x’ | ‘y’ is another don’t have to declare a new interface can define a new type of way at any time. If a component simply needs to edit the quote title:

type QuoteEditFormProps = Pick<Quotation, 'id'|'title'>
Copy the code

Or declare directly in line:

function QuotationNameEditor({id, title}: Pick<Quotation, 'id'|'title'>){ ... }Copy the code

Make no mistake, I’m a big fan of DDD-Domain Driven Design. It’s not that I’m too lazy to write two more lines to declare a new interface — I use interfaces when I need to describe domain names accurately; For the sake of native code correctness and noise reduction, I use these TS utility syntax.

Other benefits of React Hooks

The React team has always viewed React as a functional framework. In the past they used class components to handle their state. Now they have hooks, a technique that allows a function to track component state.

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>
  );
}
Copy the code

This is a good use case for using Partial safely.

Although the Reducer function will be executed multiple times, the associated useReducer hooks will be created only once.

By naturally defining the Reducer function outside of the component, the code can be divided into multiple independent functions, rather than all in one class and collectively around its internal state.

This is great for testability — some functions deal only with JSX, others deal only with business logic, and so on.

You don’t (almost) need hoc-higher Order Components anymore. The Render props mode makes it easier to write functional components.

This makes it easier to read the code. Code is no longer a jumble of classes/functions/patterns, but simply a collection of functions. However, because these functions are not attached to an object, naming them can be a bit difficult.

TypeScript 仍是 JavaScript

The joy of JavaScript is that you can play with your code in any way you want. With TypeScript, you can still use keyof to access all of an object’s keys and use type syndication to create something arcane — fear of it.

Make sure your tsconfig.json has the “strict”:true option set. Check the project before it starts or you will have to refactor a lot of things!

The extent to which code should be typed is debatable. You can define everything manually, or you can let the compiler infer the type. This depends on the configuration of the Linter tool and team engagement.

In the meantime, you’ll still get runtime errors! TypeScript is simpler than Java and avoids the covariant/contravariant problem of generics.

In the next example, there is a list of Animals, and the same list of Cat.

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); // Question 1: The listOfCats statement is for an Animal[] const [animals, setAnimals] = useState<Animal[]>(listOfCats) const [Animal, SetAnimal] = useState(duck) return <div onClick={()=>{animals.unshift(animal) SetAnimals ([...animals]) // cause The dirty forceUpdate}}> The first cat says {cats[0].meow()} // self self </div>; }Copy the code

Unfortunately, since listOfCats are declared with Cat[] and Animal[] generics respectively, Duck is an Animal in the listOfAnimals array []. Duck is an Animal in the listOfAnimals array. But this causes a runtime error on the subsequent call to the listOfCats element first declared as Cat[].

TypeScript has only a simple bivariant implementation of generics for JS developers. If you name your variables properly, you can largely avoid referring to a duck as a cat.

Exist at the same time, the increase in and out constraints in TS proposal (https://github.com/microsoft/TypeScript/issues/10717), to support the covariance and inverter.






–End–






View more front-end good article please search fewelife concern public number reprint please indicate the source