preface
In recent years, more and more companies and departments have opted for cross-end solutions in the field of big front-end development, on the one hand, cross-platform front-end frameworks are becoming more mature, on the other hand, the number of native developers is decreasing. Therefore, it is necessary to master a cross-platform technology stack, which will be helpful in both breadth and depth.
So which technology solution should we choose? A few years ago, there would have been many answers. But it seems that there are only two mainstream solutions left on the market: the oft-heard React Native and Flutter. One from Facebook, one from Google.
There has been a lot of commentary on the merits of these two proposals, which basically form two camps. But in my opinion, they are not significantly different. If there was, it would have been eliminated by the market. And it looks like that disadvantage will soon be offset by a bunch of talented engineers coming up with a solution. It may be that competition helps to perfect the whole ecology. Apple, on the other hand, has been slow to catch up, perhaps because of the closed-source ecosystem, which is less eager to change than the other two.
Google’s ambitions, on the other hand, are big, with cross-platform solutions (Flutter or Kotlin) that will allow the community and developers to unify the language, and even the operating system (Fuchsia), in order to expand its reach. Facebook wants to use its years of experience in the front end to tap into all platforms with React. This may have been the original design of the two frames.
Microsoft is taking a different approach, putting a lot of effort into IDE (VSCode) to help build a better development experience and unify the development environment.
The SDK version
Flutter: 2.5.x
React Native: 0.64.x
1. The architecture
1.1 Design Concept
On the side of development, a senior summed up a very incisive view: the side of development is nothing more than three things, “data acquisition”, “state management”, “page rendering”. In the cross-end space, I understand the competition as “virtual machine”, “rendering engine”, “native interaction”, and “development environment”. Both Flutter and React Native (RN) have excellent solutions to these problems.
Dart VM is used on the VM. Dart supports both JIT and AOT compilation modes, which is what we call dynamic and static compilation. In the development stage, JIT compilation is used to achieve hot update preview and dynamic loading, while in the release stage, AOT mode is used to compile to machine code to ensure start-up speed and cross-end information transfer efficiency. On the rendering engine, Flutter uses the Skia rendering engine to render its view, avoiding differences in control rendering across platforms. Moreover, efficiency is improved by eliminating this layer of interaction. Native interaction is efficient because Dart is cross-platform, the underlying C++ has direct access to native apis, and information is transmitted using machine code binary messages.
Then RN. In the early architecture, virtual machines used JSC (Javascript Core) to perform calculations, so that it could fully reuse the JS ecosystem and attract a large number of front-end developers to participate. And because JS is naturally cross-platform, it makes sense to move the value of App across the terminal. RN doesn’t directly use WebKit or other Web engines on rendering engines, because the computational cost of building complex pages on the Web is far less than it would be with a pure native engine. So it reuses the native render channel directly, so that the experience is almost identical to the native experience.
At this point, though, you may find that while the early RN architecture took advantage of the existing ecology, it was not as self-contained as Flutter. The problem is that in the JSC to native render layer, a lot of Bridge is used, and information is passed back and forth across multiple threads through JSON serialization. Such consumption may not be obvious in simple interactions, but there will be significant lag in a large number of interactions and renderings, which has become a common point of criticism. However, in the new architecture, RN has also come up with new solutions to address these pain points, as described below.
However, we know that Flutter is not perfect. Although it makes everything by itself, due to the lack of a mature ecosystem, many problems need to be solved by enough wheels provided by the authorities or the community. Otherwise, developers will have to come up with their own solutions when they encounter certain problems. In addition, the Dart release phase uses static compilation, which increases efficiency but also lacks the flexibility of dynamic updates online.
1.2 Core Architecture
1.3.1 Flutter
The Architecture of Flutter is divided into three layers. We mostly only interact with the Flutter Framework layer. More platform-independent underlying capabilities have been encapsulated. This also makes the Flutter Framework very light. If you need more native capabilities, you usually use various Flutter plugins such as Camera.
So native abilities (wheels) depend on official and community output rates
1.3.2 the React
Contrast between old and New architectures
Old
The three threads are responsible for computation, rendering and Native interaction respectively. The intermediate interaction is transmitted by Bridge and JSON information format.
New
There are two main changes to the new architecture
-
JS bundles no longer depend on JSC(Javascript Core). In other words, it can compile and apply to any JS engine (V8, etc.).
-
By introducing JSI standard and implementing their methods based on JSI protocol, JS can directly reference C++ objects and vice versa. Interactions with natives are no longer bonded by Bridges.
The rendering engine still relies on native pipes. Presumably FB doesn’t have as many years of experience with Web rendering engines as Google, so the wheel doesn’t need to be rebuilt
A change on an RN Bridge
Old
You can see that the Bridge is very heavy
The original heavy Bridge was split into two modules, Farbric for UI and TurboModules for native interaction
Both modules are C++ modules that follow the JSI protocol
2. Core processes
2.1 Data Acquisition
2.1.1 Network Request
Flutter | React Native |
---|---|
Dart library C++ implementation | Reuse the existing JS libraries fetch, XMLHttpRequest, Axios |
Flutter
import 'package:http/http.dart' as http;
// It returns a Future object of a Flutter, similar to the JS Promise.
http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
Copy the code
RN
fetch('https://reactnative.dev/movies.json');
Copy the code
Other web libraries in the JS ecosystem are applicable
2.1.2 JSON Modeling
Flutter
The jSON_serializable library allows you to define models and attributes and generate jSON-to-model code directly from the command line.
@JsonSerializable(a)class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String.dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String.dynamic> toJson() => _$UserToJson(this);
}
Copy the code
Run the script command
flutter pub run build_runner build
Copy the code
React Native
The official JSON Model conversion library is not provided, so you need to find your own wheels.
2.2 Status Management
Flutter
Just as Flutter defined all controls as widgets, it was also divided into two types of widgets, one Stateful and the other Stateless.
Stateless
Stateless is Stateless and controls cannot be updated by state status
class MyScaffold extends StatelessWidget {
const MyScaffold({Key? key}) : super(key: key);
@overrideWidget build(BuildContext context) { ... }}Copy the code
Stateful
Stateful is a Stateful control that can be updated using state changes, but it is written differently from JS
class FavoriteWidget extends StatefulWidget {
const FavoriteWidget({Key? key}) : super(key: key);
// Override the createState method to implement state management
@override
_FavoriteWidgetState createState() => _FavoriteWidgetState();
}
// State Settings are usually written in private methods
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
/ /...
@overrideWidget build(BuildContext context) { ... Page build}void _toggleFavorite() {
// Use setState to change the state to trigger the Widget's re-rendering
setState(() {
...
_isFavorited = false; . }); }}Copy the code
Use InheritedWidget, Provider, and FlutterHook when transferring information upward.
React Native
React uses the React State mode, but also supports the popular Hook mode to use State, which is similar to the React mode.
// React Native Counter Example using Hooks!
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const App = () = > {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text>You clicked {count} times</Text>
<Button
onPress={()= > setCount(count + 1)}
title="Click me!"
/>
</View>
);
};
// React Native Styles
const styles = StyleSheet.create({
container: {
flex: 1.justifyContent: 'center'.alignItems: 'center'}});Copy the code
2.3 Page Rendering
2.3.1 in common
In terms of rendering methods, the theory is basically the same: first build a platform-independent Virtual Dom Tree, and then render by themselves or hand over to native rendering through their different implementations.
The benefit of a virtual tree is that the UI nodes can be updated locally, rather than fully, and are platform independent
-
Both frameworks are UI responsive frameworks
UI = f(state) THE UI depends only on its superclass and its own state
The Design of Flutter also borrowed a lot of React design ideas.
-
All components can be combined into a virtual tree (VDom), which is converted into RenderObj/VDom by each framework before actual rendering.
2.3.2 differences
2.3.2.1 layout
Flutter
In A Flutter, the UI Component is called a Widget. A Flutter encapsulates all possible controls as widgets, while RN does not encapsulate all controls. Instead, styles are separated from components and freely combined. So you don’t see long nesting in RN.
Nested combinations of Flutter widgets:
While it may seem reasonable to combine uIs, it’s a bit of a stretch when dealing with complex UI scenarios, such as rich text.
In RN, the UI Component is called Component. The layout uses Component (similar to Web UI elements) + Style (similar to CSS) instead of encapsulate various “styles + components” like Flutter widgets. It’s about putting the options in your hands.
import {View, Text, StyleSheet} from'the react - native'; class HelloThere extends React.Component { render() { return (
Hello World!
); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 } });Copy the code
2.3.2.2 draw
Flutter
As shown in the architecture mentioned above, Flutter does not need to deal with a native rendering engine, drawing directly via Skia (2D rendering engine) (GPU), so its rendering pipeline is very simple and efficient.
React Native
RN calculates the position through Yoga (Layout engine) and renders through the rendering pipes of different platforms. Therefore, there are more Bridge links in the process of Layout calculation and delivery results, which can be imagined as efficiency. \
The Flutter UI is what you see is what you get and behaves consistently across all platforms. RN relies on the platform’s native control style and behaves more native.
2.3.3 Rendering process
Flutter
As mentioned earlier, Flutter renders directly on the GPU after updating the UI Tree
React Native
React Render is similar in that it updates the VDom and then the real component, except RN is a Native component
2.4 Native Interaction
2.4.1 Mixed Development
Flutter
Embed Native pages inside Flutter
Fluttter provides AndroidView and UiKitView to support native page embedding. However, the use of these widgets requires attention to layout, event callback, etc. The official documentation does not recommend this scenario. While there are no architectural limitations, widgets are not currently supported on the desktop.
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the maps plugin');
Copy the code
Native embedded Fluttter
As shown in the Flutter Demo, it can be embedded in any Activity or ViewController.
The official advice is to load the Flutter environment properly when the application is initialized, or before displaying the Flutter page to the user. This is because there are a lot of things to do with Flutter initialization, such as loading the Flutter library, initializing the Dart VM, creating the Dart Isolate(memory and thread management), and UI initialization. The time consumed for preheating is about 300ms (refer to official data)
React Native
React Native and Native controls are relatively easy to interembed.
Native embedded in RN pages
iOS
RCTRootView can be thought of as a container for RN and can be added just like a regular View. Note that layout in RN should be set to flex layout to fit the container size.
- (void)viewDidLoad {
.
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:appName
initialProperties:props];
rootView.frame = CGRectMake(0.0.self.view.width, 200);
[self.view addSubview:rootView];
.
}
Copy the code
RN embedded Native pages
iOS
RCTViewManager inheritance. Then, as with event communication, we expose the Native corresponding class via RCT_EXPORT_MODULE, and implement the View method to return the Native view instance.
// RNTMapManager.m
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
@interface RNTMapManager : RCTViewManager
@end
@implementation RNTMapManager
RCT_EXPORT_MODULE(RNTMap)
- (UIView *)view
{
return [[MKMapView alloc] init];
}
@end
Copy the code
And then we insert that View directly into RN into the corresponding UI component.
import { requireNativeComponent } from 'react-native';
// requireNativeComponent automatically resolves 'RNTMap' to 'RNTMapManager'
module.exports = requireNativeComponent('RNTMap');
// MyApp.js
import MapView from './MapView.js'; .render() {
return <MapView style={{ flex: 1}} / >;
}
Copy the code
Android is embedded in a similar way
2.4.2 Event Communication
Flutter
Native <-> Shell (iOS /Android) <-> MethodChannel (Flutter Framework) <-> Dart Code
Message is converted between platforms, such as Map -> HashMap/Dictionary
Dart
const channel = MethodChannel('foo');
final String greeting = await channel.invokeMethod('bar'.'world');
print(greeting);
Copy the code
iOS
let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView)
channel.setMethodCallHandler {
(call: FlutterMethodCall, result: FlutterResult) - >Void in
switch (call.method) {
case "bar": result("Hello, (call.arguments as! String)")
default: result(FlutterMethodNotImplemented)}}Copy the code
Android
val channel = MethodChannel(flutterView, "foo")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"bar" -> result.success("Hello, ${call.arguments}")
else -> result.notImplemented()
}
}
Copy the code
React Native
Native
Classes or methods can be exposed to RN only by implementing corresponding protocols on the Native side
React usually calls them modules
iOS
// RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
// Add RCTBridgeModule to the corresponding Native Class declaration
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end
// RCTCalendarModule.m
#import "RCTCalendarModule.h"
@implementation RCTCalendarModule
// Expose this class to RN. If no name is specified, the class name is used by default
RCT_EXPORT_MODULE(a);// Expose a method to RN
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}
@end
Copy the code
Android
All libraries, reference to the following way to introduce an RN and inherit ReactContextBaseJavaModule, plus @ ReactMethod logo in exposed way
package com.your-app-name; // replace com.your-app-name with your app’s name
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class CalendarModule extends ReactContextBaseJavaModule {
CalendarModule(ReactApplicationContext context) {
super(context); }}// Expose method to RN
@ReactMethod
public void createCalendarEvent(String name, String location) {}Copy the code
RN
At this point, you can access the Native class through the following methods
const { CalendarModule } = ReactNative.NativeModules;
Copy the code
3. Development environment
3.1 coding
Flutter
Declarative language structures
Each Widget can continue to nest widgets, somewhat like Russian nesting dolls.
“SwiftUI” is also declarative and written in a similar way
var container = Container( // grey box
child: Center(
child: Container( // red box
child: Text(
"Lorem ipsum",
style: bold24Roboto,
),
decoration: BoxDecoration(
color: Colors.red[400],
),
padding: EdgeInsets.all(16),
width: 240.//max-width is 240
),
),
width: 320,
height: 240,
color: Colors.grey[300]);Copy the code
React Native
RN can support both functional programming hooks and Class writing. Styles are separated from component code, and no long nesting occurs.
3.2 debugging
In UI debugging, there are tools for both. In effect, RN is more like a DEBUGGING tool for JS, which is faster to get started.
react-devtools
Flutter Widget Inspector
The problem with both schemes, however, is that there seems to be no recommended practice when Native is combined with RN/Flutter, for example, when the break point is needed on both sides.
4. Maintenance costs
4.1 Environment Dependence
Flutter
- Flutter SDK
- XCode
- Android toolchain
React Native
- React Native SDK
- XCode
- Android toolchain
- Node
4.2 engineering
Flutter
Online code management can be used to conduct one-stop code submission and package the Flutter project, but there is no domestic platform to support this yet.
- Codemagic
- Bitrise
- Appcircle
Or manually set up the pipeline via Fastlane.
React Native
There is no official best practice, but since JS online packaging is supported by many platforms, it is only necessary to configure the corresponding Native engineering environment.
4.3 product
Flutter
With Flutter you can manually generate the end product using command-line tools
IOS generates two frameworks
flutter build ios-framework
Copy the code
- App. Framework (Your Dart code artifacts) ~ 100 KB (template empty project)
- Flutter. Framework (dependent Flutter library) ~ 100MB
Android can generate an AAR or APK
flutter build apk
Copy the code
- Libapp. so (your Dart code artifact) ~ 3.4MB (template empty project)
- Libflutter. So (Flutter engineering product) ~ 9 MB
React Native
Metro
RN uses the Metro (designed for React Native) packaging tool to package all RN code into the corresponding js.bundle products, which are of similar size.
You can also generate offline packages yourself from the command line:
react-native bundle --entry-file index.js --bundle-output ./bundle/ios.bundle --platform ios --dev false
Copy the code
–dev false generates a large difference in size between non-dev packages and dev packages.
The bundle generated by the official initialization project is about 750 KB
Performance of 5.
5.1 Rendering Performance
The refresh rate on most browsers and mobile devices is 60HZ, which means we can only do everything, including rendering, in 16ms per frame to keep the display smooth.
React Native
In terms of rendering efficiency, the official has also mentioned that most of our business logic and event processing are done on THE JS thread. Due to architectural reasons, after the JS thread finishes processing data, it will be thrown to the UI thread for Native control rendering. If the time is equal to 200ms, 12 frames will be lost. And there is a jam. If in any case more than 100ms will be perceived by the user. This usually happens when a new page is introduced and all the controls and layouts are computed for rendering.
Flutter
Because Flutter has fewer native controls to convert, it takes less time to bridge a step. The issues to be aware of, however, remain the same: the processing time of business logic, and the UI Tree hierarchy.
"configurations": [{"name": "Flutter"."request": "launch"."type": "dart"."flutterMode": "profile"}]Copy the code
You can install the Dart Extension with VS Code, then click on the Status bar below the status bar to open DevTools and view real-time performance.
DevTools
6. Comprehensive comparison
Flutter | React-Native | note | |
---|---|---|---|
Behind the team | |||
The release date | 2017.5 | 2015.3 | React-native is a more mature framework |
A programming language | Dart | Javascript | |
The learning curve | low | low | If you already know JS, you will be able to get started with RN more quickly. |
Thermal loading | is | is | |
Hot update | no | is | RN can deliver JS implementations. The Flutter product is already binary |
Open source | is | is | |
Document integrity | is | is | |
Programming architecture | State Manager | Flux | All based on state management |
Automated integration publishing | The official documentation | No official documentation available | |
Number of plug-in | ~20k | ~30k | If you include React, the plugin is around 200K |
The warehouse address | Flutter | React Native | |
Github Stars/Forks | 132k/19k | 99k/21k | |
product | ~10MB (Android) ~100MB (iOS) | ~ 70M (Android) ~ 40M (iOS) | Template empty engineering, multi – structure products |
When to choose a cross-platform framework
- When you don’t have a lot of UI dynamics and complex interfaces
- If you already have a native project and want to improve the development efficiency of some modules
- When you create a new project and want to try and error quickly
When is RN recommended?
- When you have an existing project and have many scenarios that you want to mix
- Have front-end page, want to transplant as soon as possible
- There are plenty of front-end developers and not enough Native people
- There are truly cross-terminal scenarios, iOS/Native/Web/Desktop
When is Flutter recommended?
- New project, not too many mixed development scenarios
- Existing projects do not have many nesting of Native and Flutter pages
- When there are high requirements for rendering performance and UI consistency on mobile devices
Refer to the article
- What is the core technology of cross-end frameworks?
- React Native Internals
Due to the limited level and time, the above article will inevitably have errors and omissions, not hesitate to correct!