npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-use-signal

v1.0.14

Published

这可能是最小的React状态管理库

Downloads

13

Readme

react-use-signal

image

Des

react-use-signal 非常纯粹,他只解决两个问题,1、数据共享; 2、防止rerender; 它可以让你像使用 useState 一样进行状态管理,同时拥有通过 key 进行共享 state 的能力,并且内部会保证一次渲染周期中,涉及组件最多渲染一次。 当配合 memo 可以最大程度优化你的应用!

🚫声明:目前此工具只适用于React Hooks,class组件请忽略。

Why?

如果你的项目只是需要组件间状态共享并且不想引入一些新的概念, 那么use-signal一定适合你:
1、更低的成本: useSignal 可以代替 useState 使用而无需打破你以往的开发习惯
2、更好的性能: useSignal 可以保证一次set操作一个组件最多更新一次甚至是深层组件或列表元素局部更新
3、更小的体积: 打包后只有7kb,gzip以后应该只有3kb

BenchMark

🚫声明:目前是跑最基本的React渲染,并未做任何性能优化,benchmark的结果只能说明use-signal在react之上做了很轻的封装,后面会对局部更新做测试优化

Use

基础使用

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const Child = () => {
 // 使用名为'app'的Signal
 const [state] = useSignal('app');
 return (
  <div>{ state.count }</div>
 );
};

const App = () => {
 // 新增并且初始化一个名为'app'的Signal
 createSignal('app', { count: 0 });
 return (
  <div>
   <Child />
  </div>
 );
};

export default App

函数式初始化一个Signal

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const initApp = () => {
 return {
  count: 0
 };
};

const Child = () => {
 // 使用名为'app'的Signal
 const [state] = useSignal('app');
 return (
  <div>{ state.count }</div>
 );
};

const App = () => {
 // 新增并且初始化一个名为'app'的Signal
 createSignal('app', initApp);
 return (
  <div>
   <Child />
  </div>
 );
};

export default App

初始化一个全局状态(非hooks实现)

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

// 初始化一个名为 'global' 的 Signal;
// 第二个参数是是否为Hooks实现:
// true: initSingalManager必须满足hooks原则
// false: initSingalManager无须满足hooks原则
// default: true
initSingalManager({ count: 0 }, false);

const Child = () => {
 // 使用名为'global'的Signal;
 // 等同于 useSignal('global');
 const [state] = useSignal();
 return (
  <div>{ state.count }</div>
 );
};

const App = () => {
 return (
  <div>
   <Child />
  </div>
 );
};

export default App

🚫注意:initSingalManager 第二个参数不传 或 传 true时,一定要在组件内部声明且符合hooks原则,否则会报错并且打乱React的状态。

初始化一个非hooks实现的Signal

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const [state, setState] = createSignal('app', { count: 0 }, 'key', false);

const Child = () => {
 // 使用名为'global'的Signal;
 // 等同于 useSignal('global');
 const [state] = useSignal('app');
 return (
  <div>{ state.count }</div>
 );
};

const App = () => {
 return (
  <div>
   <Child />
  </div>
 );
};

export default App

深度订阅模式

有两个场景可使用深度订阅优化性能:

1、当某一个组件依赖列表中的某一项

2、单独更新数组中的某一项

深度订阅特权:

1、set方法不再局限于引用类型,可以是任意类型,相应的值会根据路径改变信号源状态

2、更新范围是可以穿透树形结构进行的,也就是局部刷新

以下为基本深度订阅模式

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const Child = () => {
 // 深度订阅 signal.count
 const [count] = useSignal('app', 'count');
 return (
  <div>count: { count }</div>
 );
};

const ChildNormal = () => {
 // 深度订阅 signal.length
 const [length] = useSignal('app', 'length');
 return (
  <div>length: { length }</div>
 );
}

const App = () => {
 const [state, setState] = createSignal('app', { count: 0, length: 0 });
 
 // 点击以后发现只有 <Child /> 组件更新了
 // 实际上 <ChildNormal /> 与 <Child /> 依赖同一个Signal
 const onAdd = () => {
  setState({ count: state.count + 1 });
 };

 return (
  <div>
   <div onClick={onAdd}>Click to chang count!</div>
   <Child />
   <ChildNormal />
  </div>
 );
};

export default App

数组深度订阅

Item内部点击事件,只会更新当前点击的Item,并不会刷新整个List

并且setId可以传递任意类型

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const Item = ({index}) => {
 const [id, setId] = useSignal('app', `list.${index}`);
 return (
  <div onClick={() => setId(123)}>id: { id }</div>
 );
};

