SolidJS(TypeScript)点击外部切换组件状态失效问题排查
SolidJS点击外部切换组件状态问题排查与修复
问题背景
正在学习SolidJS框架,尝试通过自定义指令实现点击组件外部时切换组件状态,但功能失效。同时尝试使用ref的方案也遇到TypeScript错误,需要技术支持。
一、自定义指令方案代码及问题
组件代码
import { Component, createSignal, ParentComponent, Show } from 'solid-js'; import clickOutside from '../directives/click-outside'; clickOutside; const EditorBlockMenu: Component<{fun: () => false}> = (prop) => { return ( <div class='editor-block-menu' use:clickOutside={prop.fun}> <div><button>Delete</button></div> <div><button>Move Up</button></div> <div><button>Move Down</button></div> </div> ) } const EditorBlockBtn: ParentComponent = () => { const [toggle, setToggle] = createSignal(false) const isToggle = () => setToggle(!toggle()) return ( <div classList={{'editor-block-btn': !toggle()}}> <button onClick={isToggle}> </button> <Show when={toggle()} fallback={<></>} > <EditorBlockMenu fun={() => setToggle(false)}/> </Show> </div> ) } export default EditorBlockBtn;
自定义指令代码
import { onCleanup } from "solid-js"; // https://www.solidjs.com/tutorial/bindings_directives?solved // need fix export default function clickOutside(el:Element, accessor: () => false) { const onClick = (e: Event) => !el.contains(e.target as Node) && accessor(); document.body.addEventListener("click", onClick); onCleanup(() => document.body.removeEventListener("click", onClick)); } declare module "solid-js" { namespace JSX { interface Directives { clickOutside: () => false; } } }
推测setToggle()函数在fun={}属性中无法正常工作,对TypeScript类型定义不太熟悉,需要帮助。
二、Ref方案代码及错误
尝试用ref实现功能,但出现TypeScript错误,代码如下:
const EditorBlockMenu: Component = () => { return ( <div class='editor-block-menu'> <div><button>Delete</button></div> <div><button>Move Up</button></div> <div><button>Move Down</button></div> </div> ) } const EditorBlockBtn: ParentComponent = () => { const [toggle, setToggle] = createSignal(false) const isToggle = () => setToggle(!toggle()) let ref: HTMLDivElement const handleClick = (event: MouseEvent) => { if(!ref.contains(event.target as Node)) { // 不加`as Node`会红标报错 setToggle(false) } } onMount(() => { document.addEventListener('click', handleClick); }) onCleanup(() => { document.removeEventListener('click', handleClick); }) return ( <div classList={{'editor-block-btn': !toggle()}}> <button onClick={isToggle}> </button> <Show when={toggle()} fallback={<></>} > <EditorBlockMenu ref={ref!}/> </Show> </div> ) } export default EditorBlockBtn;
错误提示:Type '{ ref: HTMLDivElement; }' is not assignable to type 'IntrinsicAttributes & { children?: any; }'. Property 'ref' does not exist on type 'IntrinsicAttributes & { children?: any; }'.
解决方案
1. 修复自定义指令方案
问题根源在于TypeScript类型定义错误,以及缺少事件冒泡阻止逻辑:
- 指令接收的回调函数类型应为无返回值的函数,而非
() => false - 点击组件内部时需阻止触发外部关闭逻辑,同时点击显示按钮时要阻止冒泡避免立即关闭
修改后的自定义指令代码
import { onCleanup } from "solid-js"; export default function clickOutside(el: Element, accessor: () => () => void) { const handler = accessor(); const onClick = (e: MouseEvent) => { // 点击当前元素内部时不执行回调 if (el.contains(e.target as Node)) return; handler(); }; document.body.addEventListener("click", onClick); onCleanup(() => document.body.removeEventListener("click", onClick)); } declare module "solid-js" { namespace JSX { interface Directives { clickOutside: () => void; } } }
修改后的组件代码关键部分
// 修正EditorBlockMenu的props类型 const EditorBlockMenu: Component<{fun: () => void}> = (prop) => { return ( <div class='editor-block-menu' use:clickOutside={prop.fun}> <div><button>Delete</button></div> <div><button>Move Up</button></div> <div><button>Move Down</button></div> </div> ) } // 修改按钮点击事件,阻止冒泡 const isToggle = (e: MouseEvent) => { e.stopPropagation(); setToggle(!toggle()) } // 按钮绑定修改后的事件 <button onClick={isToggle}>...</button>
2. 修复Ref方案
错误原因是EditorBlockMenu组件未定义ref属性,需调整组件接收ref并修正空值类型:
修改后的EditorBlockMenu组件
import { Component, mergeProps } from 'solid-js'; const EditorBlockMenu: Component<{ ref?: HTMLDivElement }> = (props) => { const merged = mergeProps({ ref: undefined }, props); return ( <div ref={merged.ref} class='editor-block-menu'> <div><button>Delete</button></div> <div><button>Move Up</button></div> <div><button>Move Down</button></div> </div> ) }
修改后的EditorBlockBtn关键部分
// 允许ref为undefined,避免空值报错 let ref: HTMLDivElement | undefined; // 同样给按钮点击事件添加阻止冒泡 const isToggle = (e: MouseEvent) => { e.stopPropagation(); setToggle(!toggle()) }
内容的提问来源于stack exchange,提问作者Pahan
相关产品推荐
相关产品推荐

