学习《SolidJS》(五)Html标签的属性额外支持

231 阅读6分钟

HTML标签

对于原生标签来说,比如div,input,select,img等等标签,经常会用到value/type/class/src/multiple/还有一些事件等等属性。 SolidJS当然都提供了支持,但是对于事件来说写法会有区别,对于class提供了ClassList这个额外的支持。另外还提供了指令的支持。如果你是学过Vue或者React,style的的支持与它们还有一些区别。

本文章中所有的编译结果都只是假想的,只是逻辑上的,不代表真实的编译结果

事件

对于事件来说,写法上和React是一样的。

function EventHandle(){
    const handler = (event: MouseEvent) => {
        console.log("事件参数:", event)
        alert('你点击了按钮')
    }
    return (
        <button onClick={handler}>点我</button>
    )
}

当然了,一般这样用就行了。不过如果只是这样的话,我也没必要说了。SolidJS还提供了另外一个用法

function EventHandle(){
  const handler = (data: number, event: MouseEvent) => {
    console.log("事件参数:", event)
    alert(`你点击了按钮, 参数是${data}`)
  }
  return (
    <button onClick={[handler, 1]} >点我</button>
  )
}

也就是可以是一个数组(如果你会tsx的话,理解为元组会更好一些),第一个元素就是事件函数本身,第二个就是要传递给事件函数的参数,也就是对应data。

不过还是尽量少这样写比较好

首先第一个就是如果上面的1是一个响应式数据,那永远都是同一个值

import {createSignal} from "solid-js";

function EventHandle() {
  const [count, setCount] = createSignal(0);

  const handler = (data: number, event: MouseEvent) => {
    console.log("事件参数:", event)
    alert(`你点击了按钮, 参数是${data}`)
    setCount(pre => pre += 1)
  }
  return (
    <button onClick={[handler, count()]}>点我</button>
  )
}

如果你像上面这样写,count将永远是0。 因为上面这个代码onClick编译后,是直接将count()运行之后一次,然后保存到button这个dom中对象中

但是也不是说所有的事件编译后都一样,因为上面这个click是编译成了事件委托的形式,由document进行事件监听,然后再找到对应的函数调用。另外还有一种可能就是直接调用当前dom的addEventListener函数。

只是用到响应式数据,就不尽量要通过传参的形式来做,而是直接在函数里面使用,或者再套一层函数,就不要依赖于它的编译特异性了。

on编译后都是小写

比如,下面这段代码

function EventHandle(){
    const handler = (event: MouseEvent) => {
        console.log("事件参数:", event)
        alert('你点击了按钮')
    }
    return (
        <button onDOMContentLoaded={handler}>点我</button>
    )
}

它被编译之后可以理解为

//...省略上面的代码
button.addEventListener("domcontentloaded", handler);

不管原字符串的大小写,一律转成了小写。这显然不是我们需要的,因为这个事件是不存在的。官方推荐我们像下面这样写

function EventHandle(){
    const handler = (event: MouseEvent) => {
        console.log("事件参数:", event)
        alert('你点击了按钮')
    }
    return (
        <button on:DOMContentLoaded={handler}>点我</button>
    )
}

也就是on后面多了一个:符号,这样它就不会转换,而是直接使用原字符串,并且一定不会使用document进行事件委托。

还有一件事,它不支持[handler, 1]这种形式,因为是直接将{}中的内容原封不动的作为addEventListener的第二个参数。

自定义组件中的事件

在自定义组件中,肯定也有自定义的事件

function ChildComponent(props: {onClick(): void}){
    return (
        <button onClick={props.onClick}></button>
    )
}

function ParentComponent(){
    return (
        <ChildComponent onClick={() => console.log('点击了子组件')} />
    )
}

写法基本上就是上面这样,不过这个时候不用担心,它不会有任何转换,而是直接传递给子组件。 因为编译可以理解成,

function ParentComponent(){
    return () => {
        return ChildComponent({
            onClick: () => console.log('点击了子组件')
        })
    }
}

也就是原封不懂得传递,前面说的那些规则只是对原生的标签起作用,同时也on:不支持这种形式,毕竟自定义组件中事件其实也只是属性的一种。

style

这个要说的就只有一个,那就是在React或者Vue中style都是没有短横线的,但是SolidJS就不是了,是与css里面的命名保持一致,比如font-size,

<div style={{'font-size': "20px"}}>
看到了吗?
</div>

同时它还是支持,所有以短横线开头的属性,也就意味着可以在style里面写变量

<div style={{ "--my-color": "red" }} />

ClassList

这也是SolidJS额外提供的,它与Dom的classList不一样请不要混为一谈(不过实际上确实是使用了Dom的classLsit)。

