Refs
提供了一種方式,容許咱們訪問 DOM 節點或在 render
方法中建立的 React 元素。html
在某些狀況下,咱們須要在典型數據流以外強制修改子組件,被修改的子組件多是一個 React 組件的實例,也多是一個 DOM 元素,例如:react
支持在函數組件和類組件內部使用
createRef
是 React16.3 版本中引入的。redux
使用 React.createRef()
建立 Refs
,並經過 ref
屬性附加至 React
元素上。一般在構造函數中,將 Refs
分配給實例屬性,以便在整個組件中引用。數組
當 ref
被傳遞給 render
中的元素時,對該節點的引用能夠在 ref
的 current
屬性中訪問。app
import React from 'react'; export default class MyInput extends React.Component { constructor(props) { super(props); //分配給實例屬性 this.inputRef = React.createRef(null); } componentDidMount() { //經過 this.inputRef.current 獲取對該節點的引用 this.inputRef && this.inputRef.current.focus(); } render() { //把 <input> ref 關聯到構造函數中建立的 `inputRef` 上 return ( <input type="text" ref={this.inputRef}/> ) } }
ref
的值根據節點的類型而有所不一樣:dom
ref
屬性用於 HTML
元素時,構造函數中使用 React.createRef()
建立的 ref
接收底層 DOM
元素做爲其 current
屬性。ref
屬性用於自定義的 class 組件時, ref
對象接收組件的掛載實例做爲其 current
屬性。ref
屬性,由於函數組件沒有實例。總結:爲 DOM 添加 ref
,那麼咱們就能夠經過 ref
獲取到對該DOM節點的引用。而給React組件添加 ref
,那麼咱們能夠經過 ref
獲取到該組件的實例【不能在函數組件上使用 ref 屬性,由於函數組件沒有實例】。ide
僅限於在函數組件內使用
useRef
是 React16.8 中引入的,只能在函數組件中使用。函數
使用 React.useRef()
建立 Refs
,並經過 ref
屬性附加至 React
元素上。測試
const refContainer = useRef(initialValue);
useRef
返回的 ref 對象在組件的整個生命週期內保持不變。動畫
當 ref
被傳遞給 React 元素時,對該節點的引用能夠在 ref
的 current
屬性中訪問。
import React from 'react'; export default function MyInput(props) { const inputRef = React.useRef(null); React.useEffect(() => { inputRef.current.focus(); }); return ( <input type="text" ref={inputRef} /> ) }
關於 React.useRef()
返回的 ref 對象在組件的整個生命週期內保持不變,咱們來和 React.createRef()
來作一個對比,代碼以下:
import React, { useRef, useEffect, createRef, useState } from 'react'; function MyInput() { let [count, setCount] = useState(0); const myRef = createRef(null); const inputRef = useRef(null); //僅執行一次 useEffect(() => { inputRef.current.focus(); window.myRef = myRef; window.inputRef = inputRef; }, []); useEffect(() => { //除了第一次爲true, 其它每次都是 false 【createRef】 console.log('myRef === window.myRef', myRef === window.myRef); //始終爲true 【useRef】 console.log('inputRef === window.inputRef', inputRef === window.inputRef); }) return ( <> <input type="text" ref={inputRef}/> <button onClick={() => setCount(count+1)}>{count}</button> </> ) }
支持在函數組件和類組件內部使用
React
支持 回調 refs
的方式設置 Refs。這種方式能夠幫助咱們更精細的控制什麼時候 Refs 被設置和解除。
使用 回調 refs
須要將回調函數傳遞給 React元素
的 ref
屬性。這個函數接受 React 組件實例 或 HTML DOM 元素做爲參數,將其掛載到實例屬性上,以下所示:
import React from 'react'; export default class MyInput extends React.Component { constructor(props) { super(props); this.inputRef = null; this.setTextInputRef = (ele) => { this.inputRef = ele; } } componentDidMount() { this.inputRef && this.inputRef.focus(); } render() { return ( <input type="text" ref={this.setTextInputRef}/> ) } }
React 會在組件掛載時,調用 ref
回調函數並傳入 DOM元素(或React實例),當卸載時調用它並傳入 null
。在 componentDidMount
或 componentDidUpdate
觸發前,React 會保證 Refs 必定是最新的。
能夠在組件間傳遞迴調形式的 refs.
import React from 'react'; export default function Form() { let ref = null; React.useEffect(() => { //ref 便是 MyInput 中的 input 節點 ref.focus(); }, [ref]); return ( <> <MyInput inputRef={ele => ref = ele} /> {/** other code */} </> ) } function MyInput (props) { return ( <input type="text" ref={props.inputRef}/> ) }
函數組件內部不支持使用 字符串 refs [支持 createRef | useRef | 回調 Ref]
function MyInput() { return ( <> <input type='text' ref={'inputRef'} /> </> ) }
類組件
經過 this.refs.XXX
獲取 React 元素。
class MyInput extends React.Component { componentDidMount() { this.refs.inputRef.focus(); } render() { return ( <input type='text' ref={'inputRef'} /> ) } }
在 Hook 以前,高階組件(HOC) 和 render props
是 React 中複用組件邏輯的主要手段。
儘管高階組件的約定是將全部的 props
傳遞給被包裝組件,可是 refs
是不會被傳遞的,事實上, ref
並非一個 prop
,和 key
同樣,它由 React 專門處理。
這個問題能夠經過 React.forwardRef
(React 16.3中新增)來解決。在 React.forwardRef
以前,這個問題,咱們能夠經過給容器組件添加 forwardedRef
(prop的名字自行肯定,不過不能是 ref 或者是 key).
React.forwardRef 以前
import React from 'react'; import hoistNonReactStatic from 'hoist-non-react-statics'; const withData = (WrappedComponent) => { class ProxyComponent extends React.Component { componentDidMount() { //code } //這裏有個注意點就是使用時,咱們須要知道這個組件是被包裝以後的組件 //將ref值傳遞給 forwardedRef 的 prop render() { const {forwardedRef, ...remainingProps} = this.props; return ( <WrappedComponent ref={forwardedRef} {...remainingProps}/> ) } } //指定 displayName. 未複製靜態方法(重點不是爲了講 HOC) ProxyComponent.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; //複製非 React 靜態方法 hoistNonReactStatic(ProxyComponent, WrappedComponent); return ProxyComponent; }
這個示例中,咱們將 ref
的屬性值經過 forwardedRef
的 prop
,傳遞給被包裝的組件,使用:
class MyInput extends React.Component { render() { return ( <input type="text" {...this.props} /> ) } } MyInput = withData(MyInput); function Form(props) { const inputRef = React.useRef(null); React.useEffect(() => { console.log(inputRef.current) }) //咱們在使用 MyInput 時,須要區分其是不是包裝過的組件,以肯定是指定 ref 仍是 forwardedRef return ( <MyInput forwardedRef={inputRef} /> ) }
React.forwardRef
Ref 轉發是一項將 ref 自動地經過組件傳遞到其一子組件的技巧,其容許某些組件接收 ref,並將其向下傳遞給子組件。
轉發 ref 到DOM中:
import React from 'react'; const MyInput = React.forwardRef((props, ref) => { return ( <input type="text" ref={ref} {...props} /> ) }); function Form() { const inputRef = React.useRef(null); React.useEffect(() => { console.log(inputRef.current);//input節點 }) return ( <MyInput ref={inputRef} /> ) }
React.useRef
建立了一個 React ref
並將其賦值給 ref
變量。ref
爲JSX屬性,並向下傳遞 <MyInput ref={inputRef}>
ref
給 forwardRef
內函數 (props, ref) => ...
做爲其第二個參數。ref
參數到 <button ref={ref}>
,將其指定爲JSX屬性ref
掛載完成,inputRef.current
指向 input
DOM節點注意
第二個參數 ref
只在使用 React.forwardRef
定義組件時存在。常規函數和 class 組件不接收 ref
參數,且 props
中也不存在 ref
。
在 React.forwardRef
以前,咱們若是想傳遞 ref
屬性給子組件,須要區分出是不是被HOC包裝以後的組件,對使用來講,形成了必定的不便。咱們來使用 React.forwardRef
重構。
import React from 'react'; import hoistNonReactStatic from 'hoist-non-react-statics'; function withData(WrappedComponent) { class ProxyComponent extends React.Component { componentDidMount() { //code } render() { const {forwardedRef, ...remainingProps} = this.props; return ( <WrappedComponent ref={forwardedRef} {...remainingProps}/> ) } } //咱們在使用被withData包裝過的組件時,只須要傳 ref 便可 const forwardRef = React.forwardRef((props, ref) => ( <ProxyComponent {...props} forwardedRef={ref} /> )); //指定 displayName. forwardRef.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; return hoistNonReactStatic(forwardRef, WrappedComponent); }
class MyInput extends React.Component { render() { return ( <input type="text" {...this.props} /> ) } } MyInput.getName = function() { console.log('name'); } MyInput = withData(MyInput); console.log(MyInput.getName); //測試靜態方法拷貝是否正常 function Form(props) { const inputRef = React.useRef(null); React.useEffect(() => { console.log(inputRef.current);//被包裝組件MyInput }) //在使用時,傳遞 ref 便可 return ( <MyInput ref={inputRef} /> ) }
舊版本中(V4 / V5)
咱們知道,connect
有四個參數,若是咱們想要在父組件中子組件(木偶組件)的實例,那麼須要設置第四個參數 options
的 withRef
爲 true
。隨後能夠在父組件中經過容器組件實例的 getWrappedInstance()
方法獲取到木偶組件(被包裝的組件)的實例,以下所示:
//MyInput.js import React from 'react'; import { connect } from 'react-redux'; class MyInput extends React.Component { render() { return ( <input type="text" /> ) } } export default connect(null, null, null, { withRef: true })(MyInput);
//index.js import React from "react"; import ReactDOM from "react-dom"; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import MyInput from './MyInput'; function reducer(state, action) { return state; } const store = createStore(reducer); function Main() { let ref = React.createRef(); React.useEffect(() => { console.log(ref.current.getWrappedInstance()); }) return ( <Provider store={store}> <MyInput ref={ref} /> </Provider> ) } ReactDOM.render(<Main />, document.getElementById("root"));
這裏須要注意的是:MyInput
必須是類組件,而函數組件沒有實例,天然也沒法經過 ref
獲取其實例。react-redux
源碼中,經過給被包裝組件增長 ref
屬性,getWrappedInstance
返回的是該實例 this.refs.wrappedInstance
。
if (withRef) { this.renderedElement = createElement(WrappedComponent, { ...this.mergedProps, ref: 'wrappedInstance' }) }
新版本(V6 / V7)
react-redux
新版本中使用了 React.forwardRef
方法進行了 ref
轉發。 自 V6 版本起,option
中的 withRef
已廢棄,若是想要獲取被包裝組件的實例,那麼須要指定 connect
的第四個參數 option
的 forwardRef
爲 true
,具體可見下面的示例:
//MyInput.js 文件 import React from 'react'; import { connect } from 'react-redux'; class MyInput extends React.Component { render() { return ( <input type="text" /> ) } } export default connect(null, null, null, { forwardRef: true })(MyInput);
直接給被包裝過的組件增長 ref
,便可以獲取到被包裝組件的實例,以下所示:
//index.js import React from "react"; import ReactDOM from "react-dom"; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import MyInput from './MyInput'; function reducer(state, action) { return state; } const store = createStore(reducer); function Main() { let ref = React.createRef(); React.useEffect(() => { console.log(ref.current); }) return ( <Provider store={store}> <MyInput ref={ref} /> </Provider> ) } ReactDOM.render(<Main />, document.getElementById("root"));
一樣,MyInput
必須是類組件,由於函數組件沒有實例,天然也沒法經過 ref
獲取其實例。
react-redux 中將 ref
轉發至 Connect
組件中。經過 forwardedRef
傳遞給被包裝組件 WrappedComponent
的 ref
。
if (forwardRef) { const forwarded = React.forwardRef(function forwardConnectRef( props, ref ) { return <Connect {...props} forwardedRef={ref} /> }) forwarded.displayName = displayName forwarded.WrappedComponent = WrappedComponent return hoistStatics(forwarded, WrappedComponent) } //... const { forwardedRef, ...wrapperProps } = props const renderedWrappedComponent = useMemo( () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />, [forwardedRef, WrappedComponent, actualChildProps] )
參考連接: