函數式組件中實現Antd打開Modal後其Input框自動聚焦(focus)到文字的最後

目前React使用函數式組件已經成為趨勢, 如何把React函數式組件用好, 提高性能, 從而實現業務需求也成為了一種能力的體現……咳咳咳, 進入正題:

現實場景需求

我想實現這一個需求, 父組件收到文字消息後將其傳送給子組件, 子組件是一個Antd組件中的Modal, 裡面只有一個input輸入框, 子組件收到父組件傳過來的文字消息後打開Modal對話框, 其input輸入框中的默認值為父組件傳遞過來的文字消息, 並且自動focus到文字消息的最後, 從而方便用戶輸入, 當輸入完之後, 點擊確定按鈕調用函數再把消息發送回去.

遇到的問題

怎麼說呢, 我本來是打算使用一個給input輸入框一個ref(inputRef), 然後當modal框打開時, 使用useEffect副作用鉤子, 然後通過使用inputRef的foucs函數來實現自動聚焦, 然而想像是美好的, 現實是殘酷的, 如果這麼容易能解決我就不會寫部落格了, 先上錯誤程式碼:

import React, { useRef, useEffect } from 'react'
import { Input, Modal } from 'antd'
// 引入useSelector用於讀取redux store中保存的數據, 引入useDispath用於分發數據
import { useSelector, useDispatch } from "react-redux"
// 引入action
import { change_input_modal_visible } from "../../redux/actions/visible"
const { TextArea } = Input


// props 傳遞過來的是發送消息的函數和文本編輯框已有的內容textContent
export default function InputModal(props) {
    const inputRef = useRef()
    // store dispatch
    const dispatch = useDispatch()
    // store里保存的數據, 來控制modal是否顯示, 父組件收到文本框編輯消息後會改為true, 從而顯示modal對話框
    const inputModalVisible = useSelector(state => state.visible.inputModalVisible)

    // 如果inputModalVisible為true, 聚焦input框
    useEffect(() => {
        if(inputModalVisible) {
            inputRef.current.focus({
                cursor: 'end'
            })
        }
    }, [inputModalVisible, inputRef])

    const handleOk = () => {
        let textValue  = inputRef.current.resizableTextArea.props.value
        console.log(textValue)
        // 去除前後多餘的空格
        let res = textValue.trim()
        // 如果內容不為空才發送給UE4程式, 具體發送邏輯省略
        // 將modal對話框關閉
        dispatch(change_input_modal_visible(false));
    };
    
    // 取消發送消息
    const handleCancel = () => {
        // 將modal對話框關閉
        dispatch(change_input_modal_visible(false));
    };

    return (
        <div className='modal_wrapper'>
            <Modal
                centered
                closable={false}
                destroyOnClose
                title={null}
                visible={inputModalVisible}
                onOk={handleOk}
                onCancel={handleCancel}
                cancelText="取消"
                okText="確定"
            >
                <TextArea
                    showCount
                    maxLength={100}
                    placeholder="請輸入內容"
                    allowClear
                    defaultValue={props.textContent}
                    ref={inputRef}
                />
            </Modal>
        </div>
    )
}

但是bug隨之就來了:
image
原因是在Modal框的visible為false時, 網頁上根本不會載入Modal節點, 當然就獲取不到inputRef, inputRef.current的結果就為null, 下圖第一張圖為Modal框的visible為false時的DOM樹, 第二張圖為Modal框的visible為true時的DOM樹:
image
Modal框的visible為false時的DOM樹
image
Modal框的visible為true時的DOM樹
既然問題找到了, 那就提一下我目前的解決辦法吧!

解決辦法

我的解決辦法是利用ref的原理, 當input輸入框掛載時, 使用ref進行聚焦, 關鍵程式碼片段如下:

<TextArea
	ref={(input) => {
		if (input != null) {
			input.focus({
				cursor: 'end'
			});
		}
	}}
>

但是隨之還有一個問題, 我現在ref用來進行聚焦了, 我如何拿到input輸入框內的值呢? 我還要輸入框消息發送回去呢! 還好Input輸入框還有一個onChange函數, 我可以用這個來維護一個state來保存在state中, 既然思路有了, 就上一下源程式碼:

import React, { useState, useEffect } from 'react'
import { Input, Modal } from 'antd'
// 引入useSelector用於讀取redux store中保存的數據, 引入useDispath用於分發數據
import { useSelector, useDispatch } from "react-redux"
// 引入action
import { change_input_modal_visible } from "../../redux/actions/visible"
const { TextArea } = Input


// props 傳遞過來的是發送消息的函數和UE4中文本編輯框已有的內容textContent
export default function InputModal(props) {
    // 在state中保存目前輸入框內的內容, 初始化為空字元串
    const [textValue, setTextValue] = useState('')
    // store dispatch
    const dispatch = useDispatch()
    // store里保存的數據, 來控制modal是否顯示, 父組件收到文本框編輯消息後會改為true, 從而顯示modal對話框
    const inputModalVisible = useSelector(state => state.visible.inputModalVisible)

    // 當props中textContent發生變化時, 即收到文本編輯框內容消息更新之後
    // 同步更新保存在textValue中
    useEffect(() => {
        setTextValue(props.textContent)
    }, [props.textContent])

    const handleOk = () => {
        // 發送消息
        console.log(textValue)
        // 去除前後多餘的空格
        let res = textValue.trim()
        // 如果內容不為空才發送給UE4程式, 具體發送邏輯不再展示
        // 將modal對話框關閉
        dispatch(change_input_modal_visible(false));
    };
    
    // 取消發送消息
    const handleCancel = () => {
        // 將modal對話框關閉
        dispatch(change_input_modal_visible(false));
    };

    const handleChange = e => {
        // 當輸入框內容發生變化時, 同步給textValue
        setTextValue(e.target.value)
    }

    return (
        <>
            <Modal
                centered
                closable={false}
                destroyOnClose
                title={null}
                visible={inputModalVisible}
                onOk={handleOk}
                onCancel={handleCancel}
                cancelText="取消"
                okText="確定"
            >
                <TextArea
                    showCount
                    maxLength={100}
                    placeholder="請輸入內容"
                    allowClear
                    defaultValue={props.textContent}
                    ref={(input) => {
                        if (input != null) {
                            input.focus({
                                cursor: 'end'
                            });
                        }
                    }}
                    onChange={handleChange}
                />
            </Modal>
        </>
    )
}

結語

至此, 本篇結束, 如果大家有更好的方法, 希望大家提出來, 或者有不懂的也可以留言, 感謝!