react hooks系列之useRef

react hooks系列之useRef

react hooks是 react 16.8 引入的特性,這裏咱們經過對react-hook-form進行分析來了解成熟的庫是如何使用hook的。這將是一個系列,首先推薦 useRefreact

useRef

簡介

在react中,咱們使用Ref來獲取組件的實例或者DOM元素。咱們可使用兩種方式來建立 Refgit

import * as React from 'react'
import { useState, useEffect, useRef, createRef } from 'react'

export default () => {
    const ref1 = createRef<HTMLFormElement>()
    const ref2 = useRef<HTMLInputElement>()

    useEffect(() => {
        console.log(ref1)
        console.log(ref2)
    }, [])

    return (
        <form ref={ref1}>
            <label>用戶信息</label>
            <input type="text" ref={ref2} />
        </form>
    )
}

上面兩種方式都能在組件 mounted 以後獲取相關的DOM元素。在一個組件的正常的生命週期中能夠大體能夠分爲3個階段github

  1. 從建立組件到掛載到DOM階段。初始化props以及state, 根據state與props來構建DOM
  2. 組件依賴的props以及state狀態發生變動,觸發更新
  3. 銷燬階段

在第1階段,使用createRefuseRef二者是沒有區別。可是在第2階段, 也就是更新階段二者是有區別的。咱們知道,在一個局部函數中,函數每一次執行,都會在把函數的變量從新生成一次。api

export default () => {
    const ref1 = createRef<HTMLFormElement>()
    const ref2 = useRef<HTMLInputElement>()

    const [ count, setCount ] = useState<number>(0)

    useEffect(() => {
        if (!store.ref1) {
            store.ref1 = ref1
        } else {
            console.log(store.ref1 === ref1)
        }
    })
    
    useEffect(() => {
        setTimeout(() => {
            setCount(1)
        }, 1000)
    }, [])

    return (
        <form ref={ref1}>
            <label>用戶信息</label>
            <input type="text" ref={ref2} />
        </form>
    )
}

image.png
咱們使用一個外部的變量store來存儲初次所建立的ref,在咱們對組件進行更新後,會發現更新後的ref與咱們初次建立的ref其實並不一致。這樣也就意味着咱們每更新一次組件, 就從新建立一次ref數組

因爲有上面的問題,這在函數組件中,使用createRef去獲取ref是不合理的。因此hook給咱們提供一個新的API, 就是useRef。在useRef建立的ref彷彿就像外部定義的一個全局變量,不會隨着組件的更新而從新建立。但組件銷燬,它也會消失,不用手動進行銷燬。閉包

export default () => {
    const ref1 = createRef<HTMLFormElement>()
    const ref2 = useRef<HTMLInputElement>()

    const [ count, setCount ] = useState<number>(0)

    useEffect(() => {
        if (!store.ref1) {
            store.ref1 = ref1
        } else {
            console.log('ref1:', store.ref1 === ref1)
        }

        if (!store.ref2) {
            store.ref2 = ref2
        } else {
            console.log('ref2:', store.ref2 === ref2)
        }
    })

    useEffect(() => {
        setTimeout(() => {
            setCount(1)
        }, 1000)
    }, [])

    return (
        <form ref={ref1}>
            <label>用戶信息</label>
            <input type="text" ref={ref2} />
        </form>
    )
}

image.png

經過上面的說明,咱們知道useRef建立的ref並不會隨着組件的更新而從新構建。因爲這個特性,在使用react-hook的時候,可使用useRef來存儲常量。函數

useRef在react-hook-form中應用

如今回到咱們的主題,看看react-hook-form是如何處理ref。在react-hook-form有一個API爲register
源碼實現以下ui

...
function register(refOrValidationOptions, validationOptions) {
        if (isWindowUndefined) {
            return;
        }
        if (isString(refOrValidationOptions)) {
            registerFieldsRef({ name: refOrValidationOptions }, validationOptions);
            return;
        }
        if (isObject(refOrValidationOptions) && 'name' in refOrValidationOptions) {
            registerFieldsRef(refOrValidationOptions, validationOptions);
            return;
        }
        return (ref) => ref && registerFieldsRef(ref, refOrValidationOptions);
    }

使用閉包存儲了對當前輸入框的validationOptions, 返回的函數被ref的接收。這裏使用了ref另一種獲取方式‘回調refs’spa

import { useForm } from 'react-hook-form'


export default () => {
    const { register, errors, handleSubmit } = useForm()

    const submit = useCallback((data, e) => {
        console.log(data, e)
    }, [])

    useEffect(() => {
        console.log(errors)
    })
    
    return (
        <form onSubmit={handleSubmit(submit)}>
            <label>用戶信息</label>
            <input name="userName" ref={register({ required: true })} />
            {errors.userName && "Your input is required"}

            <button type={'submit'}>提交</button>
        </form>
    )
}

這裏爲何使用回調refs,而不是refs。其實理由很簡單,由於後面要引用的DOM元素或者React實例是未知,咱們是不知道使用者會把register註冊到INPUTTEXTAREA、仍是其餘的第三方組件。註冊一個仍是多個。使用回調refs咱們可以直接獲取到對應的真實DOM元素或者React實例,而使用了refs就會失去這種靈活性。code

若是咱們繼續日後面進行分析,會看到useForm這個hook中,使用了大量的useRef來存儲變量,緣由看前面。
image.png而前面經過register中調用的ref對象被註冊到filedsRef中。

相關文章
相關標籤/搜索