SolidJS Typescript 开发指南(5) 自定义指令

自定义指令Directives

把自定义指令单独拿出来是因为在TypeScript下这里我也踩了不少坑,这里连同我遇到的问题一起说一下。

SolidJS使用use namespace绑定到一个原生dom上,相当于一个ref的语法糖,同一个dom可以绑定多个指令且不会产生冲突。
自定义指令是一个具有两个参数的函数

type Accessor<T> = () => T
interface Directives {
	[x: string]: (el: Element, accessor: Accessor<any>) => void;
}

el参数为绑定的dom,而accessor为指令传入的回调函数
注意: 指令不适用于非原生dom也就是自定义组件,可行的解决方办法是使用props去传递回调函数


下面给出官网案例,按照官方文档中的描述:

<div class="modal" use:clickOutside={() => setShow(false)}>
  Some Modal
</div>
// clickOutside.ts
export default function clickOutside(el, accessor) {
  const onClick = (e) => !el.contains(e.target) && accessor()?.();
  document.body.addEventListener("click", onClick);

  onCleanup(() => document.body.removeEventListener("click", onClick));
}

这里编译前直接报错,要给原生dom上指令,所以只能从namespace上下手

不能将类型“{ children: string; "use:clickOutside": () => void; }”分配给类型“IntrinsicAttributes & ButttonProps”。
  类型“IntrinsicAttributes & ButttonProps”上不存在属性“use:clickOutside”。

经过一系列查询之后,发现官方大佬给出了正确的书写方式

declare module "solid-js" {
    namespace JSX {
        interface Directives {
            clickOutside?: () => void;
        }
    }
}

加上这一段之后代码确实没有飘红了,但奇怪的是我的ide依然提示引入的clickOutside是灰色的,而这个自定义指令也并没有正常加载

import type { Component } from "solid-js";
import { clickOutside } from "../directives/clickOutside";
const Index: Component = () => {
    console.log(clickOutside); //这里正常输出clickOutside返回的内容,但是指令并未生效
    return (
        <div>
            <button
                use:clickOutside={() => {
                    console.log("clickOutside");
                }}
            >
                Click
            </button>
        </div>
    );
};

export default Index;

推测可能是由于babel在tree shaking的时候把这个变量删掉了,导致SolidJS读取不到这个指令
查了一下vite相关的设定,给出的设置是

// vite.config.ts
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

export default defineConfig({
  plugins: [solidPlugin({ babel: { onlyRemoveTypeImports: true } })],
  // ...
}) 

但这里依然会报错

[vite] Internal server error: Unknown option: .onlyRemoveTypeImports. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.

之后我搜索到这个设置并不是在babel下,应该是这样的

// vite.config.ts
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

export default defineConfig({
  plugins: [solidPlugin({ typescript: { onlyRemoveTypeImports: true } })],
  // ...
})

并且要在全局内保持对指令的引用
因此改造clickOutside.tsx文件

import { onCleanup } from "solid-js";
declare module "solid-js" {
    namespace JSX {
        interface Directives {
            clickOutside?: () => void;
        }
    }
}
export const clickOutside = function (el: Element, accessor: () => Function) {
    console.log("active");
    const onClick = (e: any) => !el.contains(e.target) && accessor()?.();
    document.body.addEventListener("click", onClick);

    onCleanup(() => document.body.removeEventListener("click", onClick));
};

export const useDirective = (fn: (el: HTMLElement, accessor: () => Function) => void) => {};

挂载指令之前使用useDirective包裹指令

import type { Component } from "solid-js";
import { clickOutside, useDirective } from "../directives/clickOutside";
useDirective(clickOutside);
const Index: Component = () => {
    return (
        <div>
            <button
                use:clickOutside={() => {
                    console.log("clickOutside");
                }}
            >
                Click
            </button>
        </div>
    );
};

export default Index;

此时控制台输出active说明指令被正常挂载在dom上了

自定义指令是我在官方文档上踩过最大的坑,而且自定义指令无法支持自定义组件,因为官方文档没有给出TypeScript的相关设置,stackoverflow也没有给出什么有用的信息,我一度怀疑官方对于ts的支持是消极态度,还好在各种查询后解决了官方文档未提及的问题。

本文链接:https://blog.csdn.net/weixin_41907106/article/details/126348166


版权声明:本文为weixin_41907106原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。