百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

专注于性能的 SolidJS solidstatedisk

liuian 2024-12-30 05:16 35 浏览

大家好,我是Echa哥。

介绍

SolidJS 一个用于构建用户界面,简单高效、性能卓越的 JavaScript 库。

  1. 性能-始终在UI速度和内存利用率基准测试中名列前茅
  2. 强大-可组合的响应式原语与 JSX 的灵活性相结合。
  3. 实用-合理且量身定制的 API 使开发变得有趣而简单。
  4. 生产力-人体工程化设计和熟悉程度使得构建简单或复杂的东西变得轻而易举。

全栈修仙之路专注分享 TS、Vue3、前端架构和源码解析等技术干货。159篇原创内容

公众号

专注于性能

性能仅次于原生JS

一个简单的例子

import { render } from "solid-js/web";

const App = () => <div> hello solidjs!</div>

render(() => <App/>, document.getElementById("app"))

写过react的应该很熟悉这段代码,jsx片段,render函数。会让你感觉既熟悉又现代。

响应式

createSignal

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

function Counter() {
    const [count, setCount] = createSignal(0);
    setInterval(() => setCount(count() + 1), 1000);
    return <div>{count()}</div>;
}
    
render(() => <Counter />, document.getElementById('app'));

signal是solid中基本的响应单元,createSignal类似react中的useState,传递给createSignal调用的参数是初始值,createSignal返回一个两个==函数==的数组,第一个getter,第二个是setter,第一个返回的值是一个getter而不是一个值,使用的时候需要调用,框架拦截读取值的任何位置来进行自动跟踪,从而响应式更新,所以调用getter的位置很重要,和react不同的是,例如setState触发更新,react会生成Fiber树,进行diff算法,最后执行dom操作。solid则是直接调用编译好的dom操作方法,没有虚拟dom比较。

createEffect

import { render } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';

function Counter() {
    const [count, setCount] = createSignal(0);
    
    createEffect(() => {
        console.log('count is :', count())
    })
    
    
    return <button onClick={() => setCount(count() + 1)}>Click me</button>;
}
    
render(() => <Counter />, document.getElementById('app'));

createEffect接收一个函数,监听其执行情况,createEffect会自动订阅在执行期间读取的所有Signal,并在Signal值之一发生改变的时候,重新运行此函数。count更改的时候,createEffect函数就会运行,从而点击一次,就打印一次结果。类似react的useEffect

useEffect(() => {/*....*/}, [count])

衍生Signal

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

function Counter() {
    const [count, setCount] = createSignal(0);
    
    const doubleCount = () => count() * 2
    
    setInterval(() => setCount(count() + 1), 1000);

    return <div>Count: {doubleCount()}</div>;
}

render(() => <Counter />, document.getElementById('app'));

这次没有直接使用count(), 而是使用了doubleCount函数包了一层count() * 2, 日志正常打印2倍的count(), 意味着任何包装count()的函数,都是一个Signal,访问JSX中的js表达式也是,只要访问一个signal,就会触发更新。

Props

Props是组件执行的时候传进来的对象,Props是只读的,并且具备对象getter的响应性的,但是响应性,只能通过props.propsName的形式来访问,才能被追踪到。不能解构props,解构就会脱离追踪范围而失去响应。

默认Props

// greeting.jsx
import { mergeProps } from "solid-js";

export default function Greeting(props) {
  return <h3>{props.greeting || "Hi"} {props.name || "John"}</h3>

    <!--const { greeting, name } = props-->
    <!--return <h3>{greeting || 'Hi'} {name || 'John'}</h3>-->
}

// main.jsx

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

import Greeting from "./greeting";

function App() {
  const [name, setName] = createSignal();

  return <>
    <Greeting greeting="Hello" />
    <Greeting name="Jeremy" />
    <Greeting name={name()} />
    <button onClick={() => setName("Jarod")}>Set Name</button>
  </>;
}

render(() => <App />, document.getElementById('app'));

