React Native实现模仿web端通过id获取input DOM
React Native中无法通过document.getElementById来获取input DOM,从而手动调用input.focus。以下介绍一种方法,通过context与ref来实现类似的效果。
React Native无法像web端一样绑定id,以下实现一个可传入id的input组件CIdInput,还有一个InputRefsContext,提供了CIdInput.focus的方法focusInput和通过id将CIdInput.ref注册的方法registerInput。
import { useMemoizedFn } from 'ahooks'
import {
createContext,
FC,
forwardRef,
memo,
PropsWithChildren,
useContext,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from 'react'
import { TextInput } from 'react-native'
import { CInput, CInputProps } from './input'
interface CIdInputRef {
focus: () => void
}
interface InputRefsContextType {
registerInput: (id: string, ref: CIdInputNumberRef) => void
// 通过调用focusInput来实现子孙组件中的CIdInput的聚焦效果
focusInput: (id: string) => void
}
export const InputRefsContext = createContext<InputRefsContextType | undefined>(undefined)
// CInputProps为自定义组件Input的props
interface CIdInputProps extends CInputProps {
id: string
}
// 获取context的hooks
export const useInputRefs = () => {
const context = useContext(InputRefsContext)
if (context === undefined) {
throw new Error('useInputRefs must be used within an InputRefsProvider')
}
return context
}
// 为了方便多层嵌套,利用contenxt.Provider
export const InputRefsContextProvider: FC<PropsWithChildren> = memo(({ children }) => {
const inputRefs = useRef<{ [key: string]: CIdInputNumberRef }>({})
const registerInput = useMemoizedFn((id: string, ref: CIdInputNumberRef) => {
inputRefs.current[id] = ref
})
const focusInput = useMemoizedFn((id: string) => {
const input = inputRefs.current[id]
if (input) input.focus()
})
const inputRefsContextValue = useMemo(() => {
return { registerInput, focusInput }
}, [registerInput, focusInput])
return <InputRefsContext.Provider value={inputRefsContextValue}>{children}</InputRefsContext.Provider>
})
const CIdInput = forwardRef<CIdInputRef, CIdInputProps>(({ id, ...props }, ref) => {
const inputRef = useRef<TextInput>(null)
const { registerInput } = useInputRefs()
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
}))
useEffect(() => {
// 根据id将input绑定到InputRefsContext.Provider的inputRefs中
if (inputRef.current) {
registerInput(id, inputRef.current)
}
}, [id, registerInput])
return <CInput {...props} ref={inputRef} />
})
export { CIdInput }
在我们嵌套多层组件时,需要在祖先组件ParentComponent通过id获取子孙组件中的input的dom,在web端可以直接使用document.getElementById去获取,在React Native中实现了上述CIdInput组件后,可以实现通过contextProvider实现效果。
// 深层嵌套的组件
const DeepNestedInputs: React.FC = () => {
return (
<View>
<CIdInput id="input1" placeholder="Input 1" />
<View>
<CIdInput id="input2" placeholder="Input 2" />
</View>
<View>
<View>
<CIdInput id="input3" placeholder="Input 3" />
</View>
</View>
</View>
);
}
const ParentComponent: React.FC = () => {
const { focusInput } = useInputRefs()
return (
<InputRefsContextProvider>
<View>
<DeepNestedInputs />
<Button title="Focus Input 1" onPress={() => focusInput?.('input1')} />
<Button title="Focus Input 2" onPress={() => focusInput?.('input2')} />
<Button title="Focus Input 3" onPress={() => focusInput?.('input3')} />
</View>
</InputRefsContextProvider>
)
}