React - 自定义Hooks - useCompositions
前言
在工作中时常会遇到这样一个场景:页面上方有一个搜索框,根据搜索关键字去请求接口获取列表数据,将数据渲染至下方列表中做展示。
这时我们会遇到一个问题:在输入框中输入中文时,会发现每输入一个中文拼音字符,都会触发一次 Input 输入框 onChange 事件,进而触发请求接口逻辑;
这显然不是我们所期望的,我们希望可以在输入中文拼音并按下空格选择中文后,触发接口请求。
这时我们就需要使用 onCompositionStart 和 onCompositionEnd 事件来处理中文拼音的触发请求时机。
下面将介绍两种解决中文拼音输入问题的方式:
普通方式;
自定义 Hook 方式(基于普通方式封装为一个 Hook 实现功能复用)。
普通方式:
Input 基础示例:
import React { useState, useRef } from 'react'; const App = () => { const [value, setValue] = useState<string>(''); const onChange = (event: React.ChangeEvent<TInputElement>) => { setValue(event.target.value); fetchListDataApi(); // 伪代码,强调用做请求数据的独立方法 } return ( <Input value={value} onChange={onChange} /> ) } 复制代码
定义 compositionLock:
compositionLock 是一个 boolean 值,用于在 onCompositionStart 和 onCompositionEnd 中控制输入中文拼音的一个 锁
。
这里我们借助 useRef 对数据持久化的特性,来保存 compositionLock
值。
const App () => { // ... const compositionLockRef = React.useRef<boolean>(false); // ... } 复制代码
onCompositionStart 和 onCompositionEnd 事件定义:
input 元素提供了 onCompositionStart 和 onCompositionEnd 这两个 DOM 事件,在事件对象 event 中可以根据 type 区分是 start 还是 end,因此可以复用一个处理函数。
完整代码如下:
import React { useState, useRef } from 'react'; const App = () => { const [value, setValue] = useState<string>(''); const compositionLockRef = React.useRef<boolean>(false); const onChange = (event: React.ChangeEvent<TInputElement>) => { setValue(event.target.value); // 将中文拼音更新到 state 和 view 中 if (compositionLockRef.current) return; // 允许输入中文时更新视图 value,但不触发数据逻辑 fetchListDataApi(event.target.value); // fetch API } const onComposition = (event: React.CompositionEvent<TInputElement>) => { if (event.type === 'compositionend') { compositionLockRef.current = false; fetchListDataApi(event.currentTarget.value); // fetch API } else { compositionLockRef.current = true; } } return ( <Input value={value} onChange={onChange} onCompositionStart={onComposition} onCompositionEnd={onComposition} /> ) } 复制代码
自定义 Hook - useCompositions
从上面的实现可以看到,思路也比较简单清晰;
但试想:项目中有多处都用到了 input 搜索请求,显然 Copy 这样的代码不是最可取的选择,能不能有一种方式可以做到复用 onCompositionStart 和 onCompositionEnd 的处理逻辑呢?于是,useCompositions
诞生!
我们可以编写一个 Hook,由它来提供 compositionLock
锁和 onComposition
方法,外边不关注它的具体实现,只需要将 onComposition
方法绑定到 input 元素的 onCompositionStart 和 onCompositionEnd 事件上即可。
实现:
import React from 'react'; type TInputElement = HTMLInputElement | HTMLTextAreaElement; export interface CompositionsResult { value: string, setValue: React.Dispatch<React.SetStateAction<string>>, onChange: (event: React.ChangeEvent<TInputElement>) => void, onComposition: (event: React.CompositionEvent<TInputElement>) => void, } function useCompositions( defaultValue: string, onSearch?: (value: string) => void, ): CompositionsResult{ const [value, setValue] = React.useState<string>(defaultValue); const compositionLockRef = React.useRef<boolean>(false); const handleSearch = (value: string) => { onSearch && onSearch(value); } const onChange = (event: React.ChangeEvent<TInputElement>) => { const newValue = event.target.value; setValue(newValue); if (compositionLockRef.current) return; // 允许输入中文时更新视图 value,但不触发数据逻辑 handleSearch(newValue); } const onComposition = (event: React.CompositionEvent<TInputElement>) => { if (event.type === 'compositionend') { compositionLockRef.current = false; handleSearch(event.currentTarget.value); } else { compositionLockRef.current = true; } } return { value, setValue, onChange, onComposition } } export default useCompositions; 复制代码
使用:
const App = () => { // useDebounce 用于处理防抖的 hook const handlerSearch = useDebounce((val: string) => { fetchListDataApi(val); // fetch API }); const { value, onChange, onComposition } = useCompositions('', handlerSearch); return ( <Input value={value} onChange={onChange} onCompositionStart={onComposition} onCompositionEnd={onComposition} /> ) }
作者:明里人
链接:https://juejin.cn/post/7032960513025769508