目前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隨之就來了:
原因是在Modal框的visible為false時, 網頁上根本不會載入Modal節點, 當然就獲取不到inputRef, inputRef.current
的結果就為null
, 下圖第一張圖為Modal框的visible為false時的DOM樹, 第二張圖為Modal框的visible為true時的DOM樹:
Modal框的visible為false時的DOM樹
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}
</>
)
}