React 函數組件中對window添加事件監聽resize導致回調不能獲得Hooks最新狀態的問題解決思路
這幾天在忙着把自己做的項目中的類組件轉化為功能相同的函數組件,首先先貼一份該組件類組件的關鍵代碼:
import React, {Component} from "react"
// 防抖組件
import { debounce } from 'throttle-debounce';
// 引入connect用於連接UI組件與redux
import {connect} from "react-redux"
class HandSendMsg extends Component {
state = {
w_width:window.innerWidth,
w_height:window.innerHeight,
}
// 向UE4像素流發送消息
emitMessageToUE4 = (msgObj) => {
// this.props.video_ref證明已經掛載了UE4像素流推送標簽組件
if(this.props.video_ref && this.props.video_ref.current && this.props.video_ref.current.emitMessage instanceof Function) {
this.props.video_ref.current.emitMessage(msgObj);
}
}
// 修改分辨率的函數回調
handleResize = () => {
let w_width = window.innerWidth
let w_height = window.innerHeight
this.setState({w_width, w_height})
this.emitMessageToUE4({
time:Date.now(),
width:w_width,
height:w_height,
funcName:"onResize"
})
}
componentDidMount() {
// 給全局綁定一個修改分辨率的操作, 當窗口發生變化的時候就修改分辨率,加入了防抖操作
window.addEventListener("resize", debounce(300, false, this.handleResize.bind(this)))
}
render() {
const {w_height, w_width, iconLocList } = this.state
return (
<div>
</div>
)
}
}
// 使用connect()()創建並暴露一個Count的容器組件
export default connect(
state => ({
signal_state:state.signal,
video_ref:state.videoRef
}),
{
}
)(HandSendMsg)
然后我試着改寫成函數組件,初始關鍵代碼是這樣的:
import React, { useState, useEffect } from 'react'
// 防抖組件
import { debounce } from 'throttle-debounce';
// 引入useSelector用於讀取redux store中保存的數據
import { useSelector } from "react-redux"
export default function HandSendMsg() {
// state數據
const [wh, setWh] = useState({
width:window.innerWidth,
height:window.innerHeight
})
// 標志是否開始監聽UE4消息,確保只監聽一次
const [start_listen, setStart_listen] = useState(false)
// store里保存的數據
const signal_state = useSelector(state => state.signal)
const video_ref = useSelector(state => state.videoRef)
// 向UE4像素流發送消息
const emitMessageToUE4 = (msgObj) => {
// video_ref不為null證明已經掛載了UE4像素流推送組件
if(video_ref && video_ref.current && video_ref.current.emitMessage instanceof Function) {
video_ref.current.emitMessage(msgObj);
}
}
// 修改分辨率的函數回調
function handleResize() {
let w_width = window.innerWidth
let w_height = window.innerHeight
emitMessageToUE4({
time:Date.now(),
width:w_width,
height:w_height,
funcName:"onResize"
})
setWh({
width:w_width,
height:w_height
})
}
useEffect(() => {
// 給全局綁定一個修改分辨率的操作, 當窗口發生變化的時候就修改分辨率
window.addEventListener('resize',debounce(300,false,handleResize))
// eslint-disable-next-line
}, [])
return (
<div>
</div>
)
}
useState鈎子替代類組件中的state,useEffect副作用鈎子替代類組件中的生命周期鈎子(componentDidMount(), componentDidUpdate()和 componentWillUnmount()), useSelector和useDispatch來取代redux中的connect,這些都沒什么好說的,但是在我給window添加onresize的事件監聽的時候, 卻發現只能獲取到hooks的初始值,里面函數的調用根本無法獲得最新的hooks值,無法更新state值:
window.addEventListener('resize',debounce(300,false,handleResize))
於是我在return里加了幾個button,一個button用來獲取最新的hooks的值,一個button用來調用window的onresize所調用的handleResize函數,發現在外面可以進行最新hooks的獲取和更新,如下圖所示:

在費勁周折之后,在網上看到帖子說可以對這個useEffect進行優化,使其監聽一個變量,從而來使事件的綁定有效,我想了一下,要想使信息發送過去,我必須得確保signal_state這個值為true,那么我就用它來進行監聽吧,當其從false改為true的時候再開啟onResize的事件監聽,問題不就解決了么。然后實踐了一下,果然可以了,關鍵代碼如下:
import React, { useState, useEffect } from 'react'
// 防抖組件
import { debounce } from 'throttle-debounce';
// 引入useSelector用於讀取redux store中保存的數據
import { useSelector } from "react-redux"
export default function HandSendMsg() {
// state數據
const [wh, setWh] = useState({
width:window.innerWidth,
height:window.innerHeight
})
// 標志是否開始監聽UE4消息,確保只監聽一次
const [start_listen, setStart_listen] = useState(false)
// store里保存的數據
const signal_state = useSelector(state => state.signal)
const video_ref = useSelector(state => state.videoRef)
// 向UE4像素流發送消息
const emitMessageToUE4 = (msgObj) => {
// video_ref不為null證明已經掛載了UE4像素流推送組件
if(video_ref && video_ref.current && video_ref.current.emitMessage instanceof Function) {
video_ref.current.emitMessage(msgObj);
}
}
// 修改分辨率的函數回調
function handleResize() {
let w_width = window.innerWidth
let w_height = window.innerHeight
emitMessageToUE4({
time:Date.now(),
width:w_width,
height:w_height,
funcName:"onResize"
})
setWh({
width:w_width,
height:w_height
})
}
useEffect(() => {
// 給全局綁定一個修改分辨率的操作, 當窗口發生變化的時候就修改分辨率
// 只有signal_state即信令狀態由false改為true的時候才添加一次事件
if(signal_state) {
window.addEventListener('resize',debounce(300,false,handleResize))
}
// eslint-disable-next-line
}, [signal_state])
return (
<div>
</div>
)
}
至此,問題已經解決了,總體來說函數式組件由於市面上教程較少,坑也比較多,這次這個給window添加監聽事件的需要有一個變量同時監聽變化,使得useEffect()可以正確響應然后重新綁定監聽,為什么要使用useEffect()呢?因為我們的需求是必須確保只給window添加一次事件監聽,當然得要求是對的了,而useEffect()可以模擬componentDidMount(), 可以最快的解決。
如果大家還有什么疑問或者新的建議,希望大家可以在下面留言,我看到一定會回復的,謝謝!!!
