🎉SolidJS响应式原理和简易实现🎉

简介: 🎉SolidJS响应式原理和简易实现🎉


💡相关阅读

学不完的框架,🐔啄不完的米,SolidJS,你到底爱谁?😘

演练场地址:https://playground.solidjs.com/

上篇文章中主要介绍了Solid JS的基本语法,分阶段粗略地介绍了一些原理(响应式原理、编译原理和运行时原理)。

接下来的几篇文章里我会详细介绍每个阶段的详细实现原理,希望可以给你的学习带来帮助。

写这篇文章的时候有很大的犹豫,担心Solid JS受众太小,文章的反响连”平平“都算不上,所以先写一篇试试水,如果真的反响平平,我会暂时放弃这个写作计划,还请见谅!

响应式原理

作为Solid JS响应式的基石,我们先看看createSignal的用法和原理。接着我们手动实现一个简易版的createSignal,

💎 万恶之源createSignal

🚗 用法

function createSignal<T>(
    initialValue: T,
    options?: { equals?: false | ((prev: T, next: T) => boolean) }
): [get: () => T, set: (v: T) => T];

Solid JS的厉害之处是,你可以定义变量是否为响应式,甚至可以定义响应式的时机。

  • 🍎 仅提供initialValue时,(默认)是响应式的。
  • 🍎 在options设置equalsfalse时不管何时都是响应式。
  • 🍎 equals设置为函数,根据新值和旧值的关系来设置何时为响应式。

🚗 例子

下面这个例子仅仅在新的值大于旧的值(新增)时,才是响应式的。

import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
  const [count, setCount] = createSignal(1, { equals: (n, o) => n > o });
  const increment = () => setCount(count() + 1);
  const reduce = () => setCount(count() - 1);
  return (
    <>
      <button type="button" onClick={increment}>
        +
      </button>
      <button type="button">{count()}</button>
      <button type="button" onClick={reduce}>
        -
      </button>
    </>
  );
}
render(() => <Counter />, document.getElementById("app")!);

🚗 原理

createSignal简化后的逻辑如下:

🚗 实现

const signalOptions = {
  equals: false
};
function createSignal(value, options) {
  // 初始化options
  options = options
      ? Object.assign({}, signalOptions, options)
      : signalOptions;
  // 创建内部signal
  const s = {
    value,
    comparator: options.equals || undefined
  };
  // 定义setter
  const setter = value => {
    if (typeof value === "function") {
      value = value(s.value);
    }
    return writeSignal(s, value);
  };
  // 返回[getter, setter]
  return [readSignal.bind(s), setter];
}
// 返回当前内部signal的value
function readSignal() {
  return this.value;
}
// 更新内部的value,然后返回value
function writeSignal(node, value) {
  if (!node.comparator) {
    node.value = value;
  }
  return value;
}

现在我们已经实现了createSignal基本功能了,接下来我们通过实现createEffect来让它具有响应式的能力。

💎createEffect

🚗 用法

createEffect接受一个副作用函数,每当它依赖的状态发生改变时,这个副作用都被执行一次。

function createEffect<T>(fn: (v: T) => T, value?: T): void;

🚗 例子

这是个很常见的例子。

import { render } from "solid-js/web";
import { createSignal, createEffect } from "solid-js";
function Counter() {
  const [count, setCount] = createSignal(1);
  const increment = () => setCount(count() + 1);
  createEffect(() => console.log('count : ', count()))
  return (
    <button type="button" onClick={increment}>
      {count()}
    </button>
  );
}
render(() => <Counter />, document.getElementById("app")!);

🚗 原理

我们已经知道,当createEffect依赖项发生改变时,副作用会也会发生改变,这是因为createSignal是基于发布订阅模式的响应式。一个较为完整的关系如下:

🚗 实现

const signalOptions = {
  equals: false
};
const observers = []
function createEffect (effect) {
  const execute = () => {
    // 保存在observers中
    observers.push(execute);
    try {
      effect();
    } finally {
      // 释放
      observers.pop();
    }
  };
  // 副作用函数立即执行
  execute();
};
function createSignal(value, options) {
  // 初始化options
  options = options
      ? Object.assign({}, signalOptions, options)
      : signalOptions;
  // 创建内部signal
  const s = {
    value,
    // 保存订阅者
    subscribers: new Set(),
    comparator: options.equals || undefined
  };
  // 定义setter
  const setter = value => {
    if (typeof value === "function") {
      value = value(s.value);
    }
    return writeSignal(s, value);
  };
  // 返回[getter, setter]
  return [readSignal.bind(s), setter];
}
// 返回当前内部signal的value
function readSignal() {
  const curr = observers[observers.length - 1]
  curr && this.subscribers.add(curr)
  return this.value;
}
// 更新内部的value,然后返回value
function writeSignal(node, value) {
  if (!node.comparator) {
    node.value = value;
  }
  // 每次写入时执行对应的订阅者
  node.subscribers.forEach((subscriber) => subscriber());
  return value;
}

