函數式組件中實現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>
        </>
    )
}

結語

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

新解決辦法(2022/3/11)

此解決辦法來自於此帖子, 就是給所有modal彈框或者需要展示初始化數據的子組件增加一個展示判斷,顯示時重新初始化就行了.
通俗的說, 就是給Modal框套一個三元表達式, 如果visible為true才加載Modal框,這樣的話我們不需要維護onChange和一個state, 大大提高了性能, 代碼如下:

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 傳遞過來的是發送消息的函數和UE4中文本編輯框已有的內容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)

    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()
        // 如果內容不為空才發送消息, 具體邏輯代碼省略
        // 將modal對話框關閉
        dispatch(change_input_modal_visible(false));
    };
    
    // 取消發送消息
    const handleCancel = () => {
        // 將modal對話框關閉
        dispatch(change_input_modal_visible(false));
    };

    return (
        <>
            {inputModalVisible? <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> : null}
        </>
    )
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM