React Native (2): механизм субподряда и динамическая доставка
предисловие
С появлением Flutter популярность React Native постепенно снижается, да и сам facebook находится в процессе рефакторинга React Native. В текущей ситуации, если React Native хочет разработать законченное приложение или стать частью приложения, разработчики должны иметь возможность понимать механизм реализации обеих сторон клиента, потому что в клиентский код необходимо внедрить множество зависимостей. , Не говоря уже о мосте между нативом и React Native.
В этом сценарии React Native по-прежнему находит свое место во многих крупных приложениях.Механизм горячего обновленияReact Native по-прежнему остается самым гибким и трудным для отказа.
В сравнении
Когда дело доходит до React Native, вы должны упомянуть Flutter. Но, на мой взгляд, они не в одном направлении. Flutter надеется унифицировать опыт разработки на обоих концах, чтобы набор кода мог выполняться напрямую на обоих концах без изменений. React Native, с другой стороны, может решить недостаток традиционного гибридного приложения, которым является опыт H5. Даже на мобильных устройствах высокого класса трудно достичь почти родного опыта работы с H5, не говоря уже о том, что большинство пользователей по-прежнему используют модели среднего или даже бюджетного уровня.
Чтобы обеспечить опыт этих пользователей, мы должны пожертвоватьчастичная гибкость, используя решение React Native для обеспечения динамических обновлений и обратной совместимости. Ведь в большинстве случаев большинство функций, которые клиент должен предоставить H5 или React Native, будут определены в начале доступа. Начиная с нативной версии React Native, вы можете сделать свой код практически безболезненным.
Сцены
Сценарии использования React Native обычно включают следующее:
- Полное приложение разработано в React Native:
Это решение больше подходит для индивидуальных разработчиков. Мобильные приложения не могут отказаться ни от одной из двух платформ: для отдельных разработчиков стоимость изучения Objective-C/Swift и kotlin/Java одновременно слишком высока, а скорость итерации не может быть гарантирована. Таким образом, React Native для всего сайта стал возможным выбором, и это также лучший выбор. Это может не только дать пользователям лучший опыт, но и обеспечить итеративную эффективность. Конечно, когда Flutter популярен сегодня, все больше разработчиков используют Flutter для замены React Native для унифицированной разработки на обоих концах.В Flutter меньше ям, опыт на обоих концах более унифицирован, а опыт разработки лучше, чем в текущей версии React Native. .
- Частично динамический:
Это решение применялось во многих крупномасштабных приложениях, включая продукты с десятками миллионов DAU, с которыми я сталкивался. В некоторых бизнес-сценариях нам нужны не только независимые обновления клиентской версии, но и лучший пользовательский интерфейс. На данный момент вам нужно разрабатывать на основе React Native. Как правило, RCTRootView React Native монтируется в представлении на стороне клиента, и все представление обрабатывается React Native.
Этот сценарий в основном представляет собой бизнес-модуль, который полностью разработан с помощью React Native.Через механизм субподряда базовая библиотека React Native помещается в один пакет, а затем бизнес-код помещается в другой пакет, и эти два пакета независимы. .Обновление, потому что обновление базовой библиотеки не частое, а обновление бизнес-кода может быть более частым.
О субподряде и дистрибуции будет сказано позже.
- (Чрезвычайно) динамичный -- следите за доставкой данных:
Такое решение не является общим решением, это также решение, которое мы нашли в соответствии с нашими собственными бизнес-сценариями, и это также решение, над которым мы думали.
Однако, как и в предыдущем решении, могут быть случаи, когда нам не нужно управлять всем представлением в React Native, потому что производительность длинного списка React Native не очень хороша. так какпоток подачиТакие сценарии вызовут серьезные проблемы, такие как чрезмерное потребление памяти моделями Android или сбои и флэшбэки.
Более того, иногда контент в ленте будет более динамичным, и если требования соблюдены публикацией, он может не успевать за ритмом.
Например, вставить рекламу странной формы в место, указанное стрелкой (возьмем, к примеру, Наггетс, картинка захвачена и удалена)
Поэтому мы рассматриваем рендеринг некоторых высокодинамичных ячеек в потоке фида через React Native. Первоначальный план такой же, как и предыдущий: мы передаем код бизнес-ячейки и упаковываем его в бизнес-пакет. Если есть новая ячейка, пользователь может отобразить новое содержимое ячейки, обновив бизнес-пакет.
Если это так, то все будет мирно~
Сначала рассмотрите проблемы, которые будут существовать в этом распределении. Когда мы запускаем приложение и введите корм, наше приложение обнаруживает, что бизнес-пакет изменился, и перейдет в CDN, чтобы вытащить бизнес-пакет, затем загрузить его, а затем выполнить Бизнес-пакет через JSCORE. Код JavaScript в том, что мы можем сделать. Похоже, что нет проблем, но поскольку другие модули потока подачи отображаются народным, после создания обновления и пакет обслуживания медленно выталкивается, нативная ячейка RACT будет в течение длительного времени и может быть вызвана по пакету. Ошибка в одной из клеточных модулей вызывает ошибки других рендеринга клеток.
Поэтому мы приняли новое решение: отделить бизнес-пакет каждой ячейки, заархивировать пакет, закодировать его с помощью base64, а затем отправить вместе с данными рендеринга каждой ячейки.
Преимущества этого:
-
Пакет поставляется с бизнес-данными.Поскольку каждый бизнес-пакет очень мал, время распаковки и загрузки очень короткое, что в основном гарантирует завершение загрузки данных и завершение рендеринга ячейки.
-
Область действия каждого пакета изолирована, и ошибка, сообщаемая одним пакетом, не повлияет на выполнение кода других пакетов.
-
Пакеты и услуги сотовой связи соответствуют друг другу. Если стиль или функцию ячейки необходимо обновить, вам нужно только настроить новый пакет и сохранить его. Когда новые данные распространяются в фоновом режиме, вы можете напрямую вытащить новый пакет, каждый не требует обновления всего бизнес-пакета.
Конечно, почти для всех проблем серебряной пули не существует, у этого решения есть свои проблемы, о проблемах, с которыми я столкнулся, я расскажу в конце статьи.
субподряд
Динамические решения React Native трудно отделить от одногосубподряд. Базовая библиотека React Native не мала, плюс некоторые необходимые нам зависимости, такие как публичные зависимости, такие как React-Native-Vector-Icons, эти нечасто меняющиеся зависимости нужно отделить от часто меняющегося бизнес-кода и сжать бизнес-код. , чтобы обеспечить оптимальный пакет услуг при обновлении.
metro
React Native предоставил его раньшеmetro
для подпакета пакета. Чтобы использовать метро для упаковки, вам нужно настроитьmetro.config.js
файл для субподряда.
вотОфициальная документация по конфигурации
В документации, похоже, есть много вариантов, но многие из них предназначены для конкретных сценариев.
Вариантов, которые нам нужно использовать для субподряда, в основном два:
-
createModuleIdFactory
: эта функция передает абсолютный путь к файлу модуля для упаковки и возвращает идентификатор, сгенерированный модулем при его упаковке. -
processModuleFilter
: эта функция передает информацию о модуле и возвращает логическое значение,false
Это означает, что этот файл не входит в текущий пакет.
Стратегия субподряда
Наша субподрядная стратегия заключается в следующем:
-
common.bundle
: ввести все общедоступные зависимые библиотеки, которые распространяются вместе с клиентской версией и не выполняют горячих обновлений; -
business.bundle
: База бизнес-кода крупных бизнес-модулей, количество этого пакета связано с количеством бизнес-модулей, которое указано в первом разделе.Частично динамическийБизнес-пакет, используемый в сценарии. -
RN-xxx.bundle
: может быть несколько бизнес-пакетов для разных ячеек в потоке фида, в зависимости от типа ячейки. так было сказано в первой частиСледуйте данным, выданнымБизнес-пакет, используемый в сценарии.
в,common.bundle
а такжеbusiness.bundle
предварительно упакован в клиентский код, потому что два пакета больше, иbusiness.bundle
Поддержка динамического распространения.common.bundle
Если объем слишком большой, то лучше со спокойной душой поместить его в клиентский код, иначе стоимость доставки будет слишком высока. И в большинстве случаев, если вам нужно добавить новую зависимость React Native, вы должны добавить соответствующий код зависимости на стороне клиента в клиенте (то есть, когда вы выполняете ссылку на реакцию, клиентская сторона будет добавлен в зависимость на стороне клиента), поэтомуcommon.bundle
Также разумно следить за выпуском клиента.
субподряд
Основной комплект (common.bundle)
Поскольку конфигурация Metro может разделять зависимости, сначала упакуйте необходимыеcommon.bundle
Код импортируется в файл:
// common.js
import {} from 'react';
import {} from 'react-native';
import {} from 'react-redux';
import Sentry from 'react-native-sentry';
// 还可以增加一些公共代码,比如统一监控之类的
Sentry.config('dsn').install();
соответствующий,common.bundle
Требуется файл конфигурации:
'use strict';
const fs = require('fs');
const pathSep = require('path').sep;
function manifest (path) {
if (path.length) {
const manifestFile = `./dist/common_manifest_${process.env.PLATFORM}.txt`;
if (!fs.existsSync(manifestFile)) {
fs.writeFileSync(manifestFile, path);
} else {
fs.appendFileSync(manifestFile, '\n' + path);
}
}
}
function processModuleFilter(module) {
if (module['path'].indexOf('__prelude__') >= 0) {
return false;
}
manifest(module['path']);
return true;
}
function createModuleIdFactory () {
return path => {
let name = '';
if (path.startsWith(__dirname)) {
name = path.substr(__dirname.length + 1);
}
let regExp = pathSep == '\\' ?
new RegExp('\\\\', "gm") :
new RegExp(pathSep, "gm");
return name.replace(regExp, '_');
}
}
module.exports = {
serializer: {
createModuleIdFactory,
processModuleFilter
}
};
После завершения упакованной конфигурации выполните:
node node_modules/react-native/local-cli/cli.js bundle --platform ios --dev false --entry-file ./common.js --bundle-output ./dist/common.bundle --config ./common.config.js
Эта команда очень длинная, вы можете в соответствии с их потребностями, пишетshell
,python
илиpackage.json
в сценарии.
Таким образом, мы получаем два файла:
-
common.bundle
:всеcommon.js
Все публичные зависимости, представленные в этом бандле, будут упакованы в этот бандл, при импорте на стороне клиента этот бандл можно импортировать первым. -
common_manifest_ios(android).txt
: Сохраняет информацию о зависимостях в основном пакете, чтобы при печати бизнес-пакета, прочитав содержимое этого файла, можно было определить зависимости, которые были введены в основной пакет, и не повторять упаковку.
бизнес-пакет
Все пакеты услуг, в том числе поставляемые с запросом, а также поставляемые с клиентской версией или патчем, имеют одинаковый процесс упаковки и могут быть изменены в соответствии с вашими потребностями.
// 我们这里用 charts 作为我们需要打包的业务包名字,当然你可以根据需求来随便起名~
// charts.js
import React from 'react';
import { AppRegistry, View } from 'react-native';
export default class Charts extends React.Component {
render() {
return (
<View>
<Text>Charts</Text>
</View>
);
}
};
AppRegistry.registerComponent('charts', () => Charts);
Конфигурация упаковки:
// business.config.js
'use strict'
const fs = require('fs');
const pathSep = require('path').sep;
var commonModules = null;
function isInManifest (path) {
const manifestFile = `./dist/common_manifest_${process.env.PLATFORM}.txt`;
if (commonModules === null && fs.existsSync(manifestFile)) {
const lines = String(fs.readFileSync(manifestFile)).split('\n').filter(line => line.length > 0);
commonModules = new Set(lines);
} else if (commonModules === null) {
commonModules = new Set();
}
if (commonModules.has(path)) {
return true;
}
return false;
}
function processModuleFilter(module) {
if (module['path'].indexOf('__prelude__') >= 0) {
return false;
}
if (isInManifest(module['path'])) {
return false;
}
return true;
}
function createModuleIdFactory() {
return path => {
let name = '';
if (path.startsWith(__dirname)) {
name = path.substr(__dirname.length + 1);
}
let regExp = pathSep == '\\' ?
new RegExp('\\\\',"gm") :
new RegExp(pathSep,"gm");
return name.replace(regExp,'_');
};
}
module.exports = {
serializer: {
createModuleIdFactory,
processModuleFilter,
}
};
Конфигурация упаковки бизнес-пакета очень похожа на конфигурацию common.bundle. Разница в том, что зависимости, упакованные в common.bundle, необходимо фильтровать при упаковке бизнес-пакета. пакет раздается.Большой.
Мы проходим вышеprocessModuleFilter
фильтровать и возвращать, находится ли текущий путь в предыдущемmanifest
В файле определите, нужно ли фильтровать.
После завершения настройки выполните:
node node_modules/react-native/local-cli/cli.js bundle --platform ios --dev false --entry-file ./src/charts.js --bundle-output ./dist/charts.bundle --config ./business.config.js
Вы можете получить очень оптимизированный пакет бизнес-кода. Приведенный выше небольшой фрагмент кода должен быть менее 1 КБ в упаковке.По сравнению с размером от нескольких КБ до десятков КБ для доставки H5, можно сказать, что он очень экономит сетевые ресурсы.
Перед сжатием:
После сжатия:
Видно, что после сжатия zip размер пакета практически незначителен.
Доставка и загрузка клиентов
После того, как упаковка завершена, ее необходимо обработать на бизнес-уровне. Эту часть можно разделить на две части: во-первых, нам нужно сохранить пакет кода, а затем отправить его клиенту. После того, как клиент загрузит эти пакеты, их можно отобразить клиенту пользователя.
Изданный
По результатам субподряда, полученным в предыдущем разделе, получаемcommon.bundle
Этот пакет зависимостей, который содержит общие зависимости, также получаетcharts.bundle
Бизнес-пакет, этот бизнес-пакет не содержит кода, связанного с общими зависимостями.
common.bundle
Так как изменений немного, а размер этого пакета, как правило, относительно велик, его можно запаковать прямо в клиент. Конечно, если вы хотите сохранить функцию динамического обновления, это также возможно.
Специальные сценарии для нашего приложения:
Несколько ячеек React Native вставляются в поток прокручиваемой ленты, поэтому мы принимаем решение, описанное выше, и отправляем пакет этой ячейки вместе с данными.
Преимущество этого в том, что:
- Поток фида, скорее всего, будет первым интерфейсом, в который входит пользователь. Пакет отправляется вместе с данными, что может предотвратить проблему с белым экраном при обновлении пакета. Нет другой обработанной ячейки, и React Родная ячейка все еще загружает обновление пакета.
- По сравнению с родным, мы можем получить пользовательский опыт, аналогичный клиенту, а также иметь функцию динамического обновления. Поток подачи используется в качестве носителя для переноса времени чтения пользователя. Иногда необходимо динамически вставлять действия или рекламный контент.
Конечно, есть и определенные недостатки, то есть клиентские функции нужно поддерживать заранее, а при добавлении новых функций может потребоваться их переиздание.common.bundle
Мешок.
Если пакет доставляется с использованием метода доставки данных, наилучшей стратегией будет заархивировать его, чтобы уменьшить размер пакета, а затем base64 закодировать сжатый zip-пакет, преобразовать его в форму строки и загрузить, отправленный в клиент.
Конечно, вы также можете спроектировать и внедрить платформу для загрузки и настройки пакета.После завершения настройки вы можете напрямую поместить его в библиотеку, чтобы серверная часть встречала содержимое React Native при распределении данных и напрямую извлекает base64 соответствующего пакета.
Загрузка клиента
Процесс загрузки React Native на стороне клиента и процесс выполнения кода были описаны в предыдущей статье. Для динамической загрузки React Native неизбежно изменение кода на стороне клиента. Вот краткое изложение всей схемы загрузки React Native на клиенте iOS:
нагрузка
Для React Native мост между нативным кодом и кодом JavaScript опирается наRCTBridge
преодолеть. Включая выполнение кода JavaScript до тех пор, пока рендеринг на стороне клиента не станет нативным компонентом, а также процесс взаимной связи между JavaScript и нативом. Конечно, то же самое касается загрузки пакетов.
Во-первых, нам нужен класс, управляющий загрузкой пакетов, который наследуется от<React/RCTBridge.h>
.
NSString *const COMMON_BUNDLE = @"common.bundle";
// BundleLoader.m
@interface RCTBridge (PackageBundle)
- (RCTBridge *)batchedBridge;
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync;
@end
@interface BundleLoader()<RCTBridgeDelegate>
// 一些加载相关的变量
@property (nonatomic, strong) RCTBridge *bridge;
@property (nonatomic, strong) NSString *currentLoadingBundle;
@property (nonatomic, strong) NSMutableArray *loadingQueue;
@property (nonatomic, strong) NSMutableDictionary *bundles;
@property (nonatomic, strong) NSMutableSet *loadedBundle;
@property (nonatomic, copy) NSString *commonPath;
@end
@implementation BundleLoader
// 由于这个实例需要是唯一的,所以我们实现一个单例
+ (instancetype)sharedInstance {
static dispatch_once_t pred;
static BundleLoader *instance;
dispatch_once(&pred, ^{
instance = [[BundleLoader alloc] init];
});
return instance;
}
// 进行类的初始化
- (instancetype)init {
self = [super init];
if (self) {
// 这里还要初始化各种变量
// 在 Native 中打印 React Native 中的日志,方便真机调试
RCTSetLogThreshold(RCTLogLevelInfo);
RCTAddLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
NSLog(@"React Native log: %@, %@", @(source), message);
});
// 保证 React Native 的错误被静默
RCTSetFatalHandler(^(NSError *error) {
NSLog(@"React Native Fatal Error: %@", error.localizedDescription);
// 将错误事件上报,进行统一处理
[[NSNotificationCenter defaultCenter] postNotificationName:ReactNativeFatalErrorNotification object:nil];
});
}
[self initBridge];
return self;
}
// 进行 React Native 的初始化
- (void)initBridge {
if (!self.bridge) {
// 加载 common.bundle,并且将其标记为正在加载
commonPath = [self loadCommonBundle];
currentLoadingBundle = COMMON_BUNDLE;
// 初始化 bridge,并且加载主包
self.bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
// 初始化所有事件监听
[self addObservers];
}
}
// 这个方法 override 了 RCTBridge 的同名方法,指定了主包所在的位置来让 RCTBridge 进行初始化
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
NSString *filePath = self.commonPath;
NSURL *url = [NSURL fileURLWithPath:filePath];
return url;
}
- (void)addObservers {
@weakify(self)
// JavaScript 包加载完成后触发
[[NSNotificationCenter defaultCenter] addObserver:self name:RCTJavaScriptDidLoadNotification dispatchQueue:dispatch_get_main_queue() block:^(NSNotification *notification) {
@strongify(self)
[self handleJSDidLoadNotification:notification];
}];
// JavaScript 包加载失败触发
[[NSNotificationCenter defaultCenter] addObserver:self name:RCTJavaScriptDidFailToLoadNotification dispatchQueue:dispatch_get_main_queue() block:^(NSNotification *notification) {
@strongify(self)
[self handleJSDidFailToLoadNotification:notification];
}];
}
// 将沙盒中的 common.bundle 拷贝到目标应用程序目录当中,并且推入到 bundle 加载队列当中
- (void)loadCommonBundle {
// 完成 common.bundle 的拷贝,得到文件所在的目录
// 省略了拷贝沙盒文件的过程,得到文件的路径: path
NSString *path = @"这里是 common ";
return path;
}
// 加载当前队列的第一个包
- (void)loadBundle {
// 取出队列中的第一个包
NSDictionary *bundle = self.loadingQueue.firstObject;
if (!bundle) {
return;
}
NSString *bundleName = bundle.name;
NSString *path = bundle.path;
// 如果在加载业务包的时候,COMMON 包还没有加载,则将业务包暂存
if (![self.loadedBundle containsObject:bundleName] && bundleName != COMMON_BUNDLE) {
return;
}
// 标记当前正在加载的包
self.currentLoadingBundle = bundleName;
[self.loadingQueue removeFirstObject];
// 如果需要加载的 bundle 不存在,则继续加载下一个 bundle
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:ReactNativeDidFailToLoadNotification object:nil bundle:@{@"name": bundleName}];
[self loadBundle];
});
return;
}
NSURL *fileUrl = [NSURL fileURLWithPath:path];
// 加载并且执行对应的 bundle
@weakify(self)
[RCTJavaScriptLoader loadBundleAtURL:fileUrl onProgress:nil onComplete:^(NSError *error, RCTSource *source) {
@strongify(self)
if (!error && source.data) {
// JavaScript 代码加载成功,并且成功获取到源代码 source.data,则执行这些代码
dispatch_async(dispatch_get_main_queue(), ^{
[self.bridge.batchedBridge executeSourceCode:source.data sync:YES];
[self.loadedBundle addObject:bundleName];
[[NSNotificationCenter defaultCenter] postNotificationName:ReactNativeDidExecutedNotification object:nil bundle:@{@"name": bundleName}];
// 如果这个包加载完了就不需要了,可以进行移除
// [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
});
} else {
// JavaScript 代码加载失败
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:ReactNativeDidFailToLoad object:nil bundle:@{@"name": bundleName}];
[self loadBundle];
});
}
}];
}
Приведенный выше код является основным кодом загрузки пакета React Native Давайте посмотрим на процесс загрузки кода React Native:
- Во-первых, инициализируйте весь React Native при запуске приложения или в любое другое время, которое вам нужно;
- Инициализация React Native основана на RCTBridge, который является ядром всей загрузки и выполнения React Native (представлен в предыдущей статье);
- выполнить
sourceURLForBridge
Метод возвращает путь common.bundle каталога запуска приложения, скопированного из песочницы; - Создание экземпляра RCTBridge
Таким образом, иСодержимое, связанное с основным пакетом, загружается.
После загрузки основного пакета он будет запущенRCTJavaScriptDidLoadNotification
событие, мы можем судить, какой пакет в настоящее время загружен в функции обработчика этого события.Когда загружается common.bundle, бизнес-пакет в очереди может быть загружен.
// BundleLoader.m
- (void)handleJSDidLoadNotification:(NSNotification *)notification {
NSString *bundleName = self.currentLoadingBundle;
if ([bundleName isEqualToString:COMMON_BUNDLE]) {
[self loadBundle];
}
}
В представлении, в котором необходимо использовать React Native, вы можете прослушать уведомление о событии, выдаваемое после завершения выполнения приведенного выше кода JavaScript:ReactNativeDidExecutedNotification
. После этого смонтируйте RCTRootView в указанное представление и отобразите его.
Так как мы выполнили сжатие zip для уменьшения размера при загрузке пакета, а затем выполнили кодировку base64, нам нужно сначала восстановить полученный код пакета:
// BundleLoader.m
- (void)extractBundle:(NSString *bundle) {
// 还原 bundle
NSData *decodedBundle = [[NSData alloc] initWithBase64EncodedString:bundle options:0];
// 将 zip 保存到指定路径
[[NSFileManager defaultManager] createFileAtPath:zipPath contents:decodedBundle attributes:nil];
// 将文件解压缩
[zipArchive UnzipOpenFile:zipPath];
[zipArchive UnzipFileTo:bundleDir overWrite:YES];
[zipArchive UnzipCloseFile];
// 然后将包推到待加载的队列当中,进行执行
}
Мониторинг в бизнес-кодеReactNativeDidExecutedNotification
Чтобы смонтировать React Native:
// charts.m
- (void)addObservers {
WeakifySelf
[[NSNotificationCenter defaultCenter] addObserver:self name:ReactNativeDidExecutedNotification dispatchQueue:dispatch_get_main_queue() block:^(NSNotification *notification) {
StrongifySelf
NSString *loadedBundle = notification.bundle[@"name"];
if ([loadedBundle isEqualToString:self.bundle]) {
[self _initRCTRootView];
}
}];
}
- (void)_initRCTRootView {
// 进行 React Native 容器的初始化,并且进行挂载
self.rctRootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
[self.contentView addSubview:self.rctRootView];
}
На этом установка компонента React Native завершена.
Общий процесс упаковки, распределения и погрузки выглядит следующим образом:
В заключение
В настоящее время бизнес стабильно работает онлайн уже более месяца, а в будущем были добавлены некоторые новые функции и новые типы бизнес-ячеек, позволяющие распространять бэкенд напрямую, вдали от клиентской версии разработки.
На самом деле, будь то поток новостей или другие сценарии, это решение может сделать интерфейс Native «частично» динамическим, и вы сможете наслаждаться хорошим родным интерфейсом без необходимости динамического (хотя кажется, что интерфейс React Native тоже хорош). ).
Потому что React Native все еще имеет много проблем, таких как производительность длинных списков, чрезмерное потребление памяти и так далее. Эти проблемы всегда были ахиллесовой пятой React Native, и я надеюсь, что рефакторинг React Native в Facebook поможет снизить стоимость использования React Native~