react進階第五講——ref
阿新 • • 發佈:2021-12-06
ref物件的建立
- 類元件 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 元素,元件例項等。
- 函式元件 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 三種方式
- 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>
}
列印:
- 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>
}
列印:
- 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的高階用法
- 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} />
}
- 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>
}
- 函式元件 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>
}
}。
- 函式元件快取資料
如果檢視的更新不想依賴於改變的資料,可以將改變的資料存放在ref物件中。因為,只要元件沒有銷燬,ref 物件就一直存在。