import {createSignal} from "solid-js";

function ClassList() {
  const [active, setActive] = createSignal(false);

  return (
    <button classList={{'active': active()}} onClick={() => setActive(pre => !pre)}>
      样式改变
    </button>
  )
}

基本就是像上面这样写,它接收的是一个对象,值是一个布尔值,如果是true,就添加,否则就移除。 编译后是被createEffect包裹的,所以可以使用响应式数据

Refs

有些时候,我们需要使用到Dom对象。这个时候就需要用到ref,不过与React和Vue不一样,它不需要使用ref之类的函数创建,可以直接定义变量。

import {onMount} from "solid-js";

function RefObject() {

  let el: HTMLDivElement;

  onMount(() => {
    console.log(el)
  })

  return (
    <div ref={el} >
      看这里
    </div>
  )
}

相同的是必须在onMount中才可以使用,毕竟没挂载哪来的Dom节点。

不过可能有人会问,为什么可以直接定义变量,然后使用?因为编译后就是直接得到了,return编译后其实就类似下面这样的

import {onMount} from "solid-js";

function RefObject() {

  let el: HTMLDivElement;

  onMount(() => {
    console.log(el)
  })

  return () => {
        const div = document.createElement('div');
        //...省略其它内容
        //这里已经直接有div了,所以直接赋值就行
        el = div;
    }
}

当然了,也支持函数的形式比如

function RefObject() {

  let onRef = (el) => {
      console.log(el)
  }

  return (
    <div ref={onRef} >
      看这里
    </div>
  )
}

编译后其实就是变成了,函数调用

function RefObject() {

  let onRef = (el) => {
      console.log(el)
  }

  return () => {
        const div = document.createElement('div');
        //...省略其它内容
        onRef(div);
    }
}

ref的转发

有些时候我们其实是需要,把子组件中的dom通过ref转发给父组件的。所以会需要用到ref转发,不过我们其实也没有什么需要做的

function ChildComponent(props: {ref(el: HTMLButtonElement): void}){
  return (
    <button ref={props.ref}>ref可以访问到</button>
  )
}

function ParentComponent(){
  let childRef : any = null;

  onMount(() => {
    console.log(childRef)
  })

  return (
    <ChildComponent ref={childRef} />
  )
}

其实就是ParentComponent中编译后,就是将ref转换成了一个函数

function ParentComponent(){
  let childRef : any = null;

  onMount(() => {
    console.log(childRef)
  })

  return () => {
    ChildComponent({
        ref(el): {
            childRef = el
            
           //如果childRef是函数
           //childRef(el)
        }
    )
  }
}

大概就是上面这样的一个效果,如果childRef是函数,那就是变成了函数的调用而已。

指令

最后再说一个就是指令,写法如下

// @ts-nocheck

function log(el: HTMLButtonElement, accessor: () => number) {
  console.log(el, accessor)
}

function DirectiveTest(): void}){
  return (
    <button use:log={1}>这里有指令</button>
  )
}

在DirectiveTest中可以看到,就是use:+一个函数,而这个函数的第一个参数是当前的Dom对象,第二个参数是一个函数,其实就是() => 1 编译之后可以理解为

function DirectiveTest(){
  return () => {
      var button = document.createElement('button');
      //...省略其它内容
      log(button, () => 1)
      return button;
  }
}

也就是在Dom对象创建之后执行一下,log函数。

因为是将{}内的东西编译成函数,所以是可以使用响应式数据的

那可能会问这有什么作用?

我只能说,有很多作用,毕竟你已经有了Dom对象了。 比如说,修改innerText的内容

function log(el: HTMLButtonElement, accessor: () => number) {
    el.innerText = `现在的值是${accessor}`
}

如果使用了响应式数据,而且希望innerText根据响应式数据来变化就要改成下面这样

// @ts-nocheck

import {createEffect, createSignal} from "solid-js";

function log(el: HTMLButtonElement, accessor: () => number) {
  createEffect(() => {
    el.innerText = `现在的值是${accessor()}`
  })
}

function DirectiveTest(props: {ref(ref: HTMLButtonElement): void}){
  let [count, setCount] = createSignal(0);

  return (
    <button onclick={() => setCount(pre => pre + 1)} use:log={count()}>这里有指令</button>
  )
}

总之就是有了Dom对象就可以做你想做的事情,就比如官方教程中的clickOutside。

另外就是指令也是不支持自定义组件的,只会作为普通属性对待

不过说实在话的,我感觉不太好用,因为我感觉官方提供的标签本身并不能满足我的需求。所以我一般都是使用组件库的东西,连div都可能少用。指令又不支持自定义组件,所以我就用得非常的少。