const List = ({list}) => {
 return list.map((item, i) => <Item key={item} index={i} />);
};

const App = () => {
 createSignal('app', { list: Array.from({length: 10}).map((item, i) => i)  });
 
 // 点击以后发现只有 <Child /> 组件更新了
 // 实际上 <ChildNormal /> 与 <Child /> 依赖同一个Signal
 const onAdd = () => {
  setState({ count: state.count + 1 });
 };

 return (
  <div>
   <div onClick={onAdd}>Click to chang count!</div>
   <List list={state.list} />
  </div>
 );
};

export default App

计算属性

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const Child = () => {
 // 深度订阅 signal.count
 const [count] = useSignal('app', 'count', (value) => {
  return `count: ${value}`;
 });
 return (
  // count: 0
  <div>{ count }</div>
 );
};

const ChildNormal = () => {
 // 深度订阅 signal.length
 const [length] = useSignal('app', 'length');
 return (
  <div>length: { length }</div>
 );
}

const App = () => {
 const [state, setState] = createSignal('app', { count: 0, length: 0 });
 
 // 点击以后发现只有 <Child /> 组件更新了
 // 实际上 <ChildNormal /> 与 <Child /> 依赖同一个Signal
 const onAdd = () => {
  setState({ count: state.count + 1 });
 };

 return (
  <div>
   <div onClick={onAdd}>Click to chang count!</div>
   <Child />
   <ChildNormal />
  </div>
 );
};

export default App

变更合并

以下情况只触发一次render并且最终 count === 3

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const App = () => {
 createSignal('app', { count: 0 });
 const [state, setState] = useSignal('app');
 const onAdd = () => {
  setState({ count: state.count + 1 });
  setState({ count: state.count + 2 });
  setState({ count: state.count + 3 });
 };

 return (
  <div>
   <div onClick={onAdd}>Click to chang count!</div>
   <div>count: { state.count }</div>
  </div>
 );
};

export default App;

以下情况将会触发两次render

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const App = () => {
 createSignal('app', { count: 0 });
 const [state, setState] = useSignal('app');
 const onAdd = () => {
  setState({ count: state.count + 1 });
  setTimeout(() => {
   // 此时count === 1
   setState({ count: state.count + 2 })
  });
 };

 return (
  <div>
   <div onClick={onAdd}>Click to chang count!</div>
   <div>count: { state.count }</div>
  </div>
 );
};

export default App;

获取变更结果

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const App = () => {
 createSignal('app', { count: 0 });
 const [state, setState] = useSignal('app');
 
 const onAdd = () => {
  setState({ count: state.count + 1 }).then((state) => {
    console.log(state.count); // 1
  });
  console.log(state.count); // 0
 };

 return (
  <div>
   <div onClick={onAdd}>Click to chang count!</div>
   <div>count: { state.count }</div>
  </div>
 );
};

export default App;

高级使用

🚫注意:如果前面的使用已经可以满足你的需求,就不建议你使用以下的能力,因为这可能会给你的程序带来不可控的因素,所以一切尝试都建立在你足够了解use-signal!

Hooks扩展

React的useState有2个参数,这里扩展了四个,多出的两个是use-signal的衍生物

originalSetState: 当前组件真实的set方法,来自于 useState

signal: Signal 的实例,一切数据流转都依赖他

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const Child = () => {
 const [ state, setState, originalSetState, signal ] = useSignal('app');
 return (
  <div></div>
 );
};

const App = () => {
 createSignal('app', { count: 0 });
 return (
  <div>
   <Child />
  </div>
 )
};

export default App;

class SignalManager

Des:SignalManager是中心化管理Signal的类,他非常轻,逻辑也非常简单,内部是由一个Map对象维护的。所以,Signal的name其实也可以是一个对象,但是出于语义化考虑,还是建议使用字符串(例:”app“)来命名。

当然你也可以自定义管理者,请往下看Signal类。

import { useSignal, createSignal, initSingalManager } from 'react-use-signal';

const signalManager = initSingalManager({ count: 0 }, false);

class Signal

Des: 这就是发布订阅本身

import { Signal, SignalManager } from 'react-use-signal';

const mySignal = new Signal({
 state: {},
 name: 'app',
 key: 'app-key',
 manager: new SignalManager(),
 autoDestroy: false // 是否是Hooks实现,是顶层 createSigna('app', {}, autoDestroy) 的底层实现
});