现在我们准备下面的html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SolidJS</title>
</head>
<body>
  <h1>打开控制台查看结果</h1>
  <script src="./solid.js"></script>
  <script>
    const [count, setCount] = createSignal(1);
    const increment = () => setCount(count() + 1);
    createEffect(() => console.log('count : ', count()))
    window.increment = increment
  </script>
</body>
</html>

使用window.increment模拟点击事件,打印如下。

下面我们实现createMemo

💎 createMemo

🚗 用法

createMemo通常用来做派生变量保存基于某个状态中间值。完整用法如下:

function createMemo<T>(
    fn: (v: T) => T,
    value?: T,
    options?: { equals?: false | ((prev: T, next: T) => boolean) }
): () => T;

本篇只讨论最原始的memo

🚗 例子

一个例子如下,每当count变化时,sum自动加2

import { render } from "solid-js/web";
import { createSignal, createEffect, createMemo } from "solid-js";
function Counter() {
  const [count, setCount] = createSignal(1);
  const increment = () => setCount(count() + 1);
  const sum = createMemo(() => count() + 2)
  createEffect(() => console.log('sum : ', sum()))
  createEffect(() => console.log('count : ', count()))
  return (
    <button type="button" onClick={increment}>
      {count()}
    </button>
  );
}
render(() => <Counter />, document.getElementById("app")!);

🚗 原理

它的内部是使用createSignal实现的,所以流程上来说和createEffect一样。

真实的源码里,是基于createComputation实现的,但是它的内部是createSignal

🚗 实现

const createMemo = (memo) => {
  const [value, setValue] = createSignal();
  createEffect(() => setValue(memo()));
  return value;
};

接下来在测试例子里添加如下两行

const sum = createMemo(() => count() + 2)
createEffect(() => console.log('sum : ', sum()))

然后在控制台操作

🎉 最后

今天的分享就到这了,如果发现错误,请及时指正。

觉得还不错,可以关注我的公众号,最近有活动,感兴趣的小伙伴快点来吧!

本系列未来的计划:

  • SolidJS响应式原理和简易实现
  • SolidJS模板编译过程
  • SolidJS源码学习过程总结
相关文章
|
存储 API 流计算
Flink DataStream API-概念、模式、作业流程和程序
前几篇介绍了Flink的入门、架构原理、安装等,相信你对Flink已经了解入门。接下来开始介绍Flink DataStream API内容,先介绍DataStream API基本概念和使用,然后介绍核心概念,最后再介绍经典案例和代码实现。本篇内容:Flink DataStream API的概念、模式、作业流程和程序。
Flink DataStream API-概念、模式、作业流程和程序
|
Rust JavaScript
Zed——Eslint配置支持Vue
Zed——Eslint配置支持Vue
187 0
|
SQL 关系型数据库 MySQL
实时计算 Flink版操作报错之报错File is not a valid field name 如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
XML 存储 数据库
Android 逆向笔记 —— ARSC 文件格式解析
Android 逆向笔记 —— ARSC 文件格式解析
Android 逆向笔记 —— ARSC 文件格式解析
|
前端开发 UED 容器
【专栏:CSS进阶篇】CSS Grid布局:构建复杂的二维布局
【4月更文挑战第30天】CSS Grid布局是二维布局系统,适用于复杂页面结构,如页眉、主体和侧边栏。通过定义网格线和单元格,能轻松创建行和列。基本语法包括设置容器为grid容器,定义`grid-template-rows`和`grid-template-columns`。高级特性包括命名网格线、网格区域、网格间隙、重复网格线和自动填充。在实际应用中,CSS Grid能有效提升开发效率和用户体验,尤其在响应式设计和复杂布局场景下。
266 0
|
设计模式 前端开发 JavaScript
🎉前端开发书籍推荐🎉
🎉前端开发书籍推荐🎉
|
缓存 前端开发 JavaScript
🎉干货满满,React设计原理(二):藏在源码里的两个圈🎉
🎉干货满满,React设计原理(二):藏在源码里的两个圈🎉
|
JavaScript 前端开发 测试技术
从0搭建Vue3组件库(十二):引入现代前端测试框架 Vitest
从0搭建Vue3组件库(十二):引入现代前端测试框架 Vitest
607 0
|
传感器 算法 网络协议
《移动互联网技术》 第二章 无线网络技术: 掌握各种近距离通信的基本概念和工作原理
《移动互联网技术》 第二章 无线网络技术: 掌握各种近距离通信的基本概念和工作原理
396 0
|
存储 Unix Linux
[✔️] android so库的相关命令
[✔️] android so库的相关命令
433 0