feat(infra): livedata (#5562)

LiveData is a reactive data type.

## basic usage

@example
```ts
const livedata = new LiveData(0); // create livedata with initial value

livedata.next(1); // update value

console.log(livedata.value); // get current value

livedata.subscribe(v => { // subscribe to value changes
 console.log(v); // 1
});
```

## observable

LiveData is a rxjs observable, you can use rxjs operators.

@example
```ts
new LiveData(0).pipe(
  map(v => v + 1),
  filter(v => v > 1),
  ...
)
```

NOTICE: different from normal observable, LiveData will always emit the latest value when you subscribe to it.

## from observable

LiveData can be created from observable or from other livedata.

@example
```ts
const A = LiveData.from(
  of(1, 2, 3, 4), // from observable
  0 // initial value
);

const B = LiveData.from(
  A.pipe(map(v => 'from a ' + v)), // from other livedata
  '' // initial value
);
```

NOTICE: LiveData.from will not complete when the observable completes, you can use `spreadComplete` option to change
this behavior.

## Why is it called LiveData

This API is very similar to LiveData in Android, as both are based on Observable, so I named it LiveData.
This commit is contained in:
EYHN
2024-01-30 06:31:11 +00:00
parent 588b3bcf33
commit c9f8e49f75
6 changed files with 597 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
import { use } from 'foxact/use';
import { useSyncExternalStore } from 'react';
import type { LiveData } from './index';
/**
* subscribe LiveData and return the value.
*/
export function useLiveData<T>(liveData: LiveData<T>): T {
return useSyncExternalStore(
liveData.reactSubscribe,
liveData.reactGetSnapshot
);
}
/**
* subscribe LiveData and return the value. If the value is nullish, will suspends until the value is not nullish.
*/
export function useEnsureLiveData<T>(liveData: LiveData<T>): NonNullable<T> {
const data = useLiveData(liveData);
if (data === null || data === undefined) {
return use(
new Promise((resolve, reject) => {
const subscription = liveData.subscribe({
next(value) {
if (value === null || value === undefined) {
resolve(value);
subscription.unsubscribe();
}
},
error(err) {
reject(err);
},
complete() {
reject(new Error('Unexpected completion'));
},
});
})
);
}
return data;
}