今天被战友种草了一款前端框架,打开链接看文章,在各个指标的比较下,SolidJs
脱颖而出,下面简单介绍一下这个框架,然后开始记录一下学习笔记。(Golang的事情暂时放一放,毕竟咱是专业前端「手动狗头」)。
SolidJs
作为一个新星)可谓是各个厂牌的集大成者,它支持JSX
、Fragments
、Context
、Portals
、Lazy
等等,而且是继HyperApp和Svelte后,第三个比纯JS实现更小的库。
SolidJs
在保持JSX语法的同时,做了一些template的规范,比如它的For
、Index
、Switch
、Match
.....既保留了JSX语法的灵活性
,又再某些程度提高了编译速度
,perfect!
SolidJs
并不像Vue
&React
采用了虚拟dom,解决了内存占用过多的问题
import { render } from 'solid-js/web'
function HelloWorld() {
return <p>Hello World</p>
}
render(() => <HelloWorld />, document.getElementById("app"))
在SolidJs
中,组件的含义与React
基本一致,即组件即函数,举个🌰
nested.tsx
export default () => <p>from nested.tsx</p>
main.tsx
import {render} from 'solid-js/web'
import Nested from './nested'
function App() {
return (
<Nested />
)
}
render(() => <App />, document.getElementById("app"))
如果返回了2个元素,可以用Fragement包装起来,举个🌰
import {render} from 'solid-js/web'
import Nested from './nested'
function App() {
return (
<>
<h1>组件:Nested</h1>
<Nested />
</>
)
}
render(() => <App/>, document.getElementById('app'))
Signal
是最核心的响应式基本要素,创建一个Signal
的语法为:
const [count, setCount] = createSignal(0)
当然,createSignal
需要从solid-js
中导出:
import { createSignal } from 'solid-js'
传递给createSignal
的值就是count
的初始值,返回一个带有2个函数的数组,一个是getter
,一个是setter
,可以使用解构随意命名这些参数,例如上面那个🌰,count就是getter,setCount就是setter。
注意:第一个返回值是一个getter
,而不是0
值本身。
举一个计数器的🌰:
import { render } from 'solid-js/web'
import { createSignal } from 'solid-js'
function App() {
const [count, setCount] = createSignal(0)
setInterval(() => {
setCount(count() + 1)
}, 1000)
return (
<div>count的值为:{count()}</div>
)
}
render(() => <App/>, document.getElementById('app'))
setCount也可以接受一个函数,举个🌰
setInterval(() => {
setCount(c => c +1 )
}, 1000)
类似于Vue
中的computed,举个🌰
import { render } from 'solid-js/web'
import { createSignal } from 'solid-js'
function App() {
const [count, setCount] = createSignal(0)
const doubleCount = () => count() * 2
return (
<>
<div>doubleCount: {doubleCount()}</div>
<button onClick={() => setCount(c => c+1)}>点击</button>
</>
)
}
render(() => <App/>, docu3ment.getElementById('app'))
通过从sloid-js导入createEffect
来创建Effect,接受一个函数,并监视其执行情况。createEffec 会自动订阅在执行期间读取的所有Signal,并在这些Signal值其中一个发生变化时,重新运行该函数,举个🌰
import { render } from 'solid-js/web'
import { createSignal, createEffect } from 'solid-js'
function App() {
const [count, setCount] = createSignal(0)
createEffect(() => {
console.log("当前 count:", count())
})
return (
<button onClick={() => setCount(c => c + 1)}>点击</button>
)
}
render(() => <App/>, document.getElementById('app'))
Show
组件用来控制DOM是否显示,举个🌰
import { render } from 'solid-js/web';
import { createSignal, Show } from 'solid-js';
function App() {
const [loggedIn, setLoggedIn] = createSignal(false);
const toggle = () => setLoggedIn(!loggedIn())
return (
<Show when={loggedIn()} fallback={() => <button onClick={toggle}>Log in</button>}>
<button onClick={toggle}>Log out</button>
</Show>
)
}
render(() => <App />, document.getElementById('app'))
与Vue
不同的是,solidJs是在Show组件中通过when
关键字后加条件来判断是否显示,通过fallback
关键字来显示上一个条件为false
时的组件。
For
是SolidJs遍历的列表组件,举个🌰
import { render } from 'solid-js/web';
import { createSignal, For } from 'solid-js';
function App() {
const [dogs, setDogs] = createSignal([
{ id: 0, name: "dog1" },
{ id: 1, name: "dog2" },
{ id: 2, name: "dog3" },
]);
return (
<For each={dogs()}>
{(dog, i) => (
<div>
id:{i()}; name:{dog.name}
</div>
)}
</For>
);
}
render(() => <App />, document.getElementById('app'))
Index
组件也是用来遍历的,上面的🌰,用Index
组件进行遍历:
<Index each={dogs()}>
{(dog, i) => (
{i}, {dog().name}
)}
</Index>
可以发现,Index
与For
具有相似的功能,不同点是,For
中索引是Signal
,Index
中数据项是Signal
Switch
组件是Show
组件的扩展,如果在一个多层条件判断的情况下,使用Show
组件会发生多级嵌套,代码臃肿,使用Switch
/Match
可以很好的解决这种情况,举个🌰
import { render } from 'solid-js/web';
import { createSignal, Switch, Match } from 'solid-js'
function App() {
const [age, setAge] = createSignal(12);
return (
<Switch fallback={<div>未知</div>}>
<Match when={age() < 18}>
<div>未成年</div>
</Match>
<Match when={age() >= 18 && age() < 60}>
<div>成年人</div>
</Match>
<Match when={age() >= 60}>
<div>老年人</div>
</Match>
</Switch>
);
}
render(() => <App />, document.getElementById('app'))
Dynamic
组件处可以让开发者编写<Switch>/<Match>
组件变得更简练,举个栗子:
import { render, Dynamic } from "solid-js/web";
import { createSignal, Switch, Match, For } from "solid-js";
const RedThing = () => <strong style="color: red">Red Thing</strong>;
const GreenThing = () => <strong style="color: green">Green Thing</strong>;
const BlueThing = () => <strong style="color: blue">Blue Thing</strong>;
const options = {
red: RedThing,
green: GreenThing,
blue: BlueThing,
};
function App() {
const [selected, setSelected] = createSignal("red");
return (
<>
<select
value={selected()}
onInput={(e) => setSelected(e.currentTarget.value)}
>
<For each={Object.keys(options)}>
{(color) => <option value={color}>{color}</option>}
</For>
</select>
<Dynamic component={options[selected()]} />
</>
);
}
render(() => <App />, document.getElementById("app"));
Portal
组件可以在正常顺序之外插入元素,默认情况下,Portal
的子内容将从正常的DOM顺序中拿出来,插入到document.body下的
中,举个🌰
main.tsx
import { render, Portal } from "solid-js/web";
import "./styles.css";
function App() {
return (
<div class="app-container">
<p>Just some text inside a div that has a restricted size.</p>
<Portal>
<div class="popup">
<h1>Popup</h1>
<p>Some text you might need for something or other.</p>
</div>
</Portal>
</div>
);
}
render(() => <App />, document.getElementById("app"));
styles.css
.app-container {
width: 200px;
height: 100px;
overflow: hidden;
}
.popup {
position: relative;
z-index: 2;
background: #ddd;
padding: 1rem;
min-height: 200px;
width: 200px;
}
.popup::after {
content: " ";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #ddd transparent;
}
效果是这样的:
ErrorBoundary
是一个可以捕获子组件任何未知产生的JavaScript错误,并显示发生错误的报错信息,举个🌰
import { render } from "solid-js/web";
import { ErrorBoundary } from "solid-js";
const Broken = (props) => {
throw new Error("Oh No");
return <>Never Getting Here</>
}
function App() {
return (
<>
<div>Before</div>
<ErrorBoundary fallback={(err) => err}>
<Broken />
</ErrorBoundary>
<div>After</div>
</>
);
}
render(() => <App />, document.getElementById("app"));
效果如下:
引用一段官方的话:
Solid 中只有少量的生命周期,因为一切的存活销毁都由响应系统控制。响应系统是同步创建和更新的,因此唯一的调度就是将逻辑写到更新结束的 Effect 中。
举个🌰
import { render } from "solid-js/web";
import { createSignal, onMount, For } from "solid-js";
function App() {
const [photos, setPhotos] = createSignal([]);
onMount(async () => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/photos?_limit=20`
);
setPhotos(await res.json());
});
return (
<>
<h1>photos</h1>
<div class="photos">
<For each={photos()} fallback={<p>Loading</p>}>
{(photo, i) => (
<figure>
<img src={photo.thumbnailUrl} alt={photo.title} />
<figcaption>{photo.title}</figcaption>
</figure>
)}
</For>
</div>
</>
);
}
render(() => <App />, document.getElementById("app"));
onCleanup
函数可以执行一些销毁操作,举个栗子
import { render } from "solid-js/web";
import { createSignal, onCleanup } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
const timer = setInterval(() => setCount(count() + 1), 1000);
onCleanup(() => clearInterval(timer));
return <div>Count: {count()}</div>;
}
render(() => <Counter />, document.getElementById("app"));
在SolidJs中,事件均以on
为前缀,举个🌰
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
import "./style.css";
function App() {
const [pos, setPos] = createSignal({ x: 0, y: 0 });
function handleMouseMove(event) {
setPos({
x: event.clientX,
y: event.clientY,
});
}
return (
<div onmousemove={handleMouseMove}>
The mouse position is {pos().x} x {pos().y}
</div>
);
}
render(() => <App />, document.getElementById("app"));
SolidJs中的style
属性接受样式字符串和对象,但需要注意一点,官话:
对象形式不同于
Element.prototype.style
,Solid 通过调用style.setProperty
的封装来进行样式设置。
意味着样式的键需要采用中划线的方式,例如background-color
而不是backgroundColor
,但是这意味着可以设置CSS变量:
<div style={{'--my-custom-color': themeColor()}}></div>
举个🌰
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function App() {
const [num, setNum] = createSignal(0);
setInterval(() => setNum((num() + 1) % 255), 30)
return <div style={{
color: `rgb(${num()}, 180, ${num()})`,
'font-size': `${num()}px`
}}>Some Text</div>;
}
render(() => <App />, document.getElementById('app'));
SolidJs支持使用 class
和className
设置元素的classname属性,classList
用来设置多个class名,key是类名,value是一个boolean,举个🌰
function App() {
const [current, setCurrent] = createSignal('foo')
return <>
<button class={current() === 'foo' ? 'selected' : ''} onClick={setCurrent('foo')}>foo</button>
<button class={current() === 'bar' ? 'selected' : ''} onClick={setCurrent('bar')}>bar</button>
<button class={current() === 'baz' ? 'selected' : ''} onClick={setCurrent('bar')}>baz</button>
</>
}
当然上面的代码也可以用classList
,🌰:
<button classList={{'selected': current() === 'foo'}} onClick={setCurrent('foo')}>foo</button>
SolidJs中,可以通过ref
属性获取元素的引用,官话:
赋值行为是在元素创建时,在 DOM 被追加前发生的。只需声明一个变量,元素引用就会赋值给该变量。
举个🌰
let myDiv;
<div ref={myDiv}>my Div</div>
就可以了...
利用...
扩展运算符,可以直接把属性传进子组件,举个🌰
main.tsx
import { render } from "solid-js/web";
import Info from "./info";
const pkg = {
name: "dog",
age: 3,
gender: 0,
};
function App() {
return <Info name={pkg.name} age={pkg.age} gender={pkg.gender}></Info>;
}
render(() => <App />, document.getElementById("app"));
info.tsx
export default function Info(props) {
console.log(props)
return (
<div>
<p>name: {props.name}</p>
<p>age: {props.age}</p>
<p>gender: {props.gender === 0 ? "girl" : "boy"}</p>
</div>
);
}
上面的main.tsx
可以这样改造一下:
function App() {
return <Info {...pkg}></Info>;
}
是不是省了很多事儿!
SolidJs可以通过use
自定义指令,自定义指令仅仅只是一个形式为(element, valueAccesor)
的函数,其中valueAccesor
是一个获取绑定值的函数,只要函数是在作用域中导入的,就可以通过use:
使用。
重要提示:
use:
需要被编译器检测并进行转换,并且函数需要在作用域内,因此不能作为传值的一部分或在组件上使用。
举一个modal的🌰:
main.tsx
import { render } from "solid-js/web";
import { createSignal, Show } from "solid-js";
import clickOutside from './click-outside'
function App() {
const [show, setShow] = createSignal(false)
return (
<Show when={show()} fallback={<button onClick={setShow(true)}>show Modal</button>}>
<div class="modal" use:clickOutside={show(false)}>modal box</modal>
</Show>
)
}
render(() => <App />, document.getElementById("app"));
click-outside.tsx
import { onCleanup } from 'solid-js';
export default function clickOutside(el, accessor) {
const onClick = (e) => !el.contains(e.target) && accessor()?.();
document.body.addEventListener('click', onClick)
onCleanup(() => document.removeEventListener('click', onClick))
}
SolidJs中的props传值上面已经有🌰了,很简单,再举个🌰
main.tsx
import WelcomeWord from './welcomeWord'
function App() {
reutrn (
<>
<WelcomeWord gretting="Hi" name="Bob"></WelcomeWord>
</>
)
}
welcomeWord.tsx
export default function welcomeWord(props) {
return <h1>{props.gretting || 'hello'} {props.name || 'Alice'}</h1>
}
props赋值默认值通过mergeProps
操作,上面的🌰就可以改成这样
welcomeWord.tsx
import { mergeProps } from 'solid-js'
export default function welcomeWord(props) {
const merged = mergeProps({ greeting: 'hello', name: 'Alice' }, props)
return <h1>{merged.greeting} {merged.name} </h1>
}
就很完美,官话:
Solid 有一些工具函数可以帮助我们处理 props。第一个
mergeProps
函数听起来很像它名字描述得那样合并 props。mergeProps
将潜在的响应式对象合并而不会失去响应式性。最常见的情况就是是为组件设置默认 props。
在SolidJs中解构一个props会失去数据的响应性,可以通过splitProps
来分离一个props,举个🌰
main.tsx
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
import Greeting from "./greeting";
function app() {
const [name, setName] = createSignal("Jack")
return <>
<Greeting greeting="Yo" name={name()} style="color: teal;" />
<button onClick={setName("Alice")}>set name</button>
</>
}
render(() => <App />, document.getElementById('app'));
greeting.tsx
export default function Greeting(props) {
let { greeting, name, ...others } = props
return <h3 {...others}>{greeting} {name}</h3>
}
这种方式修改name
的时候,发现子组件并没有修改,所以需要使用splitProps
import { splitProps } from "solid-js";
export default function Greeting(props) {
const [local, others] = splitProps(props, ["greeting", "name", "age"]);
console.log(local, others)
return (
<h3 {...others}>
{local.greeting} {local.name} {local.age}
</h3>
);
}
就很完美!