- Using BLoC Pattern with React
- Original article by Charuka Herath
- Translation from: The Gold Project
- This article is permalink: github.com/xitu/gold-m…
- Translator: flashhu
- Proofreader: Jaredliw, Tong-h
BLoC Mode (Business Logic Component) was originally proposed by Google as a solution for state management in the Flutter application. It takes the burden off the UI components by separating the business logic from them.
Over time, other frameworks also began to support the BLoC model. This article will explore how we can use the BLoC mode in React.
Benefits of using BLoC mode in React
The meaning behind BLoC pattern is consistent with what the name suggests. As shown in the figure above, the business logic will be separated from the UI components. First, they send events to the BLoC via an observer. Next, BLoC notifies the UI component via An Observable after processing the request.
Let’s look at the advantages of this model in detail.
1. Update the flexibility of application logic
When the business logic is independent of the UI components, the impact on the application will be minimal. You will be able to modify the business logic at any time without affecting the UI components.
2. Reuse logic
Because the business logic is written in one place, UI components can reuse the logic, eliminating the need to copy code, thus increasing the simplicity of the application.
3. Convenience of testing
When writing test cases, developers can focus on BLoC itself. So the code base will not be messed up.
4. Scalability
Over time, product requirements may change and business logic grows accordingly. In such cases, developers can even create multiple BLoC to keep the code clean.
In addition, the BLoC model is platform and environment independent, so developers can use the same BLoC model across many projects.
Put the concept into practice
Let’s build a small counting program to demonstrate how to use BLoC mode.
Step 1: Create the React application and initialize the structure
First, we need to create a new React app. I’ll call it bloc-counter-app. In addition, I will use RXJS.
// Create the React app NPX creation-react-app bloc-counter-app // Install RXJS yarn add RXJSCopy the code
Then, you need to remove all unnecessary code and adjust the file structure as shown in the following image.
- Blocs — To store the bloc we need
- Components – Store UI Components
- Utils – the utility class file that holds the project
Step 2: Achieve BLoC
Now let’s implement the BLoC class. The BLoC class will be responsible for implementing all subjects related to the business logic. In this case, it is responsible for implementing the counting logic.
So I created the file CounterBloc. Js in the Blocs folder and passed the counter to the UI component using the pipe method on the Subject.
import { Subject } from 'rxjs';
import { scan } from 'rxjs/operators';
export default class CounterBloc {
constructor() {
this._subject = new Subject();
}
get counter() {
return this._subject.pipe(scan((count, v) = > count + v, 0));
}
increase() {
this._subject.next(1);
}
decrease() {
this._subject.next(-1);
}
dispose() {
this._subject.complete(); }}Copy the code
As you can see, there is some simple logic in this class. However, as the size of the application continues to grow, imagine how complex the application would be if we didn’t separate the business logic.
Step 3: Add intermediate classes to make your code more elegant
In this step, I’ll create the file StreamBuild.js in the utils folder to handle count requests from UI components. In addition, developers can handle errors here and implement custom handlers.
class AsyncSnapshot {
constructor(data, error) {
this._data = data;
this._error = error;
this._hasData = data ? true : false;
this._hasError = error ? true : false;
}
get data() {
return this._data;
}
get error() {
return this._error;
}
get hasData() {
return this._hasData;
}
get hasError() {
return this._hasError; }}Copy the code
In the AsyncSnapshot class, we can initialize the constructor, handle the data (check availability, etc.), and handle errors. But in this case, for the sake of demonstration, I only return the data.
class StreamBuilder extends Component {
constructor(props) {
super(props);
const { initialData, stream } = props;
this.state = {
snapshot: new AsyncSnapshot(initialData),
};
stream.subscribe(
data= > {
this.setState({
snapshot: new AsyncSnapshot(data, null)}); }); }render() {
return this.props.builder(this.state.snapshot); }}Copy the code
The initial data is passed into the AysncSnapshot class and stored in the snapshot state for each subscription. It will then be rendered into the UI component.
import { Component } from 'react';
class AsyncSnapshot {
constructor(data) {
this._data = data;
}
get data() {
return this._data; }}class StreamBuilder extends Component {
constructor(props) {
super(props);
const { initialData, stream } = props;
this.state = {
snapshot: new AsyncSnapshot(initialData),
};
stream.subscribe(
data= > {
this.setState({
snapshot: new AsyncSnapshot(data, null)}); }); }render() {
return this.props.builder(this.state.snapshot); }}export default StreamBuilder;
Copy the code
Note: Make sure to unsubscribe all Observables and handle the BLoC when uninstalling UI components.
Step 4: Implement UI components
As you can see, the increase() and Decrease () methods are called directly in the UI component. However, the output data is processed by the Stream Builder.
It is best for the middle tier to implement custom handlers to handle errors.
import { Fragment } from 'react';
import StreamBuilder from '.. /utils/StreamBuilder';
const Counter = ({ bloc }) = > (
<Fragment>
<button onClick={()= > bloc.increase()}>+</button>
<button onClick={()= > bloc.decrease()}>-</button>
<lable size="large" color="olive">
Count:
<StreamBuilder
initialData={0}
stream={bloc.counter}
builder={snapshot= > <p>{snapshot.data}</p>} / ></lable>
</Fragment>
);
export default Counter;
Copy the code
In the app.js file, BLoC is initialized using the CounterBloc class. Therefore, when in use, the Counter component receives BLoC as props.
import React, { Component } from 'react';
import Counter from './components/Counter';
import CounterBloc from './blocs/CounterBloc';
const bloc = new CounterBloc();
class App extends Component {
componentWillUnmount() {
bloc.dispose();
}
render() {
return (
<div>
<Counter bloc={bloc} />
</div>); }}export default App;
Copy the code
That’s it. Now you can treat the business logic as a separate entity outside of the UI components and modify it to suit your needs.
If you want to use or improve on this sample application, refer to the project repository, and don’t forget to mention PR. 😃
Final thoughts
In my experience, BLoC can become a common solution for small projects.
However, as the project grew, using the BLoC model helped build modular applications.
In addition, you must have a basic understanding of RXJS and understand how Observables work when implementing BLoC mode in the React project.
Therefore, I invite you to try using BLoC mode and share your thoughts in the comments section.
Thank you so much for reading!!
If you find any errors in the translation or other areas that need improvement, you are welcome to revise and PR the translation in the Gold Translation program, and you can also get corresponding bonus points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
Diggings translation project is a community for translating quality Internet technical articles from diggings English sharing articles. The content covers the fields of Android, iOS, front end, back end, blockchain, products, design, artificial intelligence and so on. For more high-quality translations, please keep paying attention to The Translation Project, official weibo and zhihu column.