1. 程式人生 > 其它 >react進階第五講——ref

react進階第五講——ref

ref物件的建立

  1. 類元件 React.createRef
class Index extends React.Component{
    constructor(props){
       super(props)
       this.currentDom = React.createRef(null)
    }
    componentDidMount(){
        console.log(this.currentDom)
    }
    render= () => <div ref={ this.currentDom } >ref物件模式獲取元素或元件</div>
}

React.createRef 的底層邏輯:

export function createRef() {
  const refObject = {
    current: null,
  }
  return refObject;
}

createRef 建立了一個物件,物件上的 current 屬性,用於儲存通過 ref 獲取的 DOM 元素,元件例項等。

  1. 函式元件 useRef
export default function Index(){
    const currentDom = React.useRef(null)
    React.useEffect(()=>{
        console.log( currentDom.current ) // div
    },[])
    return  <div ref={ currentDom } >ref物件模式獲取元素或元件</div>
}

問:在函式元件中為什麼不能用 createRef ?

答:類元件有一個例項 instance 能夠維護像 ref 這種資訊,但函式元件每次更新時所有的變數都會重新宣告,此時 ref 就會隨著函式元件執行被重置。

React.useRef的內部實現:

hooks 和函式元件對應的 fiber 物件建立起關聯,將 useRef 產生的 ref 物件掛到函式元件對應的 fiber 上,函式元件每次執行,只要元件不被銷燬,函式元件對應的 fiber 物件一直存在,所以 ref 等資訊就會被儲存下來。


具體原理後面的hooks部分再詳細講解


類元件獲取 Ref 三種方式

  1. Ref屬性是一個字串
/* 類元件 */
class Children extends Component{  
    render=()=><div>hello,world</div>
}
/* TODO:  Ref屬性是一個字串 */
export default class Index extends React.Component{
    componentDidMount(){
       console.log(this.refs)
    }
    render=()=> <div>
        <div ref="currentDom"  >字串模式獲取元素或元件</div>
        <Children ref="currentComInstance"  />
    </div>
}

列印:

  1. Ref 屬性是一個函式。
class Children extends React.Component{  
    render=()=><div>hello,world</div>
}
/* TODO: Ref屬性是一個函式 */
export default class Index extends React.Component{
    currentDom = null
    currentComponentInstance = null
    componentDidMount(){
        console.log(this.currentDom)
        console.log(this.currentComponentInstance)
    }
    render=()=> <div>
        <div ref={(node)=> this.currentDom = node }  >Ref模式獲取元素或元件</div>
        <Children ref={(node) => this.currentComponentInstance = node  }  />
    </div>
}

列印:

  1. Ref屬性是一個ref物件
class Children extends React.Component{  
    render=()=><div>hello,world</div>
}
export default class Index extends React.Component{
    currentDom = React.createRef(null)
    currentComponentInstance = React.createRef(null)
    componentDidMount(){
        console.log(this.currentDom)
        console.log(this.currentComponentInstance)
    }
    render=()=> <div>
         <div ref={ this.currentDom }  >Ref物件模式獲取元素或元件</div>
        <Children ref={ this.currentComponentInstance }  />
   </div>
}

列印:

ref的高階用法

  1. forwardRef 解決 ref 跨層級捕獲和傳遞的問題。
// 表單元件
class Form extends React.Component{
    render(){
       return <div>{...}</div>
    }
}
// index 元件
class Index extends React.Component{ 
    componentDidMount(){
        const { forwardRef } = this.props
        forwardRef.current={
            form:this.form,      // 給form元件例項 ,繫結給 ref form屬性 
            index:this,          // 給index元件例項 ,繫結給 ref index屬性 
            button:this.button,  // 給button dom 元素,繫結給 ref button屬性 
        }
    }
    form = null
    button = null
    render(){
        return <div   > 
          <button ref={(button)=> this.button = button }  >點選</button>
          <Form  ref={(form) => this.form = form }  />  
      </div>
    }
}
const ForwardRefIndex = React.forwardRef(( props,ref )=><Index  {...props} forwardRef={ref}  />)
// home 元件
export default function Home(){
    const ref = useRef(null)
     useEffect(()=>{
         console.log(ref.current)
     },[])
    return <ForwardRefIndex ref={ref} />
}
  1. ref實現元件通訊

    1)類元件ref
/* 子元件 */
class Son extends React.PureComponent{
    state={
       fatherMes:'',
       sonMes:''
    }
    fatherSay=(fatherMes)=> this.setState({ fatherMes  }) /* 提供給父元件的API */
    render(){
        const { fatherMes, sonMes } = this.state
        return <div className="sonbox" >
            <div className="title" >子元件</div>
            <p>父元件對我說:{ fatherMes }</p>
            <div className="label" >對父元件說</div> <input  onChange={(e)=>this.setState({ sonMes:e.target.value })}   className="input"  /> 
            <button className="searchbtn" onClick={ ()=> this.props.toFather(sonMes) }  >to father</button>
        </div>
    }
}
/* 父元件 */
export default function Father(){
    const [ sonMes , setSonMes ] = React.useState('') 
    const sonInstance = React.useRef(null) /* 用來獲取子元件例項 */
    const [ fatherMes , setFatherMes ] = React.useState('')
    const toSon =()=> sonInstance.current.fatherSay(fatherMes) /* 呼叫子元件例項方法,改變子元件state */
    return <div className="box" >
        <div className="title" >父元件</div>
        <p>子元件對我說:{ sonMes }</p>
        <div className="label" >對子元件說</div> <input onChange={ (e) => setFatherMes(e.target.value) }  className="input"  /> 
        <button className="searchbtn"  onClick={toSon}  >to son</button>
        <Son ref={sonInstance} toFather={setSonMes} />
    </div>
}
  1. 函式元件 forwardRef + useImperativeHandle

    useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父元件的例項值。
// 子元件
function Son (props,ref) {
    const inputRef = useRef(null)
    const [ inputValue , setInputValue ] = useState('')
    useImperativeHandle(ref,()=>{
       const handleRefs = {
           onFocus(){              /* 宣告方法用於聚焦input框 */
              inputRef.current.focus()
           },
           onChangeValue(value){   /* 宣告方法用於改變input的值 */
               setInputValue(value)
           }
       }
       return handleRefs
    },[])
    return <div>
        <input placeholder="請輸入內容"  ref={inputRef}  value={inputValue} />
    </div>
}

const ForwarSon = forwardRef(Son)
// 父元件
class Index extends React.Component{
    cur = null
    handerClick(){
       const { onFocus , onChangeValue } =this.cur
       onFocus() // 讓子元件的輸入框獲取焦點
       onChangeValue('let us learn React!') // 讓子元件input  
    }
    render(){
        return <div style={{ marginTop:'50px' }} >
            <ForwarSon ref={cur => (this.cur = cur)} />
            <button onClick={this.handerClick.bind(this)} >操控子元件</button>
        </div>
    }
}。
  1. 函式元件快取資料

    如果檢視的更新不想依賴於改變的資料,可以將改變的資料存放在ref物件中。因為,只要元件沒有銷燬,ref 物件就一直存在。