1.0.14 • Published 10 months ago

react-use-signal v1.0.14

Weekly downloads
-
License
ISC
Repository
github
Last release
10 months ago

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) 的底层实现
});
1.0.11

10 months ago

1.0.10

10 months ago

1.0.14

10 months ago

1.0.13

10 months ago

1.0.12

10 months ago

1.0.9

1 year ago

1.0.8-beta

1 year ago

1.0.7-beta

1 year ago

1.0.6-beta

1 year ago

1.0.5-beta

1 year ago

1.0.4-beta

1 year ago

1.0.2-beta

1 year ago

1.0.1-beta

1 year ago

1.0.0-beta

1 year ago

1.0.0

1 year ago