InputEllipsis 实现

背景 需要一种在 disabled 情况下会显示 Tooltip 的输入框 实现 // index.tsx import { Form, Input, InputProps, Tooltip } from "antd"; import styles from "./index.module.less"; import React from "react"; import type { FormItemProps, InputRef } from "antd"; export const InputEllipsis = (props: InputProps) => { const { value, disabled, className, ...restProps } = props; const [isTooltipCouldVisible, setIsTooltipCouldVisible] = React.useState(false); const inputRef = React.useRef<InputRef>(null); // 检测文本是否溢出 const checkOverflow = () => { const inputElement = inputRef.current; if (inputElement) { // 获取 input 元素实际的输入框部分 const { input } = inputElement; if (input) { // 使用 clientWidth(可视区域宽度)和 scrollWidth(实际内容宽度)比较 const { clientWidth, scrollWidth } = input; setIsTooltipCouldVisible(scrollWidth > clientWidth); } } }; // 监听输入框内容变化和尺寸变化 React.useEffect(() => { checkOverflow(); }, [value]); React.useEffect(() => { window.addEventListener("resize", checkOverflow); return () => { window.removeEventListener("resize", checkOverflow); }; }, []); if (disabled) { return ( <Tooltip title={value} {...(isTooltipCouldVisible ? {} : { open: false })} > <span> <Input ref={inputRef} value={value} disabled={disabled} placeholder={"请输入"} {...restProps} className={`${styles.customEllipsisInput} ${className || ""}`} /> </span> </Tooltip> ); } else { return ( <Input value={value} disabled={disabled} className={className} placeholder={"请输入"} {...restProps} /> ); } }; interface InputEllipsisFormItemProps extends FormItemProps { fieldProps?: InputProps; } export const InputEllipsisFormItem = (props: InputEllipsisFormItemProps) => { const { fieldProps, ...restProps } = props; return ( <Form.Item {...restProps}> <InputEllipsis {...fieldProps} /> </Form.Item> ); }; // index.module.less .customEllipsisInput { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

六月 4, 2025 · Conor

React 输入法 Input 优化

背景 中文输入法触发次数过多或经过状态库异步处理后输入法无法正常使用的问题 原理 使用 composition 事件检测输入法输入 实现 首先动手确认下 compositionStart,compositionEnd,onChange 三者的顺序关系 <input onCompositionStart={() => { console.log("start"); }} onChange={() => { console.log("change"); }} onCompositionEnd={() => { console.log("end"); }} /> chrome safari firefox 可以看到,顺序都是 start、change、end 结果 支持受控 无需进行浏览器判断,目前浏览器行为均一致 import { Input, InputProps } from "antd"; import { useRef, useState, forwardRef, useEffect } from "react"; /** * https://github.com/facebook/react/issues/3926#issuecomment-1200414788 */ export const InputIME = forwardRef<any, InputProps>((props, ref) => { const { onChange, value, ...otherProps } = props; const [inputValue, setInputValue] = useState(""); useEffect(() => { setInputValue(value as string); }, [value]); const onComposition = useRef(false); return ( <Input // 其他默认值 placeholder="请输入文本" autoComplete="off" {...otherProps} ref={ref} value={inputValue} onCompositionStart={() => { onComposition.current = true; }} onChange={(e) => { if (onComposition.current) { setInputValue(e.target.value); } else { onChange?.(e); } }} onCompositionEnd={(e) => { onComposition.current = false; onChange?.(e); }} /> ); }); 参考 https://github.com/facebook/react/issues/3926#issuecomment-1200414788 personal-blog/开发遇到的小问题合集/解决使用输入法输入在 React input 框中的问题.md at master · Jacky-Summer/personal-blog react-composition-input/src/inputfield.js at master · LeoEatle/react-composition-input ‍

五月 9, 2025 · Conor

Valtio 原理

前言 下面所说的都是非嵌套对象的情况 分为两个部分,vanilla 和 react Vanilla 核心:为可变状态添加不变性 检测突变 使用Proxy的set来检测突变,使用版本号来标记这个对象已经发生了变化 let version = 0; const p = new Proxy( {}, { set(target, prop, value) { ++version; target[prop] = value; }, } ); p.a = 10; console.log(version); // ---> 1 ++p.a; console.log(version); // ---> 2 快照 当调用snapshot的时候,根据版本号创建快照 let version = 0; let lastVersion; let lastSnapshot; const p = new Proxy( {}, { set(target, prop, value) { ++version; target[prop] = value; }, } ); const snapshot = () => { if (lastVersion !== version) { lastVersion = version; lastSnapshot = { ...p }; } return lastSnapshot; }; p.a = 10; console.log(snapshot()); // ---> { a: 10 } p.b = 20; console.log(snapshot()); // ---> { a: 10, b: 20 } ++p.a; ++p.b; console.log(snapshot()); // ---> { a: 11, b: 21 } 事件订阅 let version = 0; const listeners = new Set(); const p = new Proxy( {}, { set(target, prop, value) { ++version; target[prop] = value; listeners.forEach((listener) => listener()); }, } ); const subscribe = (callback) => { listeners.add(callback); const unsubscribe = () => listeners.delete(callback); return unsubscribe; }; subscribe(() => { console.log("mutated!"); }); p.a = 10; // shows "mutated!" ++p.a; // shows "mutated!" p.b = 20; // shows "mutated!" React 核心:useSyncExternalStore and proxy-compare ...

四月 8, 2024 · Conor