当然SolidJS也是提供了一个工具函数mergeProps,使得具有响应性。

// greeting.jsx
import { mergeProps } from "solid-js";

export default function Greeting(props) {
  const merged = mergeProps({ greeting: "Hi", name: "John" }, props)

  return <h3>{merged.greeting} {merged.name}</h3>
}

分离Props

合并props用到的地方很少,我们经常用到的是解构组件传进来的props,然后将其他props分离出来,再传递下去。

// greeting.jsx

export default function Greeting(props) {
  const { greeting, name, ...others } = props;
  return <h3 {...others}>{greeting} {name}</h3>
}

直接解构分离,设置name的时候,不会更新,也就是解构的时候失去了响应性。但是solid为我们提供了分离props的函数splitProps,来保持响应性。

// greeting.jsx

export default function Greeting(props) {
  const [local, others] = splitProps(props, ["greeting", "name"]);
  return <h3 {...others}>{local.greeting} {local.name}</h3>
}

Store

内嵌式响应

solid可以独立处理嵌套更新,是因为它提供了一细粒度响应式,也就是哪里需要更新,就更新哪里,指哪打哪。

import { render } from "solid-js/web";
import { For, createSignal } from "solid-js";

const App = () => {
  const [todos, setTodos] = createSignal([])
  let input;
  let todoId = 0;

  const addTodo = (text) => {
    // setTodos([...todos(), { id: ++todoId, text, completed: false }]);
    const [completed, setCompleted] = createSignal(false)
    setTodos([...todos(), { id: ++todoId, text, completed, setCompleted }]);
  }
  const toggleTodo = (id) => {
    // setTodos(todos().map((todo) => (
    //   todo.id !== id ? todo : { ...todo, completed: !todo.completed }
    // )));
    const index = todos().findIndex((t) => t.id === id);
    const todo = todos()[index];
    if (todo) todo.setCompleted(!todo.completed())
  }

  return (
    <>
      <div>
        <input ref={input} />
        <button
          onClick={(e) => {
            if (!input.value.trim()) return;
            addTodo(input.value);
            input.value = "";
          }}
        >
          Add Todo
        </button>
      </div>
      <For each={todos()}>
        {(todo) => {
          const { id, text } = todo;
          console.log(`Creating ${text}`)
          return <div>
            <input
              type="checkbox"
              checked={todo.completed}
              onchange={[toggleTodo, id]}
            />
            <span
              style={{ "text-decoration": todo.completed ? "line-through" : "none"}}
            >{text}</span>
          </div>
        }}
      </For>
    </>
  );
};

render(App, document.getElementById("app"));

两种方式,第一种是追踪todos()更新,效果是新增的时候会触发渲染,等到toggleTodo的时候还是会触发渲染,第二种是,嵌套更新,追踪对象的属性completed(), 新增的时候追踪todos()变化,渲染dom,等到toggleTodo的时候,只会触发数据更新,dom已经渲染了,没有必要再次渲染了。

创建store

store是代理对象,属性可以被跟踪,那么是不是可以实现内嵌式响应,createStore接收一个初始值,返回一个类似于signal的读/写的两个元素,第一个是元素只读的store代理,第二个是setter函数。

import { render } from "solid-js/web";
import { For, createSignal } from "solid-js";
import { createStore } from "solid-js/store";

const App = () => {
  let input;
  let todoId = 0;
  const [store, setStore] = createStore({ todos: [] });
  const addTodo = (text) => {
    setStore('todos', (todos) => [...todos, { id: ++todoId, text, completed: false }]);
    };
  const toggleTodo = (id) => {
    setStore('todos', (t) => t.id === id, 'completed', (completed) => !completed);
    };
  return (
    <>
      <div>
        <input ref={input} />
        <button
          onClick={(e) => {
            if (!input.value.trim()) return;
            addTodo(input.value);
            input.value = "";
          }}
        >
          Add Todo
        </button>
      </div>
      <For each={store.todos}>
        {(todo) => {
          const { id, text } = todo;
          console.log(`Creating ${text}`)
          return <div>
            <input
              type="checkbox"
              checked={todo.completed}
              onchange={[toggleTodo, id]}
            />
            <span
              style={{ "text-decoration": todo.completed ? "line-through" : "none"}}
            >{text}</span>
          </div>
        }}
      </For>
    </>
  );
};

render(App, document.getElementById("app"));

内置组件

Show

条件渲染

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'))

For

循环遍历,数据是固定的,index是可追踪的signal,涉及到dom移动的时候,不会触发重新创建dom。只是index独立更新,数据并不会更新。

import { render } from 'solid-js/web';
import { createSignal, For } from 'solid-js';

function App() {
  const [cats, setCats] = createSignal([
  { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
  { id: 'z_AbfPXTKms', name: 'Maru' },
  { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
 ]);
  
  return (
    <ul>
      <For each={cats()}>
  {(cat, i) => (
    <li>
      <a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}>
        {i() + 1}: {cat.name}
      </a>
    </li>
  )}
</For>
    </ul>
  );
}

render(() => <App />, document.getElementById('app'))

Index

Index和For不同的是,Index是数据项是signal,索引是固定的。

import { render } from 'solid-js/web';
import { createSignal, Index } from 'solid-js';

function App() {
  const [cats, setCats] = createSignal([
  { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
  { id: 'z_AbfPXTKms', name: 'Maru' },
  { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
 ]);
  
  return (
    <ul>
      <Index each={cats()}>
  {(cat, i) => (
    <li>
      <a target="_blank" href={`https://www.youtube.com/watch?v=${cat().id}`}>
        {i + 1}: {cat().name}
      </a>
    </li>
  )}
</Index>
    </ul>
  );
}

render(() => <App />, document.getElementById('app'))

Switch

处理互斥条件

import { render } from "solid-js/web";
import { createSignal, Show, Switch, Match } from "solid-js";

function App() {
  const [x] = createSignal(7);

  return (
    //使用show实现
    <!--<Show-->
    <!--  when={x() > 10}-->
    <!--  fallback={-->
    <!--    <Show-->
    <!--      when={5 > x()}-->
    <!--      fallback={<p>{x()} is between 5 and 10</p>}-->
    <!--    >-->
    <!--      <p>{x()} is less than 5</p>-->
    <!--    </Show>-->
    <!--  }-->
    <!-->-->
    <!--  <p>{x()} is greater than 10</p>-->
    <!--</Show>-->
    //使用Switch实现
    <Switch fallback={<p>{x()} is between 5 and 10</p>}>
  <Match when={x() > 10}>
    <p>{x()} is greater than 10</p>
  </Match>
  <Match when={5 > x()}>
    <p>{x()} is less than 5</p>
  </Match>
</Switch>
  );
}

render(() => <App />, document.getElementById("app"));

Dynamic

<Show><Switch> 组件更简练

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>
      <!--<Switch fallback={<BlueThing />}>-->
      <!--  <Match when={selected() === "red"} ><RedThing /></Match>-->
      <!--  <Match when={selected() === "green"}><GreenThing /></Match>-->
      <!--</Switch>-->
      <Dynamic component={options[selected()]} />
    </>
  );
}

render(() => <App />, document.getElementById("app"));

生命周期

onMount

挂载

solid有极少的生命周期,请求数据,以及一些逻辑都写到这个地方,这个函数只会被调用一次。

import { render } from "solid-js/web";
import { createSignal, onMount, For } from "solid-js";
import "./styles.css";

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>Photo album</h1>

    <div class="photos">
      <For each={photos()} fallback={<p>Loading...</p>}>{ photo =>
        <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'));

总结

  1. 没有虚拟DOM。
  2. 响应式跟踪更新,指哪打哪。
  3. 支持jsx
  4. 写法类似react,上手容易

相关推荐

Python 中 必须掌握的 20 个核心函数——items()函数

items()是Python字典对象的方法,用于返回字典中所有键值对的视图对象。它提供了对字典完整内容的高效访问和操作。一、items()的基本用法1.1方法签名dict.items()返回:字典键...

Python字典:键值对的艺术_python字典的用法

字典(dict)是Python的核心数据结构之一,与列表同属可变序列,但采用完全不同的存储方式:定义方式:使用花括号{}(列表使用方括号[])存储结构:以键值对(key-valuepair)...

python字典中如何添加键值对_python怎么往字典里添加键

添加键值对首先定义一个空字典1>>>dic={}直接对字典中不存在的key进行赋值来添加123>>>dic['name']='zhangsan'>>...

Spring Boot @ConfigurationProperties 详解与 Nacos 配置中心集成

本文将深入探讨SpringBoot中@ConfigurationProperties的详细用法,包括其语法细节、类型转换、复合类型处理、数据校验,以及与Nacos配置中心的集成方式。通过...

Dubbo概述_dubbo工作原理和机制

什么是RPCRPC是RemoteProcedureCall的缩写翻译为:远程过程调用目标是为了实现两台(多台)计算机\服务器,互相调用方法\通信的解决方案RPC的概念主要定义了两部分内容序列化协...

再见 Feign!推荐一款微服务间调用神器,跟 SpringCloud 绝配

在微服务项目中,如果我们想实现服务间调用,一般会选择Feign。之前介绍过一款HTTP客户端工具Retrofit,配合SpringBoot非常好用!其实Retrofit不仅支持普通的HTTP调用,还能...

SpringGateway 网关_spring 网关的作用

奈非框架简介早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎这些框架和SpringCloudAlibaba的对应关系我们要知道Nacos对应Eureka都是注册中心Dubbo...

Sentinel 限流详解-Sentinel与OpenFeign服务熔断那些事

SentinelResource我们使用到过这个注解,我们需要了解的是其中两个属性:value:资源名称,必填且唯一。@SentinelResource(value="test/get&#...

超详细MPLS学习指南 手把手带你实现IP与二层网络的无缝融合

大家晚上好,我是小老虎,今天的文章有点长,但是都是干货,耐心看下去,不会让你失望的哦!随着ASIC技术的发展,路由查找速度已经不是阻碍网络发展的瓶颈。这使得MPLS在提高转发速度方面不再具备明显的优势...

Cisco 尝试配置MPLS-V.P.N从开始到放弃

本人第一次接触这个协议,所以打算分两篇进行学习和记录,本文枯燥预警,配置命令在下一篇全为定义,其也是算我毕业设计的一个小挑战。新概念重点备注为什么选择该协议IPSecVPN都属于传统VPN传统VP...

MFC -- 网络通信编程_mfc编程教程

要买东西的时候,店家常常说,你要是真心买的,还能给你便宜,你看真心就是不怎么值钱。。。----网易云热评一、创建服务端1、新建一个控制台应用程序,添加源文件server2、添加代码框架#includ...

35W快充?2TB存储?iPhone14爆料汇总,不要再漫天吹15了

iPhone14都还没发布,关于iPhone15的消息却已经漫天飞,故加紧整理了关于iPhone14目前已爆出的消息。本文将从机型、刘海、屏幕、存储、芯片、拍照、信号、机身材质、充电口、快充、配色、价...

SpringCloud Alibaba(四) - Nacos 配置中心

1、环境搭建1.1依赖<!--nacos注册中心注解@EnableDiscoveryClient--><dependency><groupI...

Nacos注册中心最全详解(图文全面总结)

Nacos注册中心是微服务的核心组件,也是大厂经常考察的内容,下面我就重点来详解Nacos注册中心@mikechen本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》里面。微服务注册中...

网络技术领域端口号备忘录,受益匪浅 !

你好,这里是网络技术联盟站,我是瑞哥。网络端口是计算机网络中用于区分不同应用程序和服务的标识符。每个端口号都是一个16位的数字,范围从0到65535。网络端口的主要功能是帮助网络设备(如计算机和服务器...