Put the original link first

The weather in Hangzhou in April and May this year is always confusing, which may also be the reason for the epidemic this year. It seems that there is not much feeling of spring. Although the temperature is slowly rising up, but the feeling is not the spring of all things recovery, flowers everywhere in full bloom feeling; I do not know whether it is the epidemic this year, or the sight is obscured by common things, in short, everything is not quite as imagined. Without further ado, here’s a Martin Hochel article

The following version is used for the article

{
  "@types/react": "16.4.7"."@types/react-dom": "16.0.6"."typescript": "3.0.1"."react": "16.4.2"."react-dom": "16.4.2"
}
Copy the code

The source address

I often get questions about how to properly define react refs types in typescript. I couldn’t find anyone who has written about it, so I wrote this article to help people new to React and typescript.

Disclaimer: Please refer to the React Ref official documentation for more details

What is the Refs

Refs provides a way to access DOM nodes or React elements created in the render method. Refs provides a way to access DOM nodes in React

Create the Refs

 class MyComponent extends Component {
  private myRef = createRef()
  render() {
    return <div ref={this.myRef} />}}Copy the code

A component defined this way will receive a compilation error

[ts]
Type 'RefObject<{}>' is not assignable to type 'RefObject<HTMLDivElement>'.
Copy the code

Why will appear this error, ts in JSX file has a very good type inference, we’re in a < div > on increased the ref, ts will deduce the HTMLDivElement type used by need to receive, we can use the following method to solve this problem. Let’s first look at the react. createRef definition:

// react.d.ts
function createRef<T> () :RefObject<T>

// react.d.ts
interface RefObject<T> {
  // immutable
  readonly current: T | null
}
Copy the code

The RefObject

returned is generic; From this we can know how to solve the above validation error:

Use the Refs

We can access the node directly via the current property defined on ref, as follows

const node = this.myRef.current
Copy the code

However, the node may be null, so we will not use the following code to perform operations on this value, and will receive ts error:

class MyComponent extends Component {
  private myRef = React.createRef<HTMLDivElement>()
  focus() {
    const node = this.myRef.current
    node.focus()
  }
}

[ts] Object is possibly 'null'.
const node: HTMLDivElement | null
Copy the code

The React document defines current:

React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts. ref updates happen before componentDidMount or componentDidUpdate lifecycle hooks. React assigns the DOM node to Current when a component mounts componentDidMount. Current is null when componentDidUpdate.

This is what TS is trying to tell you by compiling errors. You need to take some safety precautions, which are necessary for non-null judgments using if, to make sure your program doesn’t have runtime errors;

This gives us an automatic reminder of the HTMLDivElement DOM API, which is nice

Add a Ref to a custom Class component

If we want to get focus when we mount a custom component, we can access the custom component instance by ref and call the focus method of the component.

import React, { createRef, Component } from 'react'
class AutoFocusTextInput extends Component {
  // create ref with explicit generic parameter 
  // this time instance of MyComponent
  private myCmp = createRef<MyComponent>()
  componentDidMount() {
    // @FIXME
    // non null assertion used, extract this logic to method!
    this.textInput.current! .focus() } render() {return <MyComponent ref={this.textInput} />}}Copy the code

⚠ ️

  • This approach applies only to custom components declared as classes
  • We have access to all instance methods

Pass refs to DOM Components

Define a FancyButton component that renders a native button onto the page

Ref Forwarding is an optional feature of the component. It can take a REF from a component and pass it on to its child components.

In the following example, we add Forwarding Refs to the component so that we can access the button node within the component directly through the REF if necessary, just as we would with the Button node

So what’s actually happening here?

  • We create and export a Ref type to the caller of our component
  • With the forwardRef, we get the ref and pass it to the child component, which is a normal function with two parameters
// react.d.ts
function forwardRef<T.P = {}>(
  Component: RefForwardingComponent<T, P>
): ComponentType<P & ClassAttributes<T>>

// T, P here
1:T is the type of DOM2:P is the Props for passing3The definition of the return value is the component definition that combines the props and ref typesCopy the code

This way we can make type-safe calls:

Isn’t that elegant?

Refs and function components

Because function components have no instances, you might not use the ref attribute on function components; However, when you are inside a function component, you can still access a Dom node or class component through a ref

Forwarding Refs

Ref Forwarding is a technique for automatically passing the Ref hook to a component’s descendants

Forwarding refs is used in higher-order components

The official documentation on how higher-order components use Forwarding refs is somewhat complicated; In short, you can just wrap your component with the forwardRef API

return forwardRef((props, ref) = > {
  return <LogProps {. props} forwardedRef={ref} />
})
Copy the code

Here is the FancyButton implemented as a class component

Finally, we implement this higher-order component through Ref Forwarding

Higher-order components that use Ref Forwarding require us to specify parameter types

const EnhancedFancyButton = withPropsLogger<
  FancyButton, 
  FancyButtonProps
>(FancyButton)
Copy the code

So we can use it type-safe

The above! Last rule, post my blog, welcome to follow