react自定义组件中使用ref

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react自定义组件中使用ref相关的知识,希望对你有一定的参考价值。

参考技术A 自定义组件中使用ref需要用到react的2个hooks:
1.forwardRef
2.useImperativeHandle

自定义组件:

使用:

先使用 React.forwardRef;再使用 connect 包一层会使 ref 属性漏掉,导致 内部实例无法传到外部;
正确的操作方式要调整高阶组件的顺序,先用connect包裹,然后再用React.forwardRef包裹。

这样就可以同时使用2个高阶组件了。

React Hooks的使用——useRefuseImperativeHandleuseLayoutEffect解析自定义Hook

一、useRef

useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变

最常用的ref是两种用法:

  • 用法一:引入DOM(或者组件,但是需要是class组件)元素;
    案例一:引用DOM
import React, useRef from "react";

class TestCpn extends React.Component
    render() 
        return <h2>TestCpn</h2>
    


function TestCpn2(props) 
    return <h2>TestCpn2</h2>


export default function RefHookDemo01() 
    const titleRef = useRef()
    const inputRef = useRef()
    const testRef = useRef()
    const testRef2 = useRef()

    function changeDOM() 
        titleRef.current.innerHTML = 'hello world'
        inputRef.current.focus()
        console.log(testRef.current)
        console.log(testRef2.current)
    
    return (
        <div>
            <h2 ref=titleRef>RefHookDemo01</h2>
            <input type="text" ref=inputRef/>
            <TestCpn ref=testRef />
            <TestCpn2 ref=testRef2 />

            <button onClick=e => changeDOM()>修改DOM</button>
        </div>
    )


  • 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变;
    案例二:使用ref保存上一次的某一个值
import React, useEffect, useRef, useState from "react";

export default function RefHookDemo02() 
    const [count, setCount] = useState(0)
    const numRef = useRef(count)

    useEffect(() => 
        numRef.current = count
    , [count])
    return (
        <div>
            /*<h2>numRef中的值: numRef.current</h2>*/
            /*<h2>count中的值: count</h2>*/
            <h2>count上一次的值:numRef.current</h2>
            <h2>count当前的值:count</h2>
            <button onClick=e => setCount(count + 10)>+10</button>
        </div>
    )


二、useImperativeHandle

useImperativeHandle并不是特别好理解,我们一点点来学习。

我们先来回顾一下ref和forwardRef结合使用:

  • 通过forwardRef可以将ref转发到子组件;
  • 子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;
import React, forwardRef, useRef from "react";

const HYInput = forwardRef(
    (props,ref) => 
        return <input ref=ref type="text"/>
    
)

export default function ForwardRefDemo() 
    const inputRef = useRef()
    return (
        <div>
            <HYInput ref=inputRef/>
            <button onClick=e => inputRef.current.focus()>聚焦</button>
        </div>
    )


forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件:

  • 直接暴露给父组件带来的问题是某些情况的不可控;
  • 父组件可以拿到DOM后进行任意的操作;
  • 但是,事实上在上面的案例中,我们只是希望父组件可以操作的focus,其他并不希望它随意操作;

通过useImperativeHandle可以只暴露固定的操作:

  • 通过useImperativeHandle的Hook,将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起;
  • 所以在父组件中,使用 inputRef.current时,实际上使用的是返回的对象
  • 比如我调用了 focus函数;
import React, forwardRef, useImperativeHandle, useRef from "react";

const HYInput = forwardRef((props, ref) => 
    const inputRef = useRef()
    useImperativeHandle(ref, () => 
            return 
                focus: () => 
                    inputRef.current.focus()
                    console.log('useImperativeHandle中回调函数返回的对象里面的focus')
                
            
        , [inputRef.current])
        return <input ref=inputRef type="text"/>
    
)

export default function ForwardRefDemo02() 
    const inputRef = useRef()
    return (
        <div>
            <HYInput ref=inputRef/>
            <button onClick=e => inputRef.current.focus()>聚焦</button>
        </div>
    )


三、useLayoutEffect

useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:

  • useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
  • useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新

如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。
案例: useEffect和useLayoutEffect的对比


四、自定义Hook

自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。

需求0:所有的组件在创建和销毁时都进行打印

  • 组件被创建:打印 组件被创建了;
  • 组件被销毁:打印 组件被销毁了;

import React, useEffect from "react";
const Home = (props) => 
    useEffect(() => 
        console.log('Home组件被创建出来了~')
        return () => 
            console.log('Home组件被销毁了!')
        
    , [])
    return <h2>Home</h2>

const Profile = (props) => 
    useEffect(() => 
        console.log('Profile组件被创建出来了~')
        return () => 
            console.log('Profile组件被销毁了!')
        
    , [])
    return <h2>Profile</h2>

export default function CustomHookLifeDemo01() 
    useEffect(() => 
        console.log('CustomHookLifeDemo01组件被创建出来了~')
        return () => 
            console.log('CustomHookLifeDemo01组件被销毁了!')
        
    , [])
    return (
        <div>
            <h2>CustomHookLifeDemo01</h2>
            <Home />
            <Profile />
        </div>
    )


import React, useEffect from "react";
const Home = (props) => 
    useLoggingLife('Home')
    return <h2>Home</h2>

const Profile = (props) => 
    useLoggingLife('Profile')
    return <h2>Profile</h2>

export default function CustomHookLifeDemo01() 
    useLoggingLife('CustomHookLifeDemo01')
    return (
        <div>
            <h2>CustomHookLifeDemo01</h2>
            <Home />
            <Profile />
        </div>
    )

function useLoggingLife(name) 
    useEffect(() => 
        console.log(`$name组件被创建出来了~`)
        return () => 
            console.log(`$name组件被销毁了!`)
        
    , [])


需求一:Context的共享


import useContext from "react";
import TokenContext, UserContext from "../App";

function useUserContext() 
    const user = useContext(UserContext)
    const token = useContext(TokenContext)
    return [user, token]


export default useUserContext

需求二:获取鼠标滚动位置



需求三:localStorage数据存储



以上是关于react自定义组件中使用ref的主要内容,如果未能解决你的问题,请参考以下文章

React 自定义组件实现手写签名

React 自定义组件实现手写签名

React Hooks的使用——useRefuseImperativeHandleuseLayoutEffect解析自定义Hook

React-Native:将焦点设置到由数组构建的自定义组件

Vue父子组件间通信(数据传递)

React Refs的多种用法归纳整理