背景

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

使用传统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的状态,会无效,两种解决方案:

  1. 不依赖自动解包,手动去做,比如 style={{width: widthSignal.value+'px'}}(反正当前组件或者父组件内必须有地方手动结构过 比如在模板 {widthSignal.value} 或者在js中使用 widthSignal.value,然后本组件内你再style={{width: widthSignal+'px'}}也可以解决问题)
  2. 强制订阅更新
// 强制订阅 signal 变化
useEffect(() => {
if (widthSignal) {
// 确保 signal 被订阅
const unsubscribe = widthSignal.subscribe?.(() => {
// 这里可以添加调试日志
console.log( "Controls: widthSignal changed", widthSignal.value);
});
return unsubscribe;
}
}, [widthSignal]);

所以最终的建议是,如果如果用到了content上,则可以依赖自动解构,如果用在了模板的attr上则尽量自己手动解构!

随机推荐

  1. 🎀Markdown介绍与语法

    简介 Markdown是一种轻量级的标记语言,由John Gruber于2004年创建.它的设计目标是让人们能够使用易读易写的纯文本格式编写文档,并且可以通过工具将其转换为结构化的HTML(超文本标记 ...

  2. eolinker校验规则之正则匹配:返回结果校验的方法和案例(正则校验)

    如上图红色箭头,需要校验返回值内是否包含"创建满足条件的优惠券"这一内容 如果需要满足以上校验,最好的方法就是使用正则进行匹配 切换到正则匹配,输入需要校验的内容,即可实现正则匹配 ...

  3. doctrine执行原生sql并直接返回结果集

    直接返回结果集: getConnection反回了\vendor\doctrine\dbal\lib\Doctrine\DBAL\Driver\Connection.php接口的实现,所以Connec ...

  4. 移动开发框架,Hammer.js 移动设备触摸手势js库

    原文:https://www.cnblogs.com/zhwl/p/3525238.html hammer.js是一个多点触摸手势库,能够为网页加入Tap.Double Tap.Swipe.Hold. ...

  5. sonarqube+gitlab+jenkins+maven集成搭建 (五)

    Jenkins与SonarQube Jenkins 配置 SonarQube在 SonarQube 中生成 Server authentication token登录 SonarQube 后,在 &q ...

  6. Sentinel——热点规则

    目录 热点规则 配置热点规则 API配置热点规则 热点规则 热点规则是用于实现热点参数限流的规则.热点参数限流指的是,在流控规则中指定对某方法参数的 QPS 限流后,当所有对该资源的请求URL中携带有 ...

  7. 用 DevEco Studio 模拟器这些能力 没真机也能高效调测鸿蒙原生应用

    随着鸿蒙生态的快速发展,越来越多的开发者投身于鸿蒙原生应用的开发中.然而,在实际开发中,真机设备短缺.调测场景复杂等问题常困扰着开发者.为解决这些问题,华为在DevEco Studio上为开发者提供了 ...

  8. 分页工具之【PageHelper】

    1.PageHelper技术 依赖 <!-- PageHelper --> <dependency> <groupId>com.github.pagehelper& ...

  9. Flutter集成微信小程序技术教程

    .markdown-body { color: rgba(89, 89, 89, 1); font-size: 15px; font-family: -apple-system, system-ui, ...

  10. MFC对话框显示时背景闪烁

    在显示一个对话框时,可以在WM_PAINT消息处理函数中绘制窗口的背景色.但会出现一种情况,在还未执行完OnPaint函数,对话框已经先显示出白色窗体,如下: 还未绘制窗体,背景色先被显示. 解决办法 ...