为什么我不能在事件处理程序中访问功能组件的道具?

时间:2020-06-30 13:47:21

标签: javascript reactjs react-native event-handling

我在其中一个组件中遇到一个奇怪的问题,即在事件处理程序函数中道具和局部状态均未显示。

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)
  }

可能是什么原因导致这种情况发生?在事件处理程序中,我们应该可以访问本地组件的props和state,因此我想不出一个解释。

我已经确认此组件在此期间未收到更新的布局道具(通过使用useEffect并将布局作为要依赖的依赖传递)。对我来说,特别奇怪的是,事件处理程序中的日志记录仅在值为0的情况下为布局提供了正确的对象结构,而传入的布局属性未更改。

修改:
看来问题出在我如何初始化返回布局的自定义挂钩。我一直在将其初始化为具有高度,宽度,x和y属性为0的对象。具体于设备的解决方案是将其初始化为具有与iPhone X尺寸匹配的值的对象。但是我该怎么办?动态地实现这一目标?当组件未检测到新的传入prop值时,为什么在事件处理程序的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来解决此问题,同时修改了App.js以在布局不为null的情况下返回。那是:

/*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组件在事件处理程序外正确返回布局属性,而在事件处理程序内调用布局属性时却返回useComponentSize()初始状态。

1 个答案:

答案 0 :(得分:0)

我的处理程序函数引用的是陈旧的值,而不是更新的状态。

之所以per this question,是因为我的处理程序属于渲染定义的位置,因此后续的重渲染都不会更改事件侦听器,因此它仍从其渲染中读取旧值。

通过添加一个引用来跟踪布局状态的更改来解决此问题:

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}
}
相关问题