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都可能少用。指令又不支持自定义组件,所以我就用得非常的少。