React + TypeScript:元素引用的傳遞


React 中需要操作元素時,可通過 findDOMNode() 或通過 createRef() 創建對元素的引用來實現。前者官方不推薦,所以這里討論后者及其與 TypeScript 結合時如何工作。

React 中的元素引用

正常的組件中,可通過創建對元素的引用來獲取到某元素然后進行相應操作。比如元素加載后將焦點定位到輸入框。

class App extends Component {
  constructor(props){
    super(props);
    this.inputRef = React.createRef();
  }

componentDidMount(){
this.inputRef.current.focus()
}

render() {
return (
<div className="App">
<input type="text" ref={this.inputRef}/>
</div>
);
}
}

創建對元素的引用是通過 React.createRef() 方法完成的。使用的時候,通過其返回對象身上的 current 屬性可訪問到綁定引用的元素。

React 內部對引用的 current 賦值更新發生在 componentDidMountcomponentDidUpdate 生命周期之前,即存在使用的時候引用未初始化完成的情況,所以 current 不一定有值。好的做法是使用前先判空。

if(this.inputRef.current){
    this.inputRef.current.focus()
}

在上面的示例中,之所以不用判空是因為我們在 componentDidMount 生命周期中使用,此時元素已經加載到頁面,所以可以放心使用。

組件中引用的傳遞

對於原生 DOM 元素可以像上面那樣創建引用,但對於自己寫的組件,則需要使用 forwardRef() 來實現。

假如你寫了個按鈕組件,想要實現像上面那樣,讓使用者可通過傳遞一個 ref 屬性來獲取到組件中原生的這個 <button> 元素以進行相應的操作。

button.jsx

const FancyInput = props => <input type="text" className="fancy-input" />;

添加 ref 支持后的按鈕組件:

button.jsx

const FancyInput = React.forwardRef((props, ref) => {
  return <input type="text" ref={ref} className="fancy-input" />;
});

forwardRef 接收一個函數,函數的入參中第一個是組件的 props,第二個便是外部傳遞進來的 ref 引用。通過將這個引用在組件中綁定到相應的原生 DOM 元素上,實現了外部直接引用到組件內部元素的目的,所以叫 forwardRef(傳遞引用)。

使用上面創建的 FancyInput,在組件加載后使其獲得焦點:

class App extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

componentDidMount() {
if (this.inputRef.current) {
this.inputRef.current.focus();
}
}

render() {
return (
<div className="App">
- <input type="text" ref={this.inputRef}/>
+ <FancyInput ref={this.inputRef} />
</div>
);
}
}

TypeScript 中傳遞引用

先看正常情況下,對原生 DOM 元素的引用。還是上面的示例:

class App extends Component<{}, {}> {
  private inputRef = React.createRef();

componentDidMount() {
/** 🚨 Object is possibly 'null' */
this.inputRef.current.focus();
}

render() {
return (
<div className="App">
{/ 🚨 Type '{}' is missing the following properties from type 'HTMLInputElement':... /}
<input type="text" ref={this.inputRef} />
</div>
);
}
}

像上面那樣創建並使用存在兩個問題。

一個是提示我們的引用無法賦值到 <input>ref 屬性上,類型不兼容。引用需要與它真實所指代的元素類型相符,這正是 TypeScript 類型檢查為我們添加的約束。這個約束的好處是,我們在使用引用的時候,就知道這個引用真實的元素類型,TypeScript 會自動提示可用的方法和屬性,同時防止調用該元素身上沒有的屬性和方法。這里修正的方法很簡單,如果 hover 或 F12 查看 React.createRef() 的方法簽名,會發現它是個泛型方法,支持傳遞類型參數。

function createRef<T>(): RefObject<T>;

所以上面創建引用時,顯式指定它的類型。

- private inputRef = React.createRef();
+ private inputRef = React.createRef<HTMLInputElement>();

第二個問題是即使在 componentDidMount 生命周期中使用,TypeScript 仍然提示 current 的值有可能為空。上面討論過,其實此時我們知道它不可能為空的。但因為 TypeScript 無法理解 componentDidMount,所以它不知道此時引用其實是可以安全使用的。解決辦法當然是加上判空的邏輯。

  componentDidMount() {
+    if(this.inputRef.current){
      this.inputRef.current.focus();
+    }
  }

還可通過變量后添加 ! 操作符告訴 TypeScript 該變量此時非空。

  componentDidMount() {
-      this.inputRef.current.focus();
+      this.inputRef.current!.focus();
  }

修復后完整的代碼如下:

class App extends Component<{}, {}> {
  private inputRef = React.createRef<HTMLInputElement>();

componentDidMount() {
this.inputRef.current!.focus();
}

render() {
return (
<div className="App">
<input type="text" ref={this.inputRef} />
</div>
);
}
}

React + TypeScript 組件引用的傳遞

繼續到組件的情況,當需要引用的元素在另一個組件內部時,還是通過 React.forwardRef()

這是該方法的簽名:

function forwardRef<T, P = {}>(Component: RefForwardingComponent<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;

可以看到,方法接收兩個類型參數,T 為需要引用的元素類型,我們示例中是 HTMLInputElementP 為組件的 props 類型。

所以添加引用傳遞后,FancyInput 組件在 TypeScript 中的版本應該長這樣:

const FancyInput = React.forwardRef<HTMLInputElement, {}>((props, ref) => {
  return <input type="text" ref={ref} className="fancy-input" />;
});

使用組件:

class App extends Component<{}, {}> {
  private inputRef = React.createRef<HTMLInputElement>();

componentDidMount() {
this.inputRef.current!.focus();
}

render() {
return (
<div className="App">
<FancyInput ref={this.inputRef} />
</div>
);
}
}

相關資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM