为什么我不能在事件处理程序中访问功能组件的道具?
Why can't I access my functional component's props within an event handler?
我在我的一个组件中遇到了一个奇怪的问题,即 props 和本地状态都没有出现在事件处理程序函数中。
export default function KeyboardState({layout, children}) {
// Setup local component state
const [contentHeight, setContentHeight] = useState(layout.height)
const [keyboardHeight, setKeyboardHeight] = useState(0)
const [keyboardVisible, setKeyboardVisible] = useState(false)
const [keyboardWillShow, setKeyboardWillShow] = useState(false)
const [keyboardWillHide, setKeyboardWillHide] = useState(false)
const [keyboardAnimationDuration, setKeyboardAnimationDuration] = useState(INITIAL_ANIMATION_DURATION)
// set up event listeners
useEffect(()=> {
let subscriptions = []
if (Platform.OS === 'ios') {
subscriptions = [
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler),
// other listeners
]
}
return ()=> subscriptions.forEach(subscription => subscription.remove())
}, [])
// ODD BEHAVIOR
console.log(layout) // object: {height: 852, width: 414, x: 0, y: 44}
console.log(contentHeight) // 550
const keyboardWillShowHandler = (event) => {
console.log(layout) // object: {height: 0, width: 0, x: 0, y: 0}
console.log(contentHeight) // 0
setKeyboardWillShow(true)
measureEvent(event)
}
什么可能导致这种情况发生?在事件处理程序中,我们应该可以访问本地组件属性和状态,所以我想不出一个解释。
我已经确认此组件在此期间没有收到更新的布局道具(通过使用 useEffect 并将布局作为依赖项传递给 watch)。对我来说,特别奇怪的是,事件处理程序中的日志记录仅为值为 0 的布局提供了正确的对象结构,而传入的布局属性没有改变。
编辑:
问题似乎出在我如何初始化 returns 布局的自定义挂钩上。我一直在将它初始化为一个属性为 0 的高度、宽度、x 和 y 的对象。不优雅的、特定于设备的解决方案是将它初始化为一个值与 iPhone X 的尺寸匹配的对象。但是我将如何动态地实现这一目标?为什么当组件未检测到新的传入道具值时,我的布局在事件处理程序中的 KeyboardState 中变回初始状态?
/*UseComponentSize.js (custom hook) */
const useComponentSize = (initial = initialState) => {
const [layout, setLayout] = useState(initial)
const onLayout = useCallback(event => {
console.log(event)
const newLayout = {...event.nativeEvent.layout}
newLayout.y = newLayout.y + (Platform.OS === 'android' ? Constants.statusBarHeight : 0)
setLayout(newLayout)
}, [])
return [layout, onLayout]
}
/*App.js*/
const [layout, onLayout] = useComponentSize()
return (
<View style={styles.container}>
<View style={styles.layout} onLayout={onLayout}>
<KeyboardState layout={layout}>
...
</KeyboardState>
</View>
</View>
)
编辑 2:
我通过将自定义挂钩中的布局初始化为 null 解决了这个问题,同时在布局不为 null 的情况下将我的 App.js 修改为 return。即:
/*App.js*/
const [layout, onLayout] = useComponentSize()
return (
<View style={styles.container}>
<View style={styles.layout} onLayout={onLayout}>
{layout && (
<KeyboardState layout={layout}>
...
</KeyboardState>
)}
</View>
</View>
)
我仍然不知道为什么我的 KeyboardState 组件 return 在事件处理程序之外正确地编辑了布局道具,而 return 在事件处理程序。
我的处理程序函数引用了一个陈旧的值,而不是更新后的状态。
原因是,,是我的处理程序属于定义它的渲染,因此后续的重新渲染不会改变事件侦听器,因此它仍然从其渲染中读取旧值。
这是通过添加一个 ref 跟踪布局状态的变化来解决的:
export default const useKeyboard = () => {
const [layout, _setLayout] = useState(null)
const layoutRef = useRef(layout) // initially null
const [contentHeight, setContentHeight] = useState(null)
const setLayout = (value) => {
layoutRef.current = value // keep ref update
_setLayout(value) // then call setState
}
// onLayout function applied to onLayout prop of view component within App.js
const onLayout = useCallback(event => {
const newLayout = {...event.nativeEvent.layout}
newLayout.y = newLayout.y + (Platform.OS === 'android' ? Constants.statusBarHeight : 0)
setLayout(newLayout) // calls modified setLayout function
setContentHeight(newLayout.height)
}, [])
useEffect(()=> {
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler)
// cleanup function not shown for brevity
}, [])
const keyboardWillShowHandler = (event) => {
setKeyboardWillShow(true)
measureEvent(event)
}
const measureEvent = (event) => {
const { endCoordinates: {height, screenY}, duration = INITIAL_ANIMATION_DURATION} = event
setContentHeight(screenY - layoutRef.current.y) // access ref value
}
return {layout: layoutRef.current, onLayout, contentHeight}
}
我认为在这里使用 ref 不是正确的解决方案。当任何依赖关系发生变化时,我们不会更新事件侦听器,并且每次都只调用旧函数,它具有旧数据足迹。
使用 useCallback 包装您的侦听器及其所需的 deps,然后将侦听器函数作为 dep 传递,以将事件侦听器添加到新函数。
const keyboardWillShowHandler = useCallback((event) => {
console.log(layout) // object: {height: 0, width: 0, x: 0, y: 0}
console.log(contentHeight) // 0
setKeyboardWillShow(true)
measureEvent(event)
}, [layout, contentHeight]);
useEffect(()=> {
let subscriptions = []
if (Platform.OS === 'ios') {
subscriptions = [
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler),
// other listeners
]
}
return ()=> subscriptions.forEach(subscription => subscription.remove())
}, [keyboardWillShowHandler])
我在我的一个组件中遇到了一个奇怪的问题,即 props 和本地状态都没有出现在事件处理程序函数中。
export default function KeyboardState({layout, children}) {
// Setup local component state
const [contentHeight, setContentHeight] = useState(layout.height)
const [keyboardHeight, setKeyboardHeight] = useState(0)
const [keyboardVisible, setKeyboardVisible] = useState(false)
const [keyboardWillShow, setKeyboardWillShow] = useState(false)
const [keyboardWillHide, setKeyboardWillHide] = useState(false)
const [keyboardAnimationDuration, setKeyboardAnimationDuration] = useState(INITIAL_ANIMATION_DURATION)
// set up event listeners
useEffect(()=> {
let subscriptions = []
if (Platform.OS === 'ios') {
subscriptions = [
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler),
// other listeners
]
}
return ()=> subscriptions.forEach(subscription => subscription.remove())
}, [])
// ODD BEHAVIOR
console.log(layout) // object: {height: 852, width: 414, x: 0, y: 44}
console.log(contentHeight) // 550
const keyboardWillShowHandler = (event) => {
console.log(layout) // object: {height: 0, width: 0, x: 0, y: 0}
console.log(contentHeight) // 0
setKeyboardWillShow(true)
measureEvent(event)
}
什么可能导致这种情况发生?在事件处理程序中,我们应该可以访问本地组件属性和状态,所以我想不出一个解释。
我已经确认此组件在此期间没有收到更新的布局道具(通过使用 useEffect 并将布局作为依赖项传递给 watch)。对我来说,特别奇怪的是,事件处理程序中的日志记录仅为值为 0 的布局提供了正确的对象结构,而传入的布局属性没有改变。
编辑:
问题似乎出在我如何初始化 returns 布局的自定义挂钩上。我一直在将它初始化为一个属性为 0 的高度、宽度、x 和 y 的对象。不优雅的、特定于设备的解决方案是将它初始化为一个值与 iPhone X 的尺寸匹配的对象。但是我将如何动态地实现这一目标?为什么当组件未检测到新的传入道具值时,我的布局在事件处理程序中的 KeyboardState 中变回初始状态?
/*UseComponentSize.js (custom hook) */
const useComponentSize = (initial = initialState) => {
const [layout, setLayout] = useState(initial)
const onLayout = useCallback(event => {
console.log(event)
const newLayout = {...event.nativeEvent.layout}
newLayout.y = newLayout.y + (Platform.OS === 'android' ? Constants.statusBarHeight : 0)
setLayout(newLayout)
}, [])
return [layout, onLayout]
}
/*App.js*/
const [layout, onLayout] = useComponentSize()
return (
<View style={styles.container}>
<View style={styles.layout} onLayout={onLayout}>
<KeyboardState layout={layout}>
...
</KeyboardState>
</View>
</View>
)
编辑 2:
我通过将自定义挂钩中的布局初始化为 null 解决了这个问题,同时在布局不为 null 的情况下将我的 App.js 修改为 return。即:
/*App.js*/
const [layout, onLayout] = useComponentSize()
return (
<View style={styles.container}>
<View style={styles.layout} onLayout={onLayout}>
{layout && (
<KeyboardState layout={layout}>
...
</KeyboardState>
)}
</View>
</View>
)
我仍然不知道为什么我的 KeyboardState 组件 return 在事件处理程序之外正确地编辑了布局道具,而 return 在事件处理程序。
我的处理程序函数引用了一个陈旧的值,而不是更新后的状态。
原因是,
这是通过添加一个 ref 跟踪布局状态的变化来解决的:
export default const useKeyboard = () => {
const [layout, _setLayout] = useState(null)
const layoutRef = useRef(layout) // initially null
const [contentHeight, setContentHeight] = useState(null)
const setLayout = (value) => {
layoutRef.current = value // keep ref update
_setLayout(value) // then call setState
}
// onLayout function applied to onLayout prop of view component within App.js
const onLayout = useCallback(event => {
const newLayout = {...event.nativeEvent.layout}
newLayout.y = newLayout.y + (Platform.OS === 'android' ? Constants.statusBarHeight : 0)
setLayout(newLayout) // calls modified setLayout function
setContentHeight(newLayout.height)
}, [])
useEffect(()=> {
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler)
// cleanup function not shown for brevity
}, [])
const keyboardWillShowHandler = (event) => {
setKeyboardWillShow(true)
measureEvent(event)
}
const measureEvent = (event) => {
const { endCoordinates: {height, screenY}, duration = INITIAL_ANIMATION_DURATION} = event
setContentHeight(screenY - layoutRef.current.y) // access ref value
}
return {layout: layoutRef.current, onLayout, contentHeight}
}
我认为在这里使用 ref 不是正确的解决方案。当任何依赖关系发生变化时,我们不会更新事件侦听器,并且每次都只调用旧函数,它具有旧数据足迹。
使用 useCallback 包装您的侦听器及其所需的 deps,然后将侦听器函数作为 dep 传递,以将事件侦听器添加到新函数。
const keyboardWillShowHandler = useCallback((event) => {
console.log(layout) // object: {height: 0, width: 0, x: 0, y: 0}
console.log(contentHeight) // 0
setKeyboardWillShow(true)
measureEvent(event)
}, [layout, contentHeight]);
useEffect(()=> {
let subscriptions = []
if (Platform.OS === 'ios') {
subscriptions = [
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler),
// other listeners
]
}
return ()=> subscriptions.forEach(subscription => subscription.remove())
}, [keyboardWillShowHandler])