useSignal
背景
我要监听一个视频播放当前进度时间,并把显示到页面上。

使用传统state
function DemoOne() {
const [currentTime, setCurrentTime] = useState(0);
const videoRef = useRef<HTMLVideoElement>(null);
// 处理播放进度变化
const handleTimeUpdate = () => {
const video = videoRef.current;
setCurrentTime(Number(video.currentTime.toFixed(2)));
};
console.log('DemoOne Render');
return (
<div>
<video
src="https://www.w3schools.com/html/mov_bbb.mp4"
controls
ref={videoRef}
onTimeUpdate={handleTimeUpdate}
></video>
<div>当前播放时间:{currentTime} 秒</div>
</div>
);
}
你会发现DemoOne组件会一直被重新render,这是因为你在 handleTimeUpdate 里 更新状态了!
使用Signal
import { useRef } from 'react';
import { useSignal } from "@preact-signals/safe-react";
function DemoOne() {
const currentTime = useSignal(0);
const videoRef = useRef<HTMLVideoElement>(null);
// 处理播放进度变化
const handleTimeUpdate = () => {
const video = videoRef.current;
currentTime.value = Number(video.currentTime.toFixed(2));
};
console.log('DemoOne Render');
return (
<div>
<video
src="https://www.w3schools.com/html/mov_bbb.mp4"
controls
ref={videoRef}
onTimeUpdate={handleTimeUpdate}
></video>
<div>当前播放时间:{currentTime} 秒</div>
</div>
);
}
这里DemoOne组件会只会被render执行一次,而且currentTime也还是响应式的!
完整集成
踩过坑:如果不完整集成signal,jsx属性无法响应signal的更新
import { useSignal } from '@preact/signals-react';
export default function Demo() {
// 一开始为蓝色背景
const bgc = useSignal('blue');
// 鼠标移入为绿色背景
function hover() {
bgc.value = 'green';
}
// 鼠标移出为蓝色背景
function unhover() {
bgc.value = 'blue';
}
return (
<button
style={{
color: 'white',
backgroundColor: bgc.value,
}}
onMouseOver={hover}
onMouseOut={unhover}
data-bgc={bgc.value}
>
Hover Me - {bgc.value}
</button>
);
}
如何完整集成呢?其实官方文档给出了说明
vite
安装 npm install --D @vitejs/plugin-react-swc后,去配置 vite.config.js
// vite.config.ts
import { defineConfig } from "vite";
import reactSwc from "@vitejs/plugin-react-swc";
export default defineConfig({
plugins: [
reactSwc({
plugins: [["@preact-signals/safe-react/swc", {}]],
}),
],
});
nextjs
直接进入 next.config.js
const nextConfig = {
experimental: {
swcPlugins: [
[
"@preact-signals/safe-react/swc",
{
mode: "auto",
}
],
],
},
};
module.exports = nextConfig;
其它
这就很有意思了,一直引以为傲的 react 响应式更新机制 反而在这个例子中载了跟头!
这个时候使用 Signal 反而能如同 vue那样更颗粒化的精准更新,而非re-render整个组件(也就是说它能触发dom更新,而不必整个组件重新执行一遍)!
vue和react响应式
由此推导vue和react响应式的区别:
react任何状态的变更都会导致组件重新执行整体渲染(注意不是重新挂载,各种hooks内的代码不会执行),vue状态则只有依赖了变化数据的部分才会重新计算或渲染!
vue因为这个特性,所以使用watch或computed等来监听状态的变化来重新执行某些js代码逻辑。
react因为这个特性,发明了useEffect、useMemo、useCallBack等hook,来守住不用重新执行的某些js代码逻辑。
但也并不是说vue永远不会被整个reRender,比如组件被重新挂载(比如加key)or 手动调用 forceUpdate就会!
监听
若组件不会因signal的变化而整体重新执行,
那在组件非dom部分的js中,
如何监听signal的变化呢(类似于vue的watch)?
当然有 effect !
import { useRef } from 'react';
import { effect, useSignal } from "@preact-signals/safe-react";
function DemoOne() {
const currentTime = useSignal(0);
const videoRef = useRef<HTMLVideoElement>(null);
// 处理播放进度变化
const handleTimeUpdate = () => {
const video = videoRef.current;
currentTime.value = Number(video.currentTime.toFixed(2));
};
effect(() => {
console.log("currentTime 变化了:", currentTime.value);
console.log('DemoOne again Render');
});
console.log('DemoOne first Render');
return (
<div>
<video
src="https://www.w3schools.com/html/mov_bbb.mp4"
controls
ref={videoRef}
onTimeUpdate={handleTimeUpdate}
></video>
<div>当前播放时间:{currentTime} 秒</div>
</div>
);
}
计算属性
当然它也有了计算属性
import { useSignal, useComputed } from "@preact/signals-react";
import { useRef } from "react";
export default function VideoPlayer() {
const currentTime = useSignal(0);
const videoRef = useRef<HTMLVideoElement>(null);
const handleTimeUpdate = () => {
if (videoRef.current) {
currentTime.value = videoRef.current.currentTime;
}
};
const formattedTime = useComputed(() => currentTime.value.toFixed(2));
return (
<div>
<video
src="https://www.w3schools.com/html/mov_bbb.mp4"
controls
ref={videoRef}
onTimeUpdate={handleTimeUpdate}
/>
<div>当前播放时间:{formattedTime} </div>
{/* <div>当前播放时间:{currentTime.value.toFixed(2)} </div> 要求完整集Signal,否则这样无效,不会响应式更新 */}
</div>
);
}
两个版本
如下可以看到,有两种倒入方式,意思有两个库可以使用。
| @preact/signals-react | 官方 Preact Signals 的 React 适配 | 更轻量,但可能需要额外配置 |
|---|---|---|
| @preact-signals/safe-react | 社区维护的兼容版本 | 更稳定,对 React 18+ 优化 |
优先使用官方版本!
import { useSignal } from '@preact/signals-react';
import { useSignal } from '@preact-signals/safe-react';
自动解包
我发现{count.value} 和 {count} 有时候都能触发响应式更新,这是因为你配置了自解包,以及有些版本默认自动解包

