React — 神奇的 preact/signals-react

Milk Midi
4 min readJan 9, 2023

大家好,我是奶綠茶,除了 React 外,還有一套叫 preact,簡單來說就是另一個開發團隊推出的 JS framework,語法和 React 99% 都一樣。

其中最酷的他使用 Signals 來取代 React.useState,語法更簡單,也不需要用什麼 useEffect 來掛一堆相依性判斷。而且還有同步推出 React 可以用的 signals 套件包。

來看一下 preact signals 用在 React 上:

import { signal } from "@preact/signals-react";

const count = signal(0);

function CounterValue() {
// Whenever the `count` signal is updated, we'll
// re-render this component automatically for you
return <p>Value: {count.value}</p>;
}

有沒有超神奇,signal() 可以當一般的 JavaScript 使用,不需要包在 React Component 裡,而且當 count.value 有變化時,會自動 updated,真的是太神奇了。

然後就開使想他是怎麼做到的,原始碼不長
https://github.com/preactjs/signals/blob/main/packages/react/src/index.ts

原來他把本來的 React.createElement = WrapJsx(React.createElement) 再包裝過一次,有點 HOC 的感覺,並用 ES6 Proxy 來得知變數被更改。

優點:要拉出去寫成共用的變數,就非常的方便。
缺點:有點打破 React 本來的用法,再加上 HOC 的關系,可能數量越多,效能會越差,但的確提供了另一種開發的思維,也是值得研究

奶綠在試玩時,發現使用 vite 會失效,完全不會 reactive,換成 webpack 就一切正常。

如果真的很想用 preact signals,但又不想要 HOC 包,了解了他的原理後,可以自己包裝一下 hooks,要用的時候,只需要多使用該 hooks 即可。


import { useSyncExternalStore, useMemo } from 'react';
import { effect, type ReadonlySignal } from '@preact/signals-core';

const noop = (a: any) => a;

function createEffectStore(...signals: ReadonlySignal[]) {
let version = 0;
let onChangeNotifyReact: () => void;
const unsubscribe = effect(() => {
signals.forEach((s) => {
noop(s.value);
});
version++;
onChangeNotifyReact?.();
});

return {
subscribe(onStoreChange: () => void) {
onChangeNotifyReact = onStoreChange;
return () => {
unsubscribe();
};
},
getSnapshot() {
return version;
},
};
}

export default function useSignalReactive(...signals: ReadonlySignal[]) {
const store = useMemo(() => {
return createEffectStore(...signals);
}, signals);
useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
}

ExampleCode:

202404 更新,官方有推出 react 用的 runtime 可以直接使用了,不需要再用上面那個方法

import { useSignals } from '@preact/signals-react/runtime';

--

--