百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

专注于性能的 SolidJS

ztj100 2025-01-20 18:22 24 浏览 0 评论

大家好,我是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,上手容易

相关推荐

Vue 技术栈(全家桶)(vue technology)

Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...

vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)

前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...

vue3 组件初始化流程(vue组件初始化顺序)

学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...

vue3优雅的设置element-plus的table自动滚动到底部

场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...

Vue3为什么推荐使用ref而不是reactive

为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...

9、echarts 在 vue 中怎么引用?(必会)

首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...

无所不能,将 Vue 渲染到嵌入式液晶屏

该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...

vue-element-admin 增删改查(五)(vue-element-admin怎么用)

此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...

最全的 Vue 面试题+详解答案(vue面试题知识点大全)

前言本文整理了...

基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时

今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...

不来看看这些 VUE 的生命周期钩子函数?| 原力计划

作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...

Vue3.5正式上线,父传子props用法更丝滑简洁

前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...

Vue 3 生命周期完整指南(vue生命周期及使用)

Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...

救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘

前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...

怎么在 vue 中使用 form 清除校验状态?

在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...

取消回复欢迎 发表评论: