Patró BLoC amb React Hooks
El Patró BLoC ha estat dissenyat per Paolo Soares i Cong Hui, de Google i presentat per primera vegada durant la DartConf 2018 (23-24 de gener de 2018). Veure el vídeo a YouTube.
BLoC significa Business Logic Component (Component de Lògica de Negoci). Inicialment concebut per compartir codi entre Flutter i Angular Dart, funciona independentment de la plataforma: aplicació web, aplicació mòbil o back-end.
Ofereix una alternativa al port de Redux per a flutter utilitzant streams de Dart. Utilitzarem Observables de RxJS, tot i que xstream funciona igualment bé.
En resum, el BLoC:
- contindrà lògica de negoci (idealment en aplicacions més grans tindrem múltiples BLoCs)
- dependrà exclusivament de l'ús d'Observables tant per a entrada (Observer) com per a sortida (Observable)
- romandrà independent de la plataforma
- romandrà independent de l'entorn
Com funciona BLoC?
Altres han explicat BLoC millor que jo, així que cobriré només el bàsic.

El BLoC manté la lògica de negoci; els components desconeixen els seus internals. Els components envien esdeveniments al BLoC via Observers i reben notificacions via Observables.
Implementant el BLoC
Aquí està un BLoC de cerca bàsic en TypeScript utilitzant RxJS:
export class SearchBloc {private _results$: Observable<string[]>;private _preamble$: Observable<string>;private _query$ = new BehaviorSubject<string>("");constructor(private api: API) {this._results$ = this._query$.pipe(switchMap((query) => {return observableFrom(this.api.search(query));}),);this._preamble$ = this.results$.pipe(withLatestFrom(this._query$, (_, q) => {return q ? `Resultats per a ${q}` : "Tots els resultats";}),);}get results$(): Observable<string[]> {return this._results$;}get preamble$(): Observable<string> {return this._preamble$;}get query(): Observer<string> {return this._query$;}dispose() {this._query$.complete();}}
results$ i preamble$ exposen valors asíncrons que canvien quan query
canvia.
query exposa un Observer<string> perquè els components afegeixin nous
valors. Dins de SearchBloc, _query$: BehaviorSubject<string> serveix com a
font del stream, i el constructor declara _results$ i _preamble$ per
respondre a _query$.
Utilitzant-lo en React
Per utilitzar-lo en React, crea una instància del BLoC i comparteix-la amb els components fills via context de React.
const searchBloc = new SearchBloc(new API());const SearchContext = React.createContext(searchBloc);
Exposa'l utilitzant el proveïdor de context:
const App = () => {const searchBloc = useContext(SearchContext);useEffect(() => {return searchBloc.dispose;}, [searchBloc]);return (<SearchContext.Provider><SearchInput /><ResultList /></SearchContext.Provider>);};
El useEffect retorna el mètode dispose, completant l'observer quan el
component es desmunta.
Publica canvis al BLoC des del component SearchInput:
const SearchInput = () => {const searchBloc = useContext(SearchContext);const [query, setQuery] = useState("");useEffect(() => {searchBloc.query.next(query);}, [searchBloc, query]);return (<inputtype="text"name="Search"value={query}onChange={({ target }) => setQuery(target.value)}/>);};
Obtenim el BLoC via useContext, després useEffect publica cada canvi de
consulta al BLoC.
Ara el ResultList:
const ResultList = () => {const searchBloc = useContext(SearchContext);const [results, setResults] = useState([]);useEffect(() => {return searchBloc.results$.subscribe(setResults);}, [searchBloc]);return (<div>{results.map(({ id, name }) => (<div key={id}>{name}</div>))}</div>);};
Utilitzem useContext per obtenir el BLoC, després useEffect es subscriu als
canvis de results$ per actualitzar l'estat local. Retornar la subscripció
cancel·la la subscripció quan el component es desmunta.
Pensaments finals
El codi final és directe amb coneixement bàsic d'Observables i hooks. El
codi és llegible i manté la lògica de negoci fora dels components. Hem de
recordar dessubscriure'ns dels observables i descartar el BLoC al desmuntar,
però hooks personalitzats com useBlocObservable i useBlocObserver podrien
resoldre això. Planejo provar-ho en un projecte paral·lel on utilitzo aquest
patró.