Hooks-useRef

365 阅读2分钟
/**createRef
 *      https://zhuanlan.zhihu.com/p/105276393
 * 1 与createRef 一样:用来获取组件实例对象或者是DOM对象
 * 2 createRef 每次渲染都会返回一个新的引用,
 *             而 useRef 每次都会返回相同的引用(“跨渲染周期”保存数据)。
 *             此特性可用于纯函数里定时器的使用 
 * 
 * */

import React,{useState,useRef,createRef, useEffect,useMemo  } from 'react';

//传统用法createRef
function FocusInput1 (){
    const inputElement = createRef();
    const change = () =>{
        console.log(inputElement.current)
        inputElement.current.focus();
    }
    return(
        <>
            <input ref={inputElement} type="text"/>
            <button onClick={change}>Focus input</button>
        </>
    )
}
//useRef Hook传统用法 
function FocusInput (){
    const inputElement = useRef();
    const change = () =>{
        console.log(inputElement.current)
        inputElement.current.focus();
    }
    return(
        <>
            <input ref={inputElement} type="text"/>
            <button onClick={change}>Focus input</button>
        </>
    )
}

/**createRef 每次渲染都会返回一个新的引用,
 * 而 useRef 每次都会返回相同的引用。 */
function Test (){
    const [renderId,setRenderId] = React.useState(1);
    const refUseRef = useRef();
    const refCreateRef = createRef();
    console.log(refUseRef.current)//undefined
    console.log(refCreateRef.current)//null
    //就算组件重新渲染, 由于 refUseRef 的值一直存在(类似于 this ) , 无法重新赋值. 
    if(!refUseRef.current){//点击后一直是1
        refUseRef.current = renderId;
    }
    if(!refCreateRef.current){//点击后一直是null
        refCreateRef.current = renderId;
    }

    return(
        <>
            <p>Current render index: {renderId}</p>
            <p>refUseRef:{refUseRef.current}</p>
            <p>refCreateRef:{refCreateRef.current}</p>
            <button onClick={()=>setRenderId(prev=>prev+1)}>
                Cause re-render</button>
        </>
    )
}

function Test2(){
    const [count,setCount] = useState(0);

    function handleClick(){
        setTimeout(()=>{
            alert("Click on:" + count);
        },3000)
    }
    return(
        <>
        {/**最终结果是 在count为6的时候, 点击 show alert , 
         *  再继续增加 count , 弹出的值为 6, 而非 10. 
         * 为什么不是界面上 count 的实时状态?
         *      当我们更新状态的时候,React 会重新渲染组件, 每一次渲染都会拿到独立的 count 状态, 并重新渲染一个 handleAlertClick 函数. 
         *      每一个 handleAlertClick 里面都有它自己的 count .
         * 如何让点击的时候弹出实时的 count ?
         *      看下一个例子
         * */}
            <p>click count:{count}</p>
            <button onClick={()=>setCount(count+1)}>Click add</button>
            <button onClick={handleClick}>alert</button>
        </>
    )
}
//何时使用useRef:当解决Test2组件问题时
function Test3(){
    const [count,setCount] = useState(0);
    const latestCount = useRef(count);
    console.log(latestCount.current)
    useEffect(()=>{
        latestCount.current = count;
        console.log(count)//注意,这里是异步的,所以下一行的console会先打印
    })
    console.log(latestCount.current)
    /**因为 useRef 每次都会返回同一个引用, 所以在 useEffect 中修改的时候 ,在 alert 中也会同时被修改.
     *  这样子, 点击的时候就可以弹出实时的 count 了. */
    function handleClick(){
        setTimeout(()=>{
            alert("Click on:" + latestCount.current);
        },3000)
    }
    return(
        <>
        {/**最终结果是 在count为6的时候, 点击 show alert , 
         *  再继续增加 count , 弹出的值为 6, 而非 10. 
         * 为什么不是界面上 count 的实时状态?
         *      当我们更新状态的时候,React 会重新渲染组件, 每一次渲染都会拿到独立的 count 状态, 并重新渲染一个 handleAlertClick 函数. 
         *      每一个 handleAlertClick 里面都有它自己的 count .
         * 如何让点击的时候弹出实时的 count ?
         *      看下一个例子
         * */}
            <p>latestCount:{latestCount.current}</p>
            <p>click count:{count}</p>
            <button onClick={()=>setCount(count+1)}>Click add</button>
            <button onClick={handleClick}>alert</button>
        </>
    )
}

//我们希望在界面上显示出上一个 count 的值.
//好好对比Test3的例子
 function Test4(){
    const [count,setCount] = useState(0);
    const preCountUseRef = useRef(count);

    useEffect(()=>{
        preCountUseRef.current = count;
    })
    return(
        <>
            <p>preCountUseRef:{preCountUseRef.current}</p>
            <p>click count:{count}</p>
            <button onClick={()=>setCount(count+1)}>Click add</button>
        </>
    )
}
//封装自定义hook:获取上一个值
const usePrevious = state =>{
    const ref = useRef();

    useEffect(()=>{
        ref.current = state;
    })
    return ref.current;
}
//调用自定义Hook
function App(){
    const [count,setCount] = useState(0);
    const prevCount = usePrevious(count);
    /**
     * 这样, 我们就可以简单的实现类组件中 componentDidUpdate 获取 prevProps 的值了.
        componentDidUpdate(prevProps){
            if(prevProps.id !== this.props.id){

            }
        }
     */
    return(
        <div>
            <button onClick={()=>setCount(count+1)}>+</button>
            <button onClick={()=>setCount(count-1)}>-</button>
            <p>Now:{count},before:{prevCount}</p>
        </div>
    )
}

//用在定时器,方便清楚定时器
export default function MyInterval(){
    const [count,setCount] = useState(0);
    const doubleCount = useMemo(()=>{
        return 2 * count;
    },[count]);

    const timerId = useRef();

    useEffect(()=>{
        timerId.current = setInterval(()=>{
            setCount(count => count+1);
        },1000);
    },[])

    useEffect(()=>{
        if(count > 10){
            //如果不用useRef定义timerId,则不能正确清除定时器
            clearInterval(timerId.current);
        }
    })
    
    return(
        <>
            <button onClick={()=>setCount(count+1)}>
                Count:{count},double:{doubleCount}
            </button>
        </>
    )
}