
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@ivliu/react-signal
Advanced tools
Signal(信号)是一种存储应用状态的形式,类似于 React 中的 useState()。但是,有一些关键性差异使 Signal 更具优势。Vue、Preact、Solid 和 Qwik 等流行 JavaScript 框架都支持 Signal。
Signal(信号)是一种存储应用状态的形式,类似于 React 中的 useState()。但是,有一些关键性差异使 Signal 更具优势。Vue、Preact、Solid 和 Qwik 等流行 JavaScript 框架都支持 Signal。
那么react结合signal能产生什么样的火花,能解决什么问题呢?
Signal 和 State 之间的主要区别在于 Signal 返回一个 getter 和一个 setter,而非响应式系统返回一个值和一个 setter。
useState() = value + setter
useSignal() = getter + setter
注意:有些响应式系统同时返回一个 getter/setter,有些则返回两个单独的引用,但思想是一样的。
我们拿solidjs举个例子,因为react-signal的api设计和solidjs保持一致
const Counter = () => {
const [count, setCount] = createSignal(0);
return (
<button onClick={() => setCount(count() + 1)}>{count}</button>
)
}
using pnpm
pnpm add @ivliu/react-signal
using yarn
yarn add @ivliu/react-signal
using npm
npm install @ivliu/react-signal --save
import { useSignal, useEffect, untrack } from '@ivliu/react-signal';
const App = () => {
// ? [getter, setter]
const [count, setCount] = useSignal(60);
// ? untrack count();
useEffect(() => {
setInterval(() => {
setCount(untrack(() => count()) - 1);
}, 1000);
});
// ? auto track count();
useEffect(() => {
console.log('effect', count());
return () => console.log('destroy', count());
});
// ? useEffect with undefined deps
useEffect(() => {
console.log('update');
}, null);
return <div>{count()}</div>;
};
# 安装依赖
pnpm install
# 运行
npm start
# 进入example
cd example
# 安装依赖
pnpm install # or yarn
# 运行
npm start
打开http://localhost:1234,即可查看,也可更改example/index.tsx来体验
提起react hooks,我们作为开发者可以说是又爱又恨,爱的是它可以让函数组件拥有类组件的功能,从而更方便地管理组件状态,同时在逻辑复用上相较于HOC或者render props更简单更轻量。恨的是它带来了一些心智负担,尤其是闭包和显式依赖问题。
react-signal在一定程度上可以解决这些问题
react-signal使用useSignal代替useState,返回了getter和setter。
为了实现依赖自动追踪,我们重写了useEffect、useLayoutEffect、useInsertionEffect、useMemo、useCallback,且命名与react保持一致。
另外我们还提供了一些高级api,createSignal、untrack、destroy。
下面将会详细介绍每一个api。
useSignal用于替换useState,它返回一个getter和setter。
import { useSignal, useEffect } from '@ivliu/react-signal';
function App() {
const [count, setCount] = useSignal(0);
useEffect(() => {
const handle = setTimeout(() => {
// 输出最新值10,而非初次访问的闭包值
console.log(count())
}, 1000);
return () => clearTimeout(handle);
})
// useEffect都不需要写依赖了
useEffect(() => {
setCount(10);
})
// 取值改为getter方式
return <div>{count()}</div>
}
如果signal初值初始化成本较高,那么你可以通过函数指定。
// new person仅会初始化一次
useSignal(() => new Person())
另外还可以用createSignal创建初始值,但是注意createSignal需要声明在组件外部。
import { createSignal, useSignal, useEffect } from '@ivliu/react-signal';
const externalSignal = createSignal(0);
function App() {
const [count, setCount] = useSignal(externalSignal);
useEffect(() => {
const handle = setTimeout(() => {
// 输出最新值10,而非初次访问的闭包值
console.log(count())
}, 1000);
return () => clearTimeout(handle);
})
// useEffect都不需要写依赖了
useEffect(() => {
setCount(10);
})
// 取值改为getter方式
return <div>{count()}</div>
}
import { useReducer, useEffect } from '@ivliu/react-signal';
function App() {
const [count, dispatch] = useReducer((prevValue) => prevValue + 1, 0);
// dispatch引用是稳定的,当需要对子组件缓存时很有效果
return <div onClick={dispatch}>{count()}</div>
}
useEffect用于替换native useEffect,默认不需要填写依赖。执行时机和react effect一致
useEffect(() => {
/** count()会自动跟踪,count()发生变化时,effect函数会重新执行 */
console.log(count())
})
如果想实现等效native Effect不传依赖,即useEffect回调每次渲染都重新执行的效果的话,则依赖项需要显式传入null。
useEffect(() => {
console.log(count())
}, null)
useLayoutEffect、useInsertionEffect同理。
const onClick = useCallback(() => {
console.log(count());
})
如果函数仅仅依赖signal的话,那么想实现一个引用稳定的函数将轻而易举,这是个附加的feature。
function App() {
const [count, setCount] = useSignal(0);
const doubleCount = useMemo(() => {
return count() * 2;
});
return <div onClick={() => setCount(count() + 1)}>{doubleCount()}</div>
}
createSignal是脱离react组件创建signal的方式,本意是为了和useSyncExternalStore更好的结合使用。
结合useSyncExternalStore
import { useSyncExternalStore } from 'react';
import { createSignal, useCallback } from '@ivliu/react-signal';
const store = createSignal({ theme: 'light' });
function App() {
const { theme } = useSyncExternalStore(
store.subscribe,
useCallback(() => store.value),
);
return <div onClick={() => store.value = { theme: 'dark' } }>{theme}</div>
}
结合useSignal
import { createSignal, useSignal, useEffect } from '@ivliu/react-signal';
const externalSignal = createSignal(0);
externalSignal.subscribe((value) => console.log(value));
function App() {
const [count, setCount] = useSignal(externalSignal);
useEffect(() => {
const handle = setTimeout(() => {
// 输出最新值10,而非初次访问的闭包值
console.log(count())
}, 1000);
return () => clearTimeout(handle);
})
// useEffect都不需要写依赖了
useEffect(() => {
setCount(10);
})
// 取值改为getter方式
return <div>{count()}</div>
}
同时我们可以用它做一些状态保持,比如最常见的页码保持。 我们有一个列表页,然后在某页进入详情,然后返回,我们肯定希望保持在对应页,利用createSignal就可以轻松实现,因为组件销毁的时候,状态仍然保持在内存里,组件再次挂载时访问的是缓存状态。
注意不要一个external signal供多个useSignal使用。
我们实现了effect依赖的自动追踪,那么我们不想追踪某些变量的话,我们可以用untrack包裹
useEffect(() => {
// 此时count()不会追踪,setInterval仅会设置一次
const handle = setInterval(() => {
setCount(untrack(() => count()) - 1);
}, 1000);
return () => clearInterval(handle);
});
先看个问题
function App() {
const [count, setCount] = useState(0);
const [person, setPerson] = useState({ name: '' });
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
// ? person.name每次更新,两次输出的值是否一致
console.log(countRef.current);
return () => console.log(countRef.current);
}, [person.name]);
return <input value={person.name} onChange={(e) => {
setPerson({ name: e.target.name });
}} />
}
揭晓答案,不一致。因为effect destroy函数是在下一次渲染执行的。
因为我们提供了destroy api,它用在native useEffect内部访问signal的情况。
// ! native useEffect
useEffect(() => {
// ? person.name每次更新,两次输出的值保持一致
console.log(count());
return destroy(() => console.log(count()))
}, [person.name]);
react-signal并非脱离react创造新概念,且和细粒度更新没什么关系,它仅仅提供了signal形式的api。 因为我们可以非常低成本的接入,且支持和native api混用。
import { useState, useEffect } from 'react';
import { useSignal, useEffect as useEffect2 } from '@ivliu/react-signal';
function App(props: { count3: number }) {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useSignal(0);
useEffect(() => {
console.log(count1, count2(), props.count3);
}, [count1, count2, props.count3]);
useEffect2(() => {
console.log(count1, count2(), props.count3);
// state和props值无法自动追踪,需要显式声明依赖
}, [count1, props.count3]);
return <div onClick={() => {
setCount1(count1 + 1);
setCount2(count2() + 1);
}}>{count1 + count2() + props.count3}</div>
}
在使用useSignal的时候需要注意和useState的不同
function App1() {
const [count, setCount] = useState(0);
return (
<p onClick={() => {
// 点击一次,count值加1
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}}>{count}</p>
)
}
function App2() {
const [count, setCount] = useSignal(0);
return (
<p onClick={() => {
// 点击一次,count值加3,因为signal是稳定且可变的
setCount(count() + 1);
setCount(count() + 1);
setCount(count() + 1);
// 如果你想保持行为一致,你需要
// const current = count();
// setCount(current + 1);
// setCount(current + 1);
// setCount(current + 1);
}}>{count}</p>
)
}
在native effect中我们可以自由控制监听的粒度,比如
// native effect
useEffect(() => { console.log(person) }, [person.name]);
但目前react-signal只能做到signal粒度的自动追踪,我们正在努力实现该feature。 如果你想实现类似效果,你可以暂时这样做。
useEffect(() => { console.log(untrack(() => person())) }, [person().name]);
请随时提交任何问题或请求请求。我将在最快的时间回复你。
MIT
FAQs
Signal(信号)是一种存储应用状态的形式,类似于 React 中的 useState()。但是,有一些关键性差异使 Signal 更具优势。Vue、Preact、Solid 和 Qwik 等流行 JavaScript 框架都支持 Signal。
The npm package @ivliu/react-signal receives a total of 0 weekly downloads. As such, @ivliu/react-signal popularity was classified as not popular.
We found that @ivliu/react-signal demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.