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 va ser concebut per permetre reutilitzar el mateix codi entre Flutter i Angular Dart, però de fet és independent de la plataforma: aplicació web, aplicació mòbil, back-end.
Pot considerar-se una alternativa al port de Redux per a flutter fent ús de streams de Dart. En el nostre cas, utilitzarem Observables de la llibreria RxJS , però qualsevol altra opció com xstream serà vàlida també.
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?
No explicaré extensament com funciona BLoC (hi ha altres persones que van fer una millor feina de la que faré aquí), sinó només algunes pistes bàsiques.

El BLoC mantindrà la lògica de negoci i els components no tindran coneixement sobre el que està succeint dins. Els components enviaran esdeveniments al BLoC via _Observers_ i seran notificats pel BLoC via Observables.
Implementant el BLoC
Aquest és un exemple bàsic en typescript d'un BLoC de cerca 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$ estan exposats per ser subscrits des d'un component i
expressen valors asíncrons que canvien en resposta a canvis en query.
query s'exposa com Observer<string> a l'exterior, per permetre l'addició
de nou valor des dels components. Dins de SearchBloc, tenim
_query$: BehaviorSubject<string> com la font del stream, i el constructor
declara _results$ i _preamble$ per respondre a _query$.
Utilitzant-lo en React
Per utilitzar-lo en React necessitem crear una nova instància del BLoC i compartir-la als components fills utilitzant un context de React.
const searchBloc = new SearchBloc(new API());const SearchContext = React.createContext(searchBloc);
Hem d'exposar-lo utilitzant el proveïdor de context:
const App = () => {const searchBloc = useContext(SearchContext);useEffect(() => {return searchBloc.dispose;}, [searchBloc]);return (<SearchContext.Provider><SearchInput /><ResultList /></SearchContext.Provider>);};
És important tenir el useEffect retornant el mètode dispose del BLoC
perquè completi l'observer quan el component es desmunti.
Després podem publicar 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)}/>);};
Vam obtenir el BLoC utilitzant el hook useContext i després amb useEffect cada vegada
que la consulta canvia publiquem el nou valor al BLoC.
Ara és el torn de 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>);};
Com abans, utilitzem el useContext per obtenir el BLoC, però ara en el useEffect
ens subscrivim als canvis en la interfície results$ per actualitzar l'estat local
del component. És important retornar el valor de retorn de la subscripció,
ja que es dessubscriurà quan el component es desmunti.
Pensaments finals
El codi final no és complex, almenys si tens un coneixement mínim sobre
Observables i hooks. Tinc la sensació que el codi és bastant llegible
i ajuda a mantenir la lògica de negoci fora dels components. És cert que
hauríem de tenir cura de dessubscriure'ns dels observables i rebutjar el BLoC
quan els components es desmunten, però aquests problemes podrien ser fàcilment solucionables
creant alguns nous hooks com useBlocObservable i useBlocObserver. Però això
ho intentaré en el futur, aviat espero, en un projecte paral·lel on estic utilitzant aquest patró.