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= () => 
ref对象模式获取元素或组件
}

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  
ref对象模式获取元素或组件
}

问:在函数组件中为什么不能用 createRef ?

答:类组件有一个实例 instance 能够维护像 ref 这种信息,但函数组件每次更新时所有的变量都会重新声明,此时 ref 就会随着函数组件执行被重置。

React.useRef的内部实现:

hooks 和函数组件对应的 fiber 对象建立起关联,将 useRef 产生的 ref 对象挂到函数组件对应的 fiber 上,函数组件每次执行,只要组件不被销毁,函数组件对应的 fiber 对象一直存在,所以 ref 等信息就会被保存下来。


具体原理后面的hooks部分再详细讲解


类组件获取 Ref 三种方式

  1. Ref属性是一个字符串
/* 类组件 */
class Children extends Component{  
    render=()=>
hello,world
} /* TODO: Ref属性是一个字符串 */ export default class Index extends React.Component{ componentDidMount(){ console.log(this.refs) } render=()=>
字符串模式获取元素或组件
}

打印:
image

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

打印:
image

  1. Ref属性是一个ref对象
class Children extends React.Component{  
    render=()=>
hello,world
} 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=()=>
Ref对象模式获取元素或组件
}

打印:
image

ref的高级用法

  1. forwardRef 解决 ref 跨层级捕获和传递的问题。
// 表单组件
class Form extends React.Component{
    render(){
       return 
{...}
} } // 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
this.form = form } />
} } const ForwardRefIndex = React.forwardRef(( props,ref )=>) // home 组件 export default function Home(){ const ref = useRef(null) useEffect(()=>{ console.log(ref.current) },[]) return }
  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 
子组件

父组件对我说:{ fatherMes }

对父组件说
this.setState({ sonMes:e.target.value })} className="input" />
} } /* 父组件 */ 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
父组件

子组件对我说:{ sonMes }

对子组件说
setFatherMes(e.target.value) } className="input" />
}
  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 
} 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
(this.cur = cur)} />
} }。
  1. 函数组件缓存数据

    如果视图的更新不想依赖于改变的数据,可以将改变的数据存放在ref对象中。因为,只要组件没有销毁,ref 对象就一直存在。