粗看solidjs

善良单纯的小阿板
创建时间: 2022年3月8日 庆丰九年
最后编辑: 2022年3月8日 3 年前
今天晚上有苹果的发布会,我就很无聊。不是很想打游戏。

想着最近看了 solid.js,正好可以code看一下,看看有没有前途

写在前边

我应该做一个梗图,守秩混乱的那个。

现代前端格局的四个角,加上solid,已经齐全了。

vdom+jsx派别:React vdom+template派:Vue 编译时+template派:Sevlte 最后一个碎片就是我刚刚了解的,编译时+jsx派:Solid

当然这个山头不是固定的。社区有越来越多的vue的轮子,确实已经在使用jsx了。React也不能看作一个完完全全的运行时vdom,毕竟他本身只负责react,vdom那是react-dom的事情。

在白天闲暇蹲坑之余呢,我已经看了一些solid的官方文档,有了一个大致的理解。脑内编程已经可以大致的知道它的用法了。

初见

使用官方的ts template开局

pnpx degit solidjs/templates/ts learn-solid

see一下文件结构先。

.
├── README.md
├── index.html
├── package.json
├── pnpm-lock.yaml
├── solid.md
├── src
│   ├── App.module.css
│   ├── App.tsx
│   ├── assets
│   │   └── favicon.ico
│   ├── index.css
│   ├── index.tsx
│   └── logo.svg
├── tsconfig.json
└── vite.config.ts

果然是最小的小弟弟,身体里边已经完全是vue和react的形状了。

vite下边套着index.tsxApp.module.css,竟然有一种杂交的暴力美学。让我有点喜欢了。

回眸

solid生在了一个最好的时代,也是一个最坏的时代。

作为出厂即是hook(函数式)的框架,没有任何的历史包袱。

而且在函数作为一等公民的基础之上,没有了class这层思想桎梏,她比较创新性的搞了非函数式函数组件。

React's abstraction is top down component partition where render methods are called repeatedly and diffed. Solid, instead, renders each Template once in its entirety, constructing its reactive graph and only then executes instructions related to fine-grained changes.

constructing这个词,用的就很妙。 constructing 而不constructor。

其实这很符合js的设计哲学 —— 函数是一等公民,rerender并不是。

虽然还是拿jsx当作语法糖🍬,但是好像看起来,他内部已经不是这么玩的了?我一开始最感兴趣的点也就是在这里。

当然这一点也在后边做到了验证,solid的做法其实和react native很像,只是把jsx当作一个interface,一个user-friendly interface,实际上有更深度的玩具。

我在只扫了一眼函数文档的基础上,没有深读。使用我对于react的理解,进行了简单code。

目录

.
├── README.md
├── index.html
├── package.json
├── pnpm-lock.yaml
├── solid.md
├── src
│   ├── App.module.css
│   ├── App.tsx
│   ├── Data
│   │   └── index.tsx
│   ├── assets
│   │   └── favicon.ico
│   ├── index.css
│   ├── index.tsx
│   └── logo.svg
├── tsconfig.json
└── vite.config.ts

App.tsx

import { Component, createMemo, createSignal, onMount } from "solid-js"

import logo from "./logo.svg"
import styles from "./App.module.css"
import { Data } from "./Data"

interface Props {
    name?: string
}
const App: Component<Props> = (props) => {
    const { name = "default name" } = props
    const [n, setN] = createSignal(0)
    onMount(() => {
        setInterval(() => {
            setN((prev) => prev + 1)
        }, 1000)
    })
    const doubleN = createMemo(() => {
        return 2 * n()
    })
    const DoubleNComp = createMemo(() => {
      return <Data num={doubleN()}/>
    })
    console.log(n())
    return (
        <div class={styles.App}>
            <header class={styles.header}>
                <img src={logo} class={styles.logo} alt='logo' />
                <p>{name}</p>
                <Data num={n()} />
                <DoubleNComp />
                inner: {n}
                <br />
                inner doubleN: {doubleN}
                <a
                    class={styles.link}
                    href='https://github.com/solidjs/solid'
                    target='_blank'
                    rel='noopener noreferrer'
                >
                    Learn Solid
                </a>
            </header>
        </div>
    )
}

export default App

Data/index.tsx

import { Component } from "solid-js";

interface Props{
    num: number;
}
export const Data:Component<Props> = (props)=>{
    const {num} = props
    return (
        <p>
            {num}
        </p>
    )
}

css的部分我没有写。css的实践可以说已经一统江山了。基本不是module就是styled-like了。

额外的,我个人的话,现在更倾向于styled-component,比起module来要更加的灵活。可以参数式,可以直接高阶包装第三方的组件。同时,带来的额外开销是完全可以接受的,也不会影响ssr。

