-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from Rytass/react/use-observable-value
add react/use-observable-value
- Loading branch information
Showing
1 changed file
with
141 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# use-observable-value (Abstract) | ||
|
||
## 關鍵字 (key) | ||
--- | ||
- 底層元件 -> 狀態更新 | ||
- 大量元件 -> 減少重新渲染 | ||
- 前台效能優化 | ||
|
||
## 範例 (Implementation) | ||
--- | ||
- use-selector (Sportsbook) | ||
|
||
- ![Image](https://media.giphy.com/media/l7xx2FRg6oULswYrqt/giphy.gif) | ||
|
||
## 情境 (Scenario) | ||
--- | ||
> 當前台需要 ```大量``` 子元件狀態監聽時。 | ||
1. 如果把狀態監聽寫在較高的層級,當有更新時,會導致不需要更新的子元件,因爲其他元件更新而重新渲染。 | ||
|
||
2. 在小規模的資料結構上面,或許這是可以接受的。畢竟效能過剩時,是完全沒有差別的,然而當規模提昇至 百、千 個元件時,這樣的更新,會大大影響效能,輕則耗電飆高,重則卡當影響使用體驗。 | ||
|
||
> 想法: | ||
1. 較高層級的資料結構只需要,把所有的子元件 onMount 出來,然後每個子元件在 生命週期建立時,透過 ```EventEmitter``` 進行狀態監聽以及通知,透過 ```Observable``` 進行狀態更新時的行為。 | ||
|
||
2. ```EventEmitter``` 負責監聽狀態,當對應的子元件有狀態變動,可以透過 ```EventEmitter``` 主動通知 ```Observable``` 去更新重渲染對到的元件。因此每個 子元件 都有獨立的狀態 scope,不需要仰賴高層級的元件重繪,達成低層級的狀態更新。 | ||
|
||
> 優劣分析: | ||
* 優點 | ||
- 可以達成效能極大優化。 | ||
- 每個小元件的更新,不會影響到其他同層級的元件重新渲染。 | ||
- 效能 效能 效能。 | ||
* 缺點 | ||
- 需要自己寫資料結構,控制每個元件的更新,變得比較麻煩。 | ||
- 程式碼變多,維護成本提高。 | ||
- 需要學習使用 ```rxjs```, ```event-emitter```。 | ||
|
||
## 基本資料結構建立 | ||
--- | ||
- ```EventEmitterStore (class)``` | ||
```typescript | ||
import { EventEmitter } from 'eventemitter3'; | ||
import { Observable } from 'rxjs'; | ||
|
||
export class SelectorStore<T = boolean, R = Error> extends EventEmitter { | ||
static Events = { | ||
UPDATE: 'E/UPDATE', | ||
FAILED: 'E/FAILED', | ||
}; | ||
|
||
private _observableMap: Map<string, Observable<T>> = new Map(); | ||
|
||
private _selectorStore: Map<string, T> = new Map(); | ||
|
||
/** @Method 取得對應監聽中元件的狀態 (值) */ | ||
get(requestKey: string) { | ||
return this._selectorStore.get(requestKey); | ||
} | ||
|
||
/** @Method 更新對應監聽元件的狀態,這是 public setter */ | ||
add(requestKey: string, data: T) { | ||
this._selectorStore.set(requestKey, data); | ||
|
||
/** @INFO 觸發 observable 去更新重新渲染對應 requestKey 的 訂閱元件 */ | ||
this.emit(`${SelectorStore.Events.UPDATE}:${requestKey}`, data); | ||
} | ||
|
||
/** @Method 監聽對應的 key -> 回傳一個 observable */ | ||
watch(requestKey: string) { | ||
const storedObservable = this.observableMap.get(requestKey); | ||
|
||
const observable = storedObservable || new Observable<T>((subscriber) => { | ||
/** @INFO 更新時的行為函式 */ | ||
function onUpdate(selected: T) { | ||
subscriber.next(selected); | ||
} | ||
|
||
function onError(ex: R) { | ||
subscriber.error(ex); | ||
} | ||
|
||
// ... 可以自定義更多的監聽行為 | ||
|
||
/** @INFO 註冊更新時要觸發的行為 */ | ||
this.on(`${SelectorStore.Events.UPDATE}:${requestKey}`, onUpdate); | ||
|
||
this.on(`${SelectorStore.Events.FAILED}:${requestKey}`, onError); | ||
|
||
// ... | ||
}); | ||
|
||
if (!storedObservable) { | ||
this._selectorObservableMap.set(requestKey, observable); | ||
} | ||
|
||
return observable; | ||
} | ||
} | ||
|
||
/** @INFO EventEmitterStore is a singleton */ | ||
export const selectorStore = new SelectorStore(); | ||
``` | ||
|
||
## React Custom Hook | ||
--- | ||
> 示範子元件是否被選到。 | ||
- ```useIsSelected``` | ||
```typescript | ||
export function useIsSelected(selectorId: string) { | ||
const [isSelected, setIsSelected] = useState<boolean>(selectorStore.get(selectorId) ?? false); | ||
|
||
useEffect(() => { | ||
const subscription = selectorStore | ||
.watch(selectorId) // 訂閱監聽 | ||
.subscribe({ // observable 註冊更新行為 | ||
next(nextData) { // 觸發定義的 更新行為 | ||
setIsSelected(nextData); | ||
}, | ||
|
||
// error ...etc | ||
}); | ||
|
||
return () => { | ||
subscription.unsubscribe(); // componentWillUnMount 要取消狀態訂閱狀態,以免發生 memory leak 的狀況。 | ||
}; | ||
}, [selectorId]); | ||
|
||
return isSelected; | ||
} | ||
|
||
``` | ||
|
||
## References | ||
|
||
--- | ||
|
||
- [rxjs - Observable](https://rxjs.dev/guide/observable) | ||
- [eventemitter3](https://github.com/primus/eventemitter3#readme) |