比 Svelte 性能还高的前端框架?| 8月更文挑战

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

前言

Solid 是一个冷门的 web 框架。类似于 Vue、React、Svelte。它具有以下特点:

  • 使用了 React Hooks 的 DSL(写法)
  • 执行效率高于其他框架(Vue、React …),因为通过预编译将代码转换为 DOM 的原生操作,跳过了现代 web 框架的 diff 环节
  • 支持 Typescript、SSR 等一系列现代 web 必备特性

介绍

Solid 本文的重点在于讲述 Solid 的响应式原理。

Solid 响应式的核心是 createSignal 和 createEffect。我们可以通过 createSignal 创建响应式变量,通过 createEffect 创建监听响应式变量的函数。

我们先看一下关于 Solid 响应式的一段代码。

const [count, setCount] = createSignal(0)
createEffect(() => {  
    console.log('latest count is ', count())
})
setInterval(() => {  
    setCount(count() + 1)
 }, 1000)
复制代码

首先,我们通过 createSignal 传入一个基本数据类型 0,然后返回了一个数组,数组前两项分别用来 获取 count更新 count

然后,通过 createEffect 创建了一个监听函数,当 count 的值变更时,监听函数会被重新执行。

这时候大家可能会好奇,当 count 更新的时候,Solid 是如何自动执行 createEffect 中传入的监听函数的呢?

分析

我们先分析一下 createEffect 都做了哪些工作。

首先,我们可以观察到 createSignal 返回的数组成员都是函数类型。

createEffect 肯定会执行传入的函数,除此之外,它还会将函数缓存起来,我们来写一段模拟代码:

function createEffect(fn) {  
    const lastListener = Listener // 缓存监听函数  
    Listener = fn // 设置当前监听函数    
    fn() // 执行监听函数    
    Listener = lastListener // 还原监听函数
}
复制代码

可以看到,这段代码主要做了两件事:

  1. 缓存上次监听函数

  2. 执行当前监听函数

在【执行监听函数】的过程中会执行到 count() 、console.log(‘latest count is ‘)

这里我们看看 count()  都做了什么?

由于 count 是 createSignal 返回数组的第一项,所以我们排除掉无关代码,重点放在 createSignal 返回的数组第一项上。

function createSignal(init) {  
    const getter = () => {    
        if (Listener) {      
            // 创建 `当前变量` 和 `Listener` 间的关联关系   
        }    
        return node.value  
    }  
    // ...  
    return [getter, ]
}
复制代码

这里我们可以看到,每一个通过 createSignal 创建的响应式变量,在 getter 触发时都会判断是否存在 Listener,如果存在则将 响应式变量 和 Listener 产生关联。

这时候大家可能会想,传入的 init 值是基本数据类型 0,它是如何与 Listener 产生关联的呢?

翻开 Solid 源码,我们可以看到,Solid 会将 初始值 和 Listener 都保存在同一个对象中,自始至终都在维护这个对象。

function createSignal(init) {
+  const node = {
+    value: init,
+    Listeners: []
+  }  
   const getter = () => {    
     if (Listener) {      
       // 创建 `当前变量` 和 `Listener` 间的关联关系
+      node.Listeners.push(Listener)    
     }    
        return node.value  
    }  
    // ...  
    return [getter, ]
}
复制代码

通过创建了 node 对象,可以把 变量的值 和 Listener 产生关联,同时可以看到,这里的 node.Listeners 是一个数组类型,因为可能会有多个 Listener 都监听了同一个响应式变量。

那么接下来还有个问题,在更新变量的时候都发生了什么呢?

其实大家可以应该猜到,在更新变量时,应该需要通知之前缓存过的所有监听函数,即 遍历 Listeners 并依次执行。

我们继续完善 createSignal 的代码。

function createSignal(init) {  
    const node = {    
        value: init,    
        Listeners: []  
    }  
    const getter = () => {    
        if (Effect) {      
            node.Listeners.push(Listener)    
        }    
        return node.value  
    }  
+   const setter = (newValue) => {
+     node.value = newValue
+     node.Listeners.forEach(fn => fn())
+   }    
    return [getter, setter]
}
复制代码

至此,Solid 的响应式原理已经介绍完了。

现在,我们把上述代码综合一下:

let Listener = null
function createSignal(init) {  
    const node = {    
        value: init,    
        Listeners: []  
    }  
    const getter = () => {    
        if (Listener) {      
            node.Listeners.push(Listener)    
        }    
        return node.value  
    }  

    const setter = (newValue) => {    
        node.value = newValue    
        node.Listeners.forEach(fn => fn())  
    }
    
    return [getter, setter]
}

function createEffect(fn) {  
    Listener = fn  
    fn()  
    Listener = null
}

// 测试代码
const [count, setCount] = createSignal(0)
createEffect(() => {  
    console.log('latest count is ', count())
})
setInterval(() => {  
    setCount(count() + 1)
}, 1000)
复制代码

将上述代码粘贴到 Chrome 控制台,即可看到浏览器的输出:

image.png

最后,我们梳理一下执行流程:

—- 初始时 —-> 

  1. 执行 createEffect

  2. 设置 Listener 为 createEffect 传入的监听函数

  3. 执行 count()

  4. 触发 count 的 getter

  5. 绑定 count 值 和 Listener 

  6. 还原 Listener

—- 更新 count 时 —-> 

  1. 触发 count 的 setter

  2. 执行 count 的 Listener

总结

在 Solid 中,响应式的核心就是 createSignal。它通过创建一个 node 对象,把变量值、Listener 监听函数保存其中,然后在变量的 getter 时 保存监听函数、 在 setter 时 触发监听函数

我们知道 Vue 其实也有响应式更新,但和 Vue 不同的点在于,createSignal 的 getter 是通过函数调用触发的,我们可以在 getter 函数内去绑定监听函数。而 Vue 可以直接通过类似 obj.name 的方式来触发 getter,所以 Vue 会用到 Object.definePropertynew Proxy 来监听 getter。但除此之外,它们的实现方案大致都是相同的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享