额外额外的,solid的inline css的风格是接近styled-like的文本化,我在code里并没有写。

<span style={`color: #333`}></span>

这是非常讨喜的设计。react的object设计,虽然方便检验、合并和diff,但是太过于老旧了。在这个最好的时代,typescript和编译器爆发的年代,我相信模板字符串是更好的方向。(这个设计比next.js的jsx-style不知道高到哪里去了。

ok,直接跑起来。

👀

首先,直观的,console.log只运行了一次。这就十分不“符合直觉了”。这让我们思考,难道真的不是我们把react惯坏了吗?function component里的执行逻辑真的是符合直觉的么?我们竟然这么轻易的满足?

只运行一次可以见得, constructing 是确实存在的。

👀👀

第二眼,子组件的参数,并没有响应。

图1

这个有一点意料之中。一来,我们已经理解了他的 constructing 逻辑了。二来,她好像确实。。。就不该动?

然后我考虑他的type。

图2

state的访问值是一个Accessor,OK,这很符合直觉,一个getter,返回的结果是个单纯值,那确实不响应也很合理。

那为什么在memo里可以响应呢,memo又是一个什么样子的声明呢。接下来看memo的type:

export declare function createMemo<Next extends _Next, Init = undefined, _Next = Next>(..._: undefined extends Init ? [fn: EffectFunction<Init | _Next, Next>, value?: Init, options?: MemoOptions<Next>] : [fn: EffectFunction<Init | _Next, Next>, value: Init, options?: MemoOptions<Next>]): Accessor<Next>;

果然是生活在最好的时代,比起落后的react,他的一切都是原汁原味的ts,原的以至于我。。。。看不懂

他的最好的时代,我的最坏的时代。这个完全没有可读性啊,对于一个新人。于是我放弃了,只看看vscode的提示好了。

👀👀

build,速度很快。在我的m1 pro max上,甚至没消耗时间。虽然代码量不大,但是还是稍稍超出我的预期的。

开始看一下dist产物

结构

dist
├── assets
│   ├── favicon.be3575ae.ico
│   ├── index.809c4933.js
│   ├── index.9b88165b.css
│   ├── logo.123b04bc.svg
│   └── vendor.4c9300a1.js
└── index.html

看看主文档

前世曾见

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link rel="shortcut icon" type="image/ico" href="/assets/favicon.be3575ae.ico" />
    <title>Solid App</title>
    <script type="module" crossorigin src="/assets/index.809c4933.js"></script>
    <link rel="modulepreload" href="/assets/vendor.4c9300a1.js">
    <link rel="stylesheet" href="/assets/index.9b88165b.css">
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

    
  </body>
</html>

这集我见过。这就是 js module 强者的世界么?在最好的时代里没有历史包袱就是可以负重前行200斤代码扛在肩上996十里山路不换肩半路还能做十分钟大国梦。

主文档的结构很清爽我喜欢。(这句话我2016年看vue2的时候,好像也说过。

assets/vendor 格式化一下,467行,先把外包同学防在那那边备用。

assets/index 业务代码也格式化一下, 放在这边准备下锅。

import {
    t as x,
    i,
    c as A,
    o as L,
    a as N,
    s as k,
    b as u,
    d as C,
    r as O,
} from "./vendor.4c9300a1.js"
const E = function () {
    const s = document.createElement("link").relList
    if (s && s.supports && s.supports("modulepreload")) return
    for (const e of document.querySelectorAll('link[rel="modulepreload"]')) c(e)
    new MutationObserver((e) => {
        for (const t of e)
            if (t.type === "childList")
                for (const o of t.addedNodes)
                    o.tagName === "LINK" && o.rel === "modulepreload" && c(o)
    }).observe(document, { childList: !0, subtree: !0 })
    function n(e) {
        const t = {}
        return (
            e.integrity && (t.integrity = e.integrity),
            e.referrerpolicy && (t.referrerPolicy = e.referrerpolicy),
            e.crossorigin === "use-credentials"
                ? (t.credentials = "include")
                : e.crossorigin === "anonymous"
                ? (t.credentials = "omit")
                : (t.credentials = "same-origin"),
            t
        )
    }
    function c(e) {
        if (e.ep) return
        e.ep = !0
        const t = n(e)
        fetch(e.href, t)
    }
}
E()
var I = "/assets/logo.123b04bc.svg"
const M = "_App_9g4xh_1",
    D = "_logo_9g4xh_5",
    P = "_header_9g4xh_11",
    j = "_link_9g4xh_22"
var d = {
    App: M,
    logo: D,
    "logo-spin": "_logo-spin_9g4xh_1",
    header: P,
    link: j,
}
const q = x("<p></p>"),
    y = (a) => {
        const { num: s } = a
        return (() => {
            const n = q.cloneNode(!0)
            return i(n, s), n
        })()
    },
    w = x(
        '<div><header><img alt="logo"><p></p>inner: <br>inner doubleN: <a href="https://github.com/solidjs/solid" target="_blank" rel="noopener noreferrer">Learn Solid</a></header></div>'
    ),
    B = (a) => {
        const { name: s = "default name" } = a,
            [n, c] = A(0)
        L(() => {
            setInterval(() => {
                c((o) => o + 1)
            }, 1e3)
        })
        const e = N(() => 2 * n()),
            t = N(() =>
                u(y, {
                    get num() {
                        return e()
                    },
                })
            )
        return (
            console.log(n()),
            (() => {
                const o = w.cloneNode(!0),
                    l = o.firstChild,
                    g = l.firstChild,
                    _ = g.nextSibling,
                    f = _.nextSibling,
                    m = f.nextSibling,
                    S = m.nextSibling,
                    p = S.nextSibling
                return (
                    k(g, "src", I),
                    i(_, s),
                    i(
                        l,
                        u(y, {
                            get num() {
                                return n()
                            },
                        }),
                        f
                    ),
                    i(l, u(t, {}), f),
                    i(l, n, m),
                    i(l, e, p),
                    C(
                        (r) => {
                            const v = d.App,
                                h = d.header,
                                $ = d.logo,
                                b = d.link
                            return (
                                v !== r._v$ && (o.className = r._v$ = v),
                                h !== r._v$2 && (l.className = r._v$2 = h),
                                $ !== r._v$3 && (g.className = r._v$3 = $),
                                b !== r._v$4 && (p.className = r._v$4 = b),
                                r
                            )
                        },
                        {
                            _v$: void 0,
                            _v$2: void 0,
                            _v$3: void 0,
                            _v$4: void 0,
                        }
                    ),
                    o
                )
            })()
        )
    }
O(() => u(B, { name: "gg!" }), document.getElementById("root"))

这个emmmm,有点新鲜。

👀

首先,外包同学导出了9项,感觉还可以。

👀👀

一眼就可以看到的是

const q = x("<p></p>"),

莫名的舒适感。直接点进x,果不其然

function le(e, t, s) {
    const n = document.createElement("template")
    n.innerHTML = e
    let i = n.content.firstChild
    return s && (i = i.firstChild), i
}

是创建 template 的函数。

y = (a) => {
    const { num: s } = a
    return (() => {
        const n = q.cloneNode(!0)
        return i(n, s), n
    })()
},

y是我们的Data组件,闭包。clone的就是上边的template q

i函数很有意思,点进去

function z(e, t, s, n) {
    if ((s !== void 0 && !n && (n = []), typeof t != "function"))
        return E(e, t, n, s)
    N((i) => E(e, t(), i, s), n)
}

t != "function"十分有趣,应该涉及到响应。

之后调用的渲染函数,我就不读编译后的了。找源码来读就好了。大致可以猜到是渲染。

👀👀👀

B是我们的App component主体。

const e = N(() => 2 * n()),

参考N的实现,大致可以看到memo的实现。 渲染时使用i(l, e, p),,此时的e就是一个函数了,区别于上边Data中的纯粹值。

对于组件的调用,使用了一个get方法,返回的是Accessor拿到的值。

i(
    l,
    u(y, {
        get num() {
            return n()
        },
    }),
    f
),

为什么读个solid的dist,读出了逆向的感觉?

这波反向推测,大概了解了solid的原理。更多外包同学的工具函数,之后再阅读源码

心里不免会问:其实也不过如此?就这?

因为,这是最坏的时代。

每个人都在焦虑。每个写代码的人都在焦虑。

每个写前端的人,都在焦虑。

我们白天感叹性能太差,晚上感叹碌碌无为。一个人的时候,不敢错过每一个新轮子,不想错过每一个风口;一家人的时候,不敢错过每一个每一个网红盘,不想错过任何一个财富自由的机会 —— 那个从优化性能,变成优化人的机会。

前端框架的两个大哥哥和两个小弟弟,他们的内卷,尚是如此;提线的工程师们,又何尝不是如此呢?

这是最坏的时代。

dom是框架们的傀儡,框架是码农们的傀儡。码农们,又是谁的傀儡呢?

写在后边

其实我今年在努力的写博客,有两篇已经写完了,关于逆向的,没有发。我在反思我是不是过于追求去写技术了,而且过于追求去写能购突破舒适区的东西了。这导致我只想写我不会的东西,我不会的,我哪里有那么容易写出来呢?

另一个值得注意的是,vercel也是solid的赞助商,我觉得这玩意还是很有前景的。