存在坑
自动解包在nextjs上,会存在一个坑,就是html属性如果用到 signals的状态,会无效,两种解决方案:
- 不依赖自动解包,手动去做,比如
style={{width: widthSignal.value+'px'}}(反正当前组件或者父组件内必须有地方手动结构过 比如在模板{widthSignal.value}或者在js中使用widthSignal.value,然后本组件内你再style={{width: widthSignal+'px'}}也可以解决问题) - 强制订阅更新
// 强制订阅 signal 变化
useEffect(() => {
if (widthSignal) {
// 确保 signal 被订阅
const unsubscribe = widthSignal.subscribe?.(() => {
// 这里可以添加调试日志
console.log( "Controls: widthSignal changed", widthSignal.value);
});
return unsubscribe;
}
}, [widthSignal]);
所以最终的建议是,如果如果用到了content上,则可以依赖自动解构,如果用在了模板的attr上则尽量自己手动解构!
随机推荐
- Audio DSP boot 过程
在智能手机或智能手表等SoC上通常有一块专门的audio DSP(简称ADSP)来做音频处理.要做音频处理,ADSP首先要被boot起来.本文以CEVA BX2为例来讲讲ADSP的boot过程. 在上 ...
- Chrome 135 版本开发者工具(DevTools)更新内容
Chrome 135 版本开发者工具(DevTools)更新内容 一.性能(Performance)面板改进 1. 性能面板中的配置文件和函数调用现已显示来源和脚本链接 Performance > ...
- 单元测试(一)——xUnit
一.为什么要做单元测试 可以频繁测试 比人工测试要快 测试代码和人工代码紧密结合 测试结果非常可靠 更容易更快发现错误 二.测试坐标图 一般开发会做单元测试和集成测试 三.测试分成三个阶段 四.Xun ...
- VMware 17 Pro 虚拟机从下载到安装的超详细教程,解决你的所有疑问
VMware 17 Pro介绍 VMware 17 Pro是一款功能强大的虚拟机软件,适用于开发人员.测试人员.系统管理员和教育机构.它可以在一台计算机上模拟运行多台虚拟机,支持Windows.Lin ...
- ESP32S3 BLE_HID的编程实现
ESP32S3 BLE_HID的编程实现 BLE是低功耗蓝牙,HID是Human Interface Device,也就是人机接口设备. 主要用于无线连接并传输用户输入数据(如按键.触控.手势等). ...
- 【神兵利器】Windows平台shellcode免杀加载器
项目介绍 免杀,bypassav,免杀框架,nim,shellcode,使用nim编写的shellcode加载器,可快速生成免杀可执行文件 下载地址 Windows平台shellcode免杀加载器下载 ...
- 5个让你眼前一亮的JavaScript装饰器技巧
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- SpringAI-RC1正式发布:移除千帆大模型!
续 Spring AI M8 版本之后(5.1 发布),前几日 Spring AI 悄悄的发布了最新版 Spring AI 1.0.0 RC1(5.13 发布),此版本也将是 GA(Generally ...
- QuickSort之C#实现
/// <summary> /// 快速排序中的切分 /// lIndex已经是基准值,i记录基准值的大小值的边界,j记录目前遍历的边界: /// i值必须从lIndex+1开始,因为基准 ...
- 初次使用 Jetbrains Rider 编写 C#(.Net) 代码
前段时间,Jetbrains公司 公布了 Rider IDE 对非商业用途免费,看到很多业界的朋友都用到这个IDE,今天便下载下来使用一下. 1.界面的差异 Rider的界面跟我前段时间学习调试安卓代 ...