这是我参与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 // 还原监听函数
}
复制代码
可以看到,这段代码主要做了两件事:
- 
缓存上次监听函数
 - 
执行当前监听函数
 
在【执行监听函数】的过程中会执行到 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 控制台,即可看到浏览器的输出:

最后,我们梳理一下执行流程:
—- 初始时 —->
- 
执行 createEffect
 - 
设置 Listener 为 createEffect 传入的监听函数
 - 
执行 count()
 - 
触发 count 的 getter
 - 
绑定 count 值 和 Listener
 - 
还原 Listener
 
—- 更新 count 时 —->
- 
触发 count 的 setter
 - 
执行 count 的 Listener
 
总结
在 Solid 中,响应式的核心就是 createSignal。它通过创建一个 node 对象,把变量值、Listener 监听函数保存其中,然后在变量的 getter 时 保存监听函数、 在 setter 时 触发监听函数。
我们知道 Vue 其实也有响应式更新,但和 Vue 不同的点在于,createSignal 的 getter 是通过函数调用触发的,我们可以在 getter 函数内去绑定监听函数。而 Vue 可以直接通过类似 obj.name 的方式来触发 getter,所以 Vue 会用到 Object.defineProperty 或 new Proxy 来监听 getter。但除此之外,它们的实现方案大致都是相同的。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)