Weex
предыдущий постОн сказал, что гибридное приложение простое, эта статьяWeex
В качестве примера для анализа гибридного приложения эта статья не является введением.Weex
Как он используется, если вы хотите знать, как его использовать, лучше знатьErosРешение, в основном хочу проанализироватьWeex
Принцип, понялWeex
рабочий механизм.
Почему выбирают Векс
Прежде всего, я хочу поговорить о том, почему мы выбираемWeex
. Предыдущая статьяКонечная параWeex
а такжеReactNative
Был проведен краткий анализ в контексте нашего технического выбораRN
Это лучшее решение в любом случае, и можно провести больше сравнений.Сравнение weex и ReactNativeПослушайте, при выборе технологий я все время спрашиваю, почему? В конце концов, относительно хороший выбор был получен, вероятно, из следующих аспектов.
Плюсы и минусы Weex
Прежде всего, вы должны посмотреть на преимущества и недостатки.Преимущества используются, чтобы судить о том, подходит ли ваша сцена для этой технологии.Недостатки заключаются в том, будет ли ваша сцена ограничена, и есть ли способ решить или обойти это.
преимущество:
- js может писать бизнес, кроссплатформенное, горячее обновление
- Weex может использовать структуру Vue, близкую к нашему стеку технологий.
- Weex легче, чем RN, может быть передан субподрядчикам, а один экземпляр на страницу имеет лучшую производительность.
- Weex решает некоторые проблемы, уже существующие в Rn, и развиваются на RN.
- У него хорошая расширяемость, и лучше расширять новый компонент и модуль.
недостаток:
- Документация неполная, информации мало, коммьюнити почти нет, задачи накапливаются, а способ фоновых задач изменен на JIRA, что многие разработчики не понимают
- Есть много ошибок, нестабильность и много обновлений, похожих на обрывы.
- Компонента и модуля недостаточно для переопределения функциональности
По сути, резюмируя, это отечественный продукт с поздним стартом, и преимущества не повторятся. В основном это зависит от того, будут ли недостатки ограничивать бизнес-сценарий и есть ли соответствующее решение.
Соответствующей информации относительно мало, поэтому вы можете увидеть исходный код.Если у вас есть исходный код, вы сможете продолжить, вы можете увидеть исходный код, и проблема не решена, в основном, чтобы найти его.Weex
Предоставляет множество хороших свойств и методов, которые не описаны в документации.
Проект стартовал относительно поздно.bug
Их много, и обновление тоже обрывистое.Мы, наконец, приняли метод интеграции исходного кода и обнаружили, что естьbug
Просто восстановите исходный код и дайте официальное упоминаниеPR
, наша команда упомянула многоPR
Он также был официально принят, в основном потому, что это пустая трата времени на обновление каждой версии.diff
Если чиновник был зафиксирован, чтобы удалить наш собственный патч. Это действительно пустая трата времени, ноRN
Если вы хотите расширить себя, вам нужно пройти через эту боль.
который предоставилComponent
а такжеModule
Этого недостаточно для удовлетворения потребностей бизнеса.Конечно, официальная также предоставляет способ расширения соответствующего плагина.Быстрее попробовать расширить несколько плагинов с помощью нативных знаний, и мы решили использовать официальную как можно меньше с самого начала.Module
, настолько далеко, насколько возможноModule
Все продлеваются самим нашим клиентом, с одной стороны, на них не будет распространяться официальнаяModule bug
Или влияние, когда он не имеет обратной совместимости, с другой стороны, при расширении собственногоModule
В то же время мы можем понять его механизм, а также позволить расширенноеModule
Все в соответствии с нашим бизнесом.
Стоимость доступа и стоимость обучения
Наш основной технологический стек вращается вокругVue
Он был установлен, и я сам сделал унифицированные леса, и он был адаптирован для серверной системы, паблик-аккаунта WeChat, апплета, машины самообслуживания и других многоцелевых проектов.APP
решение, если вы можете использоватьVue
Чтобы получить доступ на основе фонда, он улучшит всю технологическую цепочку переднего плана, сотрудничает сVue
Стоимость переключения между проектами синтаксической инфраструктуры будет очень низкой, эффективность развития будет высокой.
на основеVue
Стек технологий позволяет нашим бизнес-студентам быстро адаптироваться, разделять компоненты,widget
плагин,mixins
Эти родственные применения можно использовать напрямую, и единственное, чему осталось научиться, этоWeex
изComponent
а такжеModule
использовать иcss
Поддержка, мы также напрямую поддерживаем после подключения лесовsass/less/styule
, весь процесс позволяет новым студентам приступить к работе, и требуется полдня, чтобы увидеть, что они могут построить полныйdemo
Страницы, начните скорее. В общем, цена для нас большой плюс.
Опыт разработки и пользовательский опыт
Вышеприведенная картинка - это то, что мы наконец дали, улучшивErosПлан развития представляет собой модель развития, основу которой составляют строительные леса.
Опыт разработки основывается наVue
метод, все виды грамматик были сглажены на слое лесов, разработка в основном такая же, как и в предыдущем режиме разработки, способ разработки и отладкиWeex
Обеспечивает независимую поддержку модулей.Поняв принцип, мы быстро реализовали функцию сохранения и обновления, плюс себяWeex debug
который предоставилdebug
страница,js
Его также можно отлаживать, и клиент также поддерживает вывод журнала.Общий опыт разработки относительно гладкий, но он не так хорош, какweb
Разработка настолько естественна, но мы значительно улучшили опыт разработки, изменив структуру, поддержав функцию горячего обновления для клиента и предоставив некоторые собственные инструменты.
Общее сравнение производительности с точки зрения пользовательского опытаRN
С улучшением стояRN
На плече клиента это действительно решило много проблем с производительностью.Для первого белого экрана мы использовали встроенный пакет, и с нашим механизмом горячего обновления мы можем гарантировать, что при открытии клиента должен быть соответствующий контент, нет необходимости загружать дополнительные ресурсы, а также гарантируется время белого экрана. При переключении страниц мы используем многостраничный подход для достиженияWeex
, с нашим собственным расширенным механизмом маршрутизации, каждая страница представляет собой отдельныйWeex
Например, поэтому производительность и эффективность рендеринга каждой страницы в отдельности лучше, и мы занимаемся предварительной загрузкой решений, хотя эффект повышения производительности не очевиден, но каждый маленький шаг может уменьшить время переключения между страницами белого экрана.
Мониторинг производительности и аварийное восстановление
Weex
Мы сами провели много мониторинга производительности. Нам нужно только подключить данные о производительности к нашей системе мониторинга, чтобы отобразить соответствующие данные о производительности. В настоящее время это реализовано с помощью эффекта мониторинга.Weex
Приверженность производительности.
Обработка аварийного восстановления используется для обработкиjsBundle
В случае отказа доступа,Weex
Если у вас есть решение для аварийного восстановления, вам нужно, чтобы разработчик выполнил преобразование для обработки перехода на более раннюю версию.При отображении страницы клиент загрузит соответствующий, если клиент загружаетjs bundle
Не удалось включитьwebView
посетить, показатьHTML
Тем не менее, опыт будет очень плохим.Мы используем встроенный механизм пакет+горячее обновление, чтобы гарантировать, что у нас не будет проблем с ошибкой разбора пакета или недоступностью.Если есть проблема с выпущенным пакетом, его можно выпустить срочно , и пользователи получат его немедленно. Чтобы обновить и сообщить пользователю, следует ли обновлять немедленно в соответствии с конфигурацией, если вы хотите сделать лучше, вы можете сохранить стабильную версию пакета в мобильном телефоне пользователя. В результате, пакет относительно большой.Если требуется стабильная обработка аварийного восстановления, это можно рассмотреть.
После завершения программы исследований и простыхdemo
Тест, мы начинаем приземляться, околоWeex
Мы также проделали большую работу по строительству окружающей среды, например, преобразовали существующие строительные леса в опорные.Weex
разработка, как построить механизм горячего обновления, какая поддержка нужна в нижней части клиента, как сделать расширения для отвязки от исходного кода и т.д.
Вернемся к делу, поговорим оWeex
общая архитектура.
Общая архитектура Weex
Из приведенного выше рисунка видно, чтоWeex
Общий принцип работы, вот общее введение в процесс, а каждый шаг будет подробно описан позже.
Weex
предоставлять разныеframework
разбор, вы можете использовать.we
а также.vue
бизнес по написанию файлов, а затем передатьwebpack
Скомпилируйте и сгенерируйтеjs bundle
, в основном используется в процессе компиляцииweex
Связанныйloader
,Erosк упакованномуjs bundle
генерируетсяzip
package, а также генерирует логику для дифференциальных пакетов. Какой бы файл ни был сгенерирован, в итоге он будетjs bundle
развернуть на сервере илиCDN
на узле.
При запуске клиента обнаруживается, что введениеWeex sdk
, сначала инициализирует среду и некоторый мониторинг, а затем запускает локальныйmain.js
которыйjs framework
,js framework
инициализирует некоторую среду, когдаjs framework
После того, как клиент и клиент готовы, начните ждать, пока клиент отобразит страницу.
Когда страница должна быть отображена, клиент инициализируетWeex
экземпляр, то естьWXSDKInstance
,Weex
Экземпляр загрузит соответствующийjs bundle
файл, весьjs bundle
Файл передается в виде строки вjs framework
, а также передать некоторые параметры среды.js framework
начать сJavaScript Core
выполнить вjs bundle
,Будуjs bundle
выполнить перевод наvirtual DOM
, готовый к двойной привязке данных, и в то же времяvDOM
Выполните глубокий обход и проанализируйте вvNode
, в соответствии с инструкциями рендеринга один за другим черезjs Core
передано клиенту.
js framework
передачаWeex SDK
готов к инициализацииcallNative
,addElement
и другие методы, передайте инструкцию нативной и найдите соответствующую инструкциюWeex Component
Выполните рендеринг и рисование, показывая по одному для каждого визуализируемого компонента,Weex
Узким местом в производительности является процесс передачи компонентов один за другим, вызовmodule
Это немного сложнее, что будет подробно объяснено позже, и привязка событий также будет подробно объяснена позже. В этот момент отображается страница.
Weex SDK
Выше мы проанализировали грубыйWeex
Архитектура также кратко представляет запущенный процесс.ErosИсходный код, чтобы подробно увидеть, как выполняется каждый шаг,Erosосновывается наWeex
Вторая инкапсуляция клиента, первая часть работы клиента — инициализацияWeex
изsdk
.
инициализацияWeex sdk
В основном выполните следующие четыре вещи:
- Ключевые узлы записывают информацию мониторинга
- Инициализируйте среду SDK, загрузите и запустите фреймворк js.
- Регистрация компонентов, модулей, обработчиков
- Если эмулятор инициализирован в среде разработки, попробуйте подключиться к локальному серверу
ErosсуществуетWeex
Многие расширения были сделаны на основеWeex
Основной процесс описан выше,ErosОсновной поток кода выглядит следующим образом.
+ (void)configDefaultData
{
/* 启动网络变化监控 */
AFNetworkReachabilityManager *reachability = [AFNetworkReachabilityManager sharedManager];
[reachability startMonitoring];
/** 初始化Weex */
[BMConfigManager initWeexSDK];
BMPlatformModel *platformInfo = TK_PlatformInfo();
/** 设置sdimage减小内存占用 */
[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
[[SDImageCache sharedImageCache] setShouldCacheImagesInMemory:NO];
/** 设置统一请求url */
[[YTKNetworkConfig sharedConfig] setBaseUrl:platformInfo.url.request];
[[YTKNetworkConfig sharedConfig] setCdnUrl:platformInfo.url.image];
/** 应用最新js资源文件 */
[[BMResourceManager sharedInstance] compareVersion];
/** 初始化数据库 */
[[BMDB DB] configDB];
/** 设置 HUD */
[BMConfigManager configProgressHUD];
/* 监听截屏事件 */
// [[BMScreenshotEventManager shareInstance] monitorScreenshotEvent];
}
Инициализировать записи мониторинга
Weex
Одним из преимуществ является то, что у него есть собственный мониторинг, и он будет фиксировать простые показатели производительности, такие как инициализацияSDK
время, запросить успех и неудачу,js
Сообщается об ошибках, эта информация будет автоматически записана вWXMonitor
середина.
Weex
Ошибки делятся на две категории, однаglobal
, классinstance
. существуетiOS
серединаWXSDKInstance
Перед инициализацией все глобальныеglobal
операции будут размещены наWXMonitor
изglobalPerformanceDict
середина. когдаWXSDKInstance
После инициализации WXPerformanceTag中
instance以下的所有操作都会放在
instance.performanceDict`.
global
мониторинг
- SDKINITTIME: мониторинг инициализации SDK
- SDKINITINVOKETIME: мониторинг вызовов инициализации SDK
- JSLIBINITTIME: мониторинг инициализации ресурсов js
instance
монитор
- NETWORKTIME: мониторинг сетевых запросов
- COMMUNICATETIME: Интерактивный мониторинг событий
- FIRSETSCREENJSFEXECUTETIME: мониторинг загрузки первого экрана js
- SCREENRENDERTIME: мониторинг времени рендеринга первого экрана
- TOTALTIME: общее время рендеринга
- JSTEMPLATESIZE: размер шаблона js
Если вы хотите получить доступ к собственной системе мониторинга, прочтитеWXMonitor
Связанный код, вы можете использовать некоторыеAOP
Режим записи ошибок в собственный мониторинг, эта часть кода не является предметом эксплуатации, и заинтересованные студенты могут изучить ее самостоятельно.
Инициализировать среду SDK
Это самая важная работа по инициализации через [BMConfigManager initWeexSDK];ErosТакже в это время вводится расширение. Размещаем наше расширение вregisterBmComponents
,registerBmModules
,registerBmHandlers
Затем эти три метода вводятся равномерно, избегаяWeex
Сам код слишком сильно связан.
+ (void)initWeexSDK
{
[WXSDKEngine initSDKEnvironment];
[BMConfigManager registerBmHandlers];
[BMConfigManager registerBmComponents];
[BMConfigManager registerBmModules];
#ifdef DEBUG
[WXDebugTool setDebug:YES];
[WXLog setLogLevel:WeexLogLevelLog];
[[BMDebugManager shareInstance] show];
// [[ATManager shareInstance] show];
#else
[WXDebugTool setDebug:NO];
[WXLog setLogLevel:WeexLogLevelError];
#endif
}
Ниже приведены некоторые из наших расширений. Подробные сведения о расширениях можно найти в нашем исходном коде. Чтобы отделиться от официального расширения интеграции с исходным кодом, мы установили время внедрения наWeex initSDKEnvironment
Позже.
// 扩展 Component
+ (void)registerBmComponents
{
NSDictionary *components = @{
@"bmmask": NSStringFromClass([BMMaskComponent class]),
@"bmpop": NSStringFromClass([BMPopupComponent class])
...
};
for (NSString *componentName in components) {
[WXSDKEngine registerComponent:componentName withClass:NSClassFromString([components valueForKey:componentName])];
}
}
// 扩展 Moudles
+ (void)registerBmModules
{
NSDictionary *modules = @{
@"bmRouter" : NSStringFromClass([BMRouterModule class]),
@"bmAxios": NSStringFromClass([BMAxiosNetworkModule class])
...
};
for (NSString *moduleName in modules.allKeys) {
[WXSDKEngine registerModule:moduleName withClass:NSClassFromString([modules valueForKey:moduleName])];
}
}
// 扩展 Handlers
+ (void)registerBmHandlers
{
[WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
[WXSDKEngine registerHandler:[WXBMNetworkDefaultlpml new] withProtocol:@protocol(WXResourceRequestHandler)];
...
}
инициализацияSDK
заключается в выполненииWXSDKEngine
Содержимое этого файла наиболее важно для регистрации текущегоComponents
,Modules
,handlers
.
+ (void)registerDefaults
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _registerDefaultComponents];
[self _registerDefaultModules];
[self _registerDefaultHandlers];
});
}
Регистрация компонентов
Студенты Xiaobai могут быть более озадачены, почемуWeex
поддерживает только некоторые определенные теги, а неHTML
Все теги в файле поддерживаются. Прежде всего, разбор тегов должен иметь соответствующие отношения с собственными, и теги этих соответствующих отношений могут быть поддерживаться. Откуда эта корреспонденция? Во-первых, Weex будет инициализировать некоторыеComponents
, первым сказатьWeex SDK
Какие теги я поддерживаю, в том числеWeex
некоторые из меток предоставлены, и мы проходимWeex Component
Метод расширения расширения вне метки.
Давайте посмотримComponents
Как зарегистрироваться, находится в вышеуказанном методе_registerDefaultComponents
, ниже приведена часть кода для этих методов
// WXSDKEngine.m
+ (void)_registerDefaultComponents
{
[self registerComponent:@"container" withClass:NSClassFromString(@"WXDivComponent") withProperties:nil];
[self registerComponent:@"cell-slot" withClass:NSClassFromString(@"WXCellSlotComponent") withProperties: @{@"append":@"tree", @"isTemplate":@YES}];
...
}
Между двумя описанными выше методами есть некоторые различия.withProperties
Параметры разные, если он с@{@"append":@"tree"}
, сначала визуализировать дочерние узлы;isTemplate
Являетсяboolean
значение, еслиtrue
, ему будут переданы все подшаблоны под этим тегом. Роль этих двух параметров будет подробно проанализирована позже.
в инициализацииWeexSDK
когда,Weex
позвоню_registerDefaultComponents
метод будетWeex
Официально расширенные компоненты зарегистрированы, продолжайте смотреть наregisterComponent:withClass:withProperties:
метод
+ (void)registerComponent:(NSString *)name withClass:(Class)clazz withProperties:(NSDictionary *)properties
{
if (!name || !clazz) {
return;
}
WXAssert(name && clazz, @"Fail to register the component, please check if the parameters are correct !");
// 注册组件的方法
[WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
// 遍历出组件的异步方法
NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
dict[@"type"] = name;
// 将组件放到 bridge 中,准备注册到 js framework 中。
if (properties) {
NSMutableDictionary *props = [properties mutableCopy];
if ([dict[@"methods"] count]) {
[props addEntriesFromDictionary:dict];
}
[[WXSDKManager bridgeMgr] registerComponents:@[props]];
} else {
[[WXSDKManager bridgeMgr] registerComponents:@[dict]];
}
}
Сначала посмотрите на параметры,name
зарегистрироваться вjsfm
серединаComponent
имя (т.е. имя тега),clazz
дляComponent
соответствующий класс,properties
для некоторых расширенных атрибутов;
Вызывается снова в этом методеWXComponentFactory
МетодыregisterComponent:name withClass:clazz withPros:properties
зарегистрироватьсяComponent
,WXComponentFactory
это синглтон, отвечающий за синтаксический анализComponent
метод и сохраняет все зарегистрированныеComponent
соответствующий метод; продолжайтеWXComponentFactory
посмотриregisterComponent:name withClass:clazz withPros:properties
Реализация метода:
// 类
- (void)registerComponent:(NSString *)name withClass:(Class)clazz withPros:(NSDictionary *)pros
{
WXAssert(name && clazz, @"name or clazz must not be nil for registering component.");
WXComponentConfig *config = nil;
[_configLock lock];
config = [_componentConfigs objectForKey:name];
if(config){
WXLogInfo(@"Overrider component name:%@ class:%@, to name:%@ class:%@",
config.name, config.class, name, clazz);
}
// 实例 WXComponentConfig 并保存到 _componentConfigs 中
config = [[WXComponentConfig alloc] initWithName:name class:NSStringFromClass(clazz) pros:pros];
[_componentConfigs setValue:config forKey:name];
[config registerMethods];
[_configLock unlock];
}
Этот метод создаст экземплярWXComponentConfig
объектconfig
, каждыйComponent
будет иметь к этому отношениеWXComponentConfig
например, тогдаconfig
пример какvalue
,key
дляComponent
изname
Сохранить_componentConfigs
середина(_componentConfigs
это словарь),config
сохранено вComponent
Все методы выставлены на js, продолжаем смотретьWXComponentConfig
изregisterMethods
метод:
- (void)registerMethods
{
// 获取类
Class currentClass = NSClassFromString(_clazz);
if (!currentClass) {
WXLogWarning(@"The module class [%@] doesn't exit!", _clazz);
return;
}
while (currentClass != [NSObject class]) {
unsigned int methodCount = 0;
// 获取方法列表
Method *methodList = class_copyMethodList(object_getClass(currentClass), &methodCount);
// 遍历方法列表
for (unsigned int i = 0; i < methodCount; i++) {
// 获取方法名称
NSString *selStr = [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding];
BOOL isSyncMethod = NO;
// 同步方法
if ([selStr hasPrefix:@"wx_export_method_sync_"]) {
isSyncMethod = YES;
// 异步方法
} else if ([selStr hasPrefix:@"wx_export_method_"]) {
isSyncMethod = NO;
// 其他未暴露方法
} else {
continue;
}
NSString *name = nil, *method = nil;
SEL selector = NSSelectorFromString(selStr);
// 获取方法实现
if ([currentClass respondsToSelector:selector]) {
method = ((NSString* (*)(id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);
}
if (method.length <= 0) {
WXLogWarning(@"The module class [%@] doesn't has any method!", _clazz);
continue;
}
NSRange range = [method rangeOfString:@":"];
if (range.location != NSNotFound) {
name = [method substringToIndex:range.location];
} else {
name = method;
}
// 将方法保持到对应的字典中
NSMutableDictionary *methods = isSyncMethod ? _syncMethods : _asyncMethods;
[methods setObject:method forKey:name];
}
free(methodList);
currentClass = class_getSuperclass(currentClass);
}
}
WXComponentConfig
Два словарь_asyncMethods
а также_syncMethods
соответственно сохранить асинхронный метод и синхронный метод;registerMethods
метод заключается в обходеComponent
класс подвергается воздействиюjsfm
метод; затем вернемся кWXSDKEngine
изregisterComponent:withClass:withProperties:
метод.
+ (void)registerComponent:(NSString *)name withClass:(Class)clazz withProperties:(NSDictionary *)properties
{
if (!name || !clazz) {
return;
}
WXAssert(name && clazz, @"Fail to register the component, please check if the parameters are correct !");
[WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
// ↑ 到这里 Component 的方法已经解析完毕,并保持到了 WXComponentFactory 中
// 获取 Component 的异步方法
NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
dict[@"type"] = name;
// 最后将 Component 注册到 jsfm 中
if (properties) {
NSMutableDictionary *props = [properties mutableCopy];
if ([dict[@"methods"] count]) {
[props addEntriesFromDictionary:dict];
}
[[WXSDKManager bridgeMgr] registerComponents:@[props]];
} else {
[[WXSDKManager bridgeMgr] registerComponents:@[dict]];
}
}
Component
После разбора он вызоветWXSDKManager
серединаbridgeMgr
изregisterComponents:
метод;WXSDKManager
держи одинWXBridgeManager
,этоWXBridgeManager
Другое свойствоWXBridgeContext
,WXBridgeContext
держи другойjs Bridge
ссылка, это то, что мы часто говоримBridge
. Ниже приведен соответствующий основной код иbridge
Отношение между. (СейчасWXDebugLoggerBridge
больше не существует)
// WXSDKManager
@interface WXSDKManager ()
@property (nonatomic, strong) WXBridgeManager *bridgeMgr;
@property (nonatomic, strong) WXThreadSafeMutableDictionary *instanceDict;
@end
// WXBridgeManager
@interface WXBridgeManager ()
@property (nonatomic, strong) WXBridgeContext *bridgeCtx;
@property (nonatomic, assign) BOOL stopRunning;
@property (nonatomic, strong) NSMutableArray *instanceIdStack;
@end
// WXBridgeContext
@interface WXBridgeContext ()
@property (nonatomic, strong) id<WXBridgeProtocol> jsBridge;
@property (nonatomic, strong) id<WXBridgeProtocol> devToolSocketBridge;
@property (nonatomic, assign) BOOL debugJS;
//store the methods which will be executed from native to js
@property (nonatomic, strong) NSMutableDictionary *sendQueue;
//the instance stack
@property (nonatomic, strong) WXThreadSafeMutableArray *insStack;
//identify if the JSFramework has been loaded
@property (nonatomic) BOOL frameworkLoadFinished;
//store some methods temporarily before JSFramework is loaded
@property (nonatomic, strong) NSMutableArray *methodQueue;
// store service
@property (nonatomic, strong) NSMutableArray *jsServiceQueue;
@end
Атрибуты трех классов кратко представлены выше.Из атрибутов мы можем увидеть общие функции.Вызывающая связь между ними также относительно ясна.При вызовеWXBridgeManager
передачаregisterComponents
метод, а затем вызватьWXBridgeContext
изregisterComponents
способ регистрации компонента.
// WXBridgeManager
- (void)registerComponents:(NSArray *)components
{
if (!components) return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx registerComponents:components];
});
}
// WXBridgeContext
- (void)registerComponents:(NSArray *)components
{
WXAssertBridgeThread();
if(!components) return;
[self callJSMethod:@"registerComponents" args:@[components]];
}
WXPerformBlockOnBridgeThread
Этот поток являетсяjsThread
, это глобально уникальный поток, но в настоящее время, если вы напрямую вызываетеcallJSMethod
, обязательно потерпит неудачу, потому что на этот разjs framework
Он может быть еще не закончен.
если в это времяjs framework
Если выполнение не было завершено, регистрируемые методы будут помещены в_methodQueue
кешировать это,js framework
После завершения загрузки он снова пройдет по этому пути._methodQueue
, который выполняет все кешированные методы.
- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{
// 如果 js frameworkLoadFinished 就立即注入 Component
if (self.frameworkLoadFinished) {
[self.jsBridge callJSMethod:method args:args];
} else {
// 如果没有执行完,就将方法放到 _methodQueue 队列中
[_methodQueue addObject:@{@"method":method, @"args":args}];
}
}
- (void)callJSMethod:(NSString *)method args:(NSArray *)args onContext:(JSContext*)context completion:(void (^)(JSValue * value))complection
{
NSMutableArray *newArg = nil;
if (!context) {
if ([self.jsBridge isKindOfClass:[WXJSCoreBridge class]]) {
context = [(NSObject*)_jsBridge valueForKey:@"jsContext"];
}
}
if (self.frameworkLoadFinished) {
newArg = [args mutableCopy];
if ([newArg containsObject:complection]) {
[newArg removeObject:complection];
}
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
JSValue *value = [[context globalObject] invokeMethod:method withArguments:args];
if (complection) {
complection(value);
}
} else {
newArg = [args mutableCopy];
if (complection) {
[newArg addObject:complection];
}
[_methodQueue addObject:@{@"method":method, @"args":[newArg copy]}];
}
}
// 当 js framework 执行完毕之后会回来调用 WXJSCoreBridge 这个方法
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}
Далее стоит позвонитьjs framework
изregisterComponents
Зарегистрируйте все соответствующиеComponents
, эта часть контента будет подробно разобрана ниже, и будет выполняться в порядке выполненияModules
Регистрация.
Регистрация модулей
вход все ещеWXSDKEngine
,передача_registerDefaultModules
, читать всеModules
зарегистрироваться, зарегистрироваться, позвонитьregisterModule
метод, то же зарегистрирует модуль, получитWXModuleFactory
, затем аналогичным образом перебирает все синхронные и асинхронные методы и, наконец, вызываетWXBridgeManager
, зарегистрируйте модуль вWXBridgeManager
середина.
+ (void)_registerDefaultModules
{
[self registerModule:@"dom" withClass:NSClassFromString(@"WXDomModule")];
[self registerModule:@"locale" withClass:NSClassFromString(@"WXLocaleModule")];
...
}
+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
if (!clazz || !name) {
return;
}
NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
[[WXSDKManager bridgeMgr] registerModules:dict];
}
Модуль регистрации также черезWXModuleFactory
, поставить всеmodule
пройти через_registerModule
генерироватьModuleMap
. Регистрация модулей не позволяет использовать модули с одинаковыми именами. Будуname
дляkey
,value
дляWXModuleConfig
депозит_moduleMap
Словарь,WXModuleConfig
спасModule
Для связанных атрибутов, если они имеют одинаковое имя, те, которые были зарегистрированы позже, перезапишут те, которые были зарегистрированы первыми.
@interface WXModuleFactory ()
@property (nonatomic, strong) NSMutableDictionary *moduleMap;
@property (nonatomic, strong) NSLock *moduleLock;
@end
- (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
[_moduleLock lock];
//allow to register module with the same name;
WXModuleConfig *config = [[WXModuleConfig alloc] init];
config.name = name;
config.clazz = NSStringFromClass(clazz);
[config registerMethods];
[_moduleMap setValue:config forKey:name];
[_moduleLock unlock];
return name;
}
когда всеModule
После создания экземпляра пройдите все методы, включая синхронные и асинхронные методы, можно увидеть следующие методы, перед прохождением методов уже есть некоторые методы в_defaultModuleMethod
В объекте здесь как минимум два методаaddEventListener
а такжеremoveAllEventListeners
, Там, где он возвращается из метода, предусмотрены два вышеуказанных метода.
- (NSMutableDictionary *)_moduleMethodMapsWithName:(NSString *)name
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSMutableArray *methods = [self _defaultModuleMethod];
[_moduleLock lock];
[dict setValue:methods forKey:name];
WXModuleConfig *config = _moduleMap[name];
void (^mBlock)(id, id, BOOL *) = ^(id mKey, id mObj, BOOL * mStop) {
[methods addObject:mKey];
};
[config.syncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[config.asyncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[_moduleLock unlock];
return dict;
}
- (NSMutableArray*)_defaultModuleMethod
{
return [NSMutableArray arrayWithObjects:@"addEventListener",@"removeAllEventListeners", nil];
}
Далее стоит позвонитьjs framework
инъекционный метод иregisterComponent
Почти, это также будет связано с проблемой потоков, и это также пройдет вышеописанное.WXSDKManager -> WXBridgeManager -> WXBridgeContext
. Наконец, вызовите следующий метод. последний звонокregisterModules
все клиентыModule
вводить вjs framework
середина,js framework
Также будет некоторая упаковка, которая будет использоваться в бизнесе.weex.registerModule
для вызова соответствующего метода.
- (void)registerModules:(NSDictionary *)modules
{
WXAssertBridgeThread();
if(!modules) return;
[self callJSMethod:@"registerModules" args:@[modules]];
}
инъекция обработчика
Component
а такжеModule
Всем понятнее использовать его часто, ноhandler
Что тогда?Weex
Некоторые методы протокола указаны, и методы в протоколе будут вызываться в определенное время.Вы можете реализовать класс для следования этим протоколам, реализовать методы в протоколе, а затем передатьhandler
Зарегистрированный путь кweex
, тогда, когда вам нужно будет вызвать эти методы протокола, он будет вызываться в реализуемом вами классе. НапримерWXResourceRequestHandler
:
@protocol WXResourceRequestHandler <NSObject>
// Send a resource request with a delegate
- (void)sendRequest:(WXResourceRequest *)request withDelegate:(id<WXResourceRequestDelegate>)delegate;
@optional
// Cancel the ongoing request
- (void)cancelRequest:(WXResourceRequest *)request;
@end
WXResourceRequestHandler
Указаны два метода, один — метод запроса для загрузки ресурсов, а другой — метод, который нужно запросить, а затем посмотретьWXResourceRequestHandlerDefaultImpl
Добрый:
//
// WXResourceRequestHandlerDefaultImpl.m
//
#pragma mark - WXResourceRequestHandler
- (void)sendRequest:(WXResourceRequest *)request withDelegate:(id<WXResourceRequestDelegate>)delegate
{
if (!_session) {
NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
if ([WXAppConfiguration customizeProtocolClasses].count > 0) {
NSArray *defaultProtocols = urlSessionConfig.protocolClasses;
urlSessionConfig.protocolClasses = [[WXAppConfiguration customizeProtocolClasses] arrayByAddingObjectsFromArray:defaultProtocols];
}
_session = [NSURLSession sessionWithConfiguration:urlSessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
_delegates = [WXThreadSafeMutableDictionary new];
}
NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
request.taskIdentifier = task;
[_delegates setObject:delegate forKey:task];
[task resume];
}
- (void)cancelRequest:(WXResourceRequest *)request
{
if ([request.taskIdentifier isKindOfClass:[NSURLSessionTask class]]) {
NSURLSessionTask *task = (NSURLSessionTask *)request.taskIdentifier;
[task cancel];
[_delegates removeObjectForKey:task];
}
}
WXResourceRequestHandlerDefaultImpl
последовалWXResourceRequestHandler
протокол и реализуйте метод протокола, затем зарегистрируйтесьHandler
, если запрос ресурса отправлен, он будет отправлен наWXResourceRequestHandlerDefaultImpl
в реализации.
Инициализация клиентаSDK
Методы, связанные с регистрацией, завершены.Выше было упомянуто, что последняя регистрация заключается в регистрации наjs
среду, передайте методjs framework
позвонить, ноjs framework
Он еще не вызывался, и далее следует загрузить этот файл.
Загрузить и запускать JS Framework
в официальномGitHub
серединаruntimeЕсть кучиjs
, эта кучаjs
в конечном итоге будет упакован вnative-bundle-main.js
файл, мы будем называть егоmain.js
, этот абзацjs
это то, что мы всегда говоримjs framework
,существуетSDK
Во время инициализации весь код передается в виде строки вWXSDKManager
и положиJavaScript Core
выполнить. Давайте сначала посмотрим на этоruntime
Под какими файлами
|-- api:冻结原型链,提供给原生调用的方法,比如 registerModules
|-- bridge:和客户端相关的接口调用,调用客户端的时候有一个任务调度
|-- entries:客户端执行 js framework 的入口文件,WXSDKEngine 调用的方法
|-- frameworks:核心文件,初始化 js bundle 实例,对实例进行管理,dom 调度转换等
|-- services:js service 存放,broadcast 调度转换等
|-- shared:polyfill 和 console 这些差异性的方法
|-- vdom:将 VDOM 转化成客户端能渲染的指令
Выглядит и мы упоминали в предыдущей статьеjs bridge
аналогичен по функциям, но почемуWeex
У этого слоя так много функций, в первую очередьWeex
Он должен быть совместим с тремя терминалами, поэтомуiOS
,android
,web
Различия должны быть сглажены, и у них могут быть разные способы и способы принятия инструкций.Например, дизайн клиентаcreateBody
а такжеaddElement
,а такжеweb
даcreateElement
,appendChild
Ждать.
В дополнение к различиям в инструкциях существуют различия в бизнес-языках верхнего уровня, таких какWeex
служба поддержкиVue
а такжеRax
, и может даже поддерживатьReact
, лишь бы он соответствовалjs framework
Реализация может отображаться в разных хост-средах через разные интерфейсы. Мы можем назвать этот слойDSL
, тоже смотримjs framework
Основной код этого слоя
|-- index.js:入口文件
|-- legacy:关于 VM 相关的主要方法
| |-- api:相关 vm 定义的接口
| |-- app:管理页面间页面实例的方法
| |-- core:实现数据监听的方法
| |-- static:静态方法
| |-- util:工具类函数
| |-- vm:解析指令相关
|-- vanilla:与客户端交互的一些方法
запустить структуру
После первой регистрации трех модулей, упомянутых выше,WXSDKEngine
Продолжать вниз, или звонить вWXBridgeManager
серединаexecuteJsFramework
, затем позвоните вWXBridgeContext
изexecuteJsFramework
, а затем выполнить в дочернем потокеjs framework
.
// WXSDKEngine
[[WXSDKManager bridgeMgr] executeJsFramework:script];
// WXBridgeManager
- (void)executeJsFramework:(NSString *)script
{
if (!script) return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx executeJsFramework:script];
});
}
// WXBridgeContext
- (void)executeJsFramework:(NSString *)script
{
WXAssertBridgeThread();
WXAssertParam(script);
WX_MONITOR_PERF_START(WXPTFrameworkExecute);
// 真正的执行 js framework
[self.jsBridge executeJSFramework:script];
WX_MONITOR_PERF_END(WXPTFrameworkExecute);
if ([self.jsBridge exception]) {
NSString *exception = [[self.jsBridge exception] toString];
NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_SDK_INIT_JSFM_INIT_FAILED] %@",exception];
[WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_SDK_INIT" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_SDK_INIT] function:@"" exception:errMsg extParams:nil];
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg);
} else {
WX_MONITOR_SUCCESS(WXMTJSFramework);
//the JSFramework has been load successfully.
// 执行完 js
self.frameworkLoadFinished = YES;
// 执行缓存在 _jsServiceQueue 中的方法
[self executeAllJsService];
// 获取 js framework 版本号
JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
if (frameworkVersion && [frameworkVersion isString]) {
[WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
}
// 计算 js framework 的字节大小
if (script) {
[WXAppConfiguration setJSFrameworkLibSize:[script lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
}
//execute methods which has been stored in methodQueue temporarily.
// 开始执行之前缓存在队列缓存在 _methodQueue 的方法
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
WX_MONITOR_PERF_END(WXPTInitalize);
};
}
Суть вышеприведенного процесса выполнения заключается в том, как выполнитьjs framework
Да, действительно загружаетсяnative-bundle-main.js
Файл не должен иметь возвращаемое значение после выполнения или содержать паруjs framework
Ссылка просто помещается в память, готовая к вызову в любое время. Также будет ведение журнала до и после выполнения
// WXBridgeContext
- (void)executeJSFramework:(NSString *)frameworkScript
{
WXAssertParam(frameworkScript);
if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
[_jsContext evaluateScript:frameworkScript withSourceURL:[NSURL URLWithString:@"native-bundle-main.js"]];
}else{
[_jsContext evaluateScript:frameworkScript];
}
}
Давай уйдемjs framework
Для выполнения самого себя сначала посмотрите какую работу выполнит клиент после завершения выполнения.Перед началом загрузки она будет закэширована в_jsServiceQueue
а также_methodQueue
метод в .
// WXBridgeContext
- (void)executeAllJsService
{
for(NSDictionary *service in _jsServiceQueue) {
NSString *script = [service valueForKey:@"script"];
NSString *name = [service valueForKey:@"name"];
[self executeJsService:script withName:name];
}
[_jsServiceQueue removeAllObjects];
}
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
_methodQueue
Нетрудно понять, какие из предыдущих нативных методов регистрации кэшируются в_methodQueue
середина,_jsServiceQueue
Откуда это?js service
Это будет подробно объяснено ниже,broadcastChannel
то естьWeex
один предоставленныйjs service
,Официальный вариант использованиятакже предоставляет расширенияjs service
кстати видно чтоjs service
будет загружен только один раз,js service
Это просто набор строк, поэтому просто выполните его напрямую.
// WXSDKEngine
NSDictionary *jsSerices = [WXDebugTool jsServiceCache];
for(NSString *serviceName in jsSerices) {
NSDictionary *service = [jsSerices objectForKey:serviceName];
NSString *serviceName = [service objectForKey:@"name"];
NSString *serviceScript = [service objectForKey:@"script"];
NSDictionary *serviceOptions = [service objectForKey:@"options"];
[WXSDKEngine registerService:serviceName withScript:serviceScript withOptions:serviceOptions];
}
// WXBridgeContext
- (void)executeJsService:(NSString *)script withName:(NSString *)name
{
if(self.frameworkLoadFinished) {
WXAssert(script, @"param script required!");
[self.jsBridge executeJavascript:script];
if ([self.jsBridge exception]) {
NSString *exception = [[self.jsBridge exception] toString];
NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_INVOKE_JSSERVICE_EXECUTE] %@",exception];
[WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_INVOKE" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_INVOKE] function:@"" exception:errMsg extParams:nil];
WX_MONITOR_FAIL(WXMTJSService, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg);
} else {
// success
}
}else {
[_jsServiceQueue addObject:@{
@"name": name,
@"script": script
}];
}
}
_methodQueue
Выполнение очереди заключается в вызовеcallJSMethod
, позвоню внизWXJSCoreBridge
изinvokeMethod
, это вызов соответствующегоjs framework
предложенный метод, находяWXJSCoreBridge
файл, вотWeex
изbridge
,_jsContext
это все клиенты иjs framework
Все способы реального взаимодействия, эти способы предоставляютсяjs framework
Для вызова основные методы будут подробно описаны позже.
процесс выполнения фреймворка js
js framework
Исполняемый входной файл/runtime/entries/index.js
, который вызовет/runtime/entries/setup.js
,здесьjs
Детализация модульности очень хорошая, мы не будем показывать код по одному, вы можете перейти кWeex
Посмотрите исходный код проекта.
/**
* Setup frameworks with runtime.
* You can package more frameworks by
* passing them as arguments.
*/
export default function (frameworks) {
const { init, config } = runtime
config.frameworks = frameworks
const { native, transformer } = subversion
for (const serviceName in services) {
runtime.service.register(serviceName, services[serviceName])
}
runtime.freezePrototype()
// register framework meta info
global.frameworkVersion = native
global.transformerVersion = transformer
// init frameworks
const globalMethods = init(config)
// set global methods
for (const methodName in globalMethods) {
global[methodName] = (...args) => {
const ret = globalMethods[methodName](...args)
if (ret instanceof Error) {
console.error(ret.toString())
}
return ret
}
}
}
Мы в основном смотрим,js framework
Какие функции завершаются выполнением, главным образом, следующих трех функций:
- Метод монтирования глобальных свойств и метод цепочки прототипов виртуальных машин
- Создано на клиентском коммуникационном мосту
- компенсировать разницу в окружающей среде
Метод монтирования глобальных свойств и метод цепочки прототипов виртуальных машин
просто сказалDSL
что это такое,js framework
Очень важной функцией в хост-среде и языковой совместимости является хорошая работа. В основном через некоторые интерфейсы для взаимодействия с клиентом, адаптация интерфейсного фреймворка фактически предназначена для адаптацииiOS
,android
и браузер. Здесь мы в основном говорим об интерфейсе, который подстраивается под клиента.
- getRoot: получить узел страницы
- receiveTasks: слушать клиентские задачи
- registerComponents: зарегистрировать компонент
- registerModles: модуль регистрации
- init: инициализация внутреннего жизненного цикла страницы
- createInstance: создан во внутреннем жизненном цикле страницы.
- refreshInstance: обновление внутреннего жизненного цикла страницы
- destroyInstance: уничтожить внутренний жизненный цикл страницы ...
Эти интерфейсы могут бытьWXBridgeContext
увидеть всеjs framework
Предоставляется клиенту для звонка. вWeex SDK
При инициализации указанногоregisterComponents
а такжеregisterMoudles
Этот метод также называется .
registerComponents
js framework
серединаregisterComponents
Видно, что передняя часть просто делаетmap
Закэшировано, ждет парсингаvDOM
При отображении, а затем передать нативному компоненту для рендеринга.
// /runtime/frameworks/legacy/static/register.js
export function registerComponents (components) {
if (Array.isArray(components)) {
components.forEach(function register (name) {
/* istanbul ignore if */
if (!name) {
return
}
if (typeof name === 'string') {
nativeComponentMap[name] = true
}
/* istanbul ignore else */
else if (typeof name === 'object' && typeof name.type === 'string') {
nativeComponentMap[name.type] = name
}
})
}
}
registerMoudles
registerMoudles
Уже почти время, положи этоnativeModules
Этот объект кэшируется, но его сложнее использовать, о чем речь пойдет позже.
// /runtime/frameworks/legacy/static/register.js
export function registerModules (modules) {
/* istanbul ignore else */
if (typeof modules === 'object') {
initModules(modules)
}
}
// /runtime/frameworks/legacy/app/register.js
export function initModules (modules, ifReplace) {
for (const moduleName in modules) {
// init `modules[moduleName][]`
let methods = nativeModules[moduleName]
if (!methods) {
methods = {}
nativeModules[moduleName] = methods
}
// push each non-existed new method
modules[moduleName].forEach(function (method) {
if (typeof method === 'string') {
method = {
name: method
}
}
if (!methods[method.name] || ifReplace) {
methods[method.name] = method
}
})
}
}
Создано на клиентском коммуникационном мосту
js framework
Это мост между клиентом и интерфейсным бизнес-кодом, поэтому он более важен.bridge
, базовая конструкция моста также обсуждалась в предыдущей статье,Weex
Выбор состоит в том, чтобы предоставить метод непосредственно дляjs
Звоните, также звоните напрямуюjs
Методы.
Звонок клиентаjs
Использовать напрямуюcallJs
,callJs
даjs
Предоставленный метод помещается в текущий поток для вызова клиентом, включаяDOM
рассылка событий,module
Обратный вызов времени при вызове уведомляется через этот интерфейсjs framework
, а затем вызовите кеш вjs framework
метод в .
js
позвонить клиенту с помощьюcallNative
, клиент также предоставит множество методов дляjs framework
для,framework
вызов, эти методы доступны вWXBridgeContext
видеть в,callNative
Только один из методов, в реальном коде есть много методов, таких какaddElement
,updateAttrs
так далее
компенсировать разницу в окружающей среде
В дополнение к основным методам, используемым для завершения функции, клиент также предоставляет некоторые методы для компенсацииjs
Метод, который недоступен при вызове, — это разница в среде, чтобы компенсировать разницу в совместимости,setTimeout
,nativeLog
Подождите, клиент предоставляет соответствующий метод,js framework
Невозможно вызвать эти методы, например, в браузере, как позвонить этим методам, это требует от обеих сторон для поддержки использования режима совместимости.
осталось немногоployfill
метод, такой какPromise
,Object.assign
,Этиployfill
Это может гарантировать, что некоторые среды будут такими же, как браузеры, что снижает стоимость написания кода.
Законченный
воплощать в жизньjs framework
Другие процессы не будут расширяться один за другим, в основном взаимные вызовы между некоторыми внешними кодами, эта часть также берет на себя много работы.Weex
Какие-то проблемы с совместимостью остались от истории, а иногда встречаются какие-то волшебные способы записи, которые могут быть для решения каких-то волшебныхbug
бар и различныеistanbul ignore
заметки.
законченныйjs framework
После клиентаframeworkLoadFinished
будет установленоYES
, задачи, оставленные ранее, также будутjs framework
Выполните после выполнения, чтобы завершить весь процесс инициализации.
Клиент выполнит первымjs-service
,потому чтоjs-service
просто нужноJavaScript Core
Выполнить строку в строке, поэтому выполнить напрямуюexecuteAllJsService
Вот и не надо звонитьjs framework
метод, просто позвольте текущей среде памяти иметьjs service
Переменная объект.
потом_methodQueue
Задачи выведены для прохождения и выполнения. Вот очередь кэша выполненияregisterComponents
,registerModules
,registerMethods
. Выше также упоминалось, как вызвать два, подробный код находится вздесь.
После казни понятно, что этоjs Thread
должны быть закрыты, а затем переработаны, но мы также должны сделать этоjs framework
работает наjs Core
, так что это нужно датьjs Thread
открылrunloop
, пусть этоjs Thread
всегда в исполнении
Инициализация экземпляра Weex
Во фронте очень много процессов инициализации, просто чтобы было понятнее в процессе как отображается страница.Фронт эквивалентен подготовке к работе.Давайте посмотрим на этот раз.Weex
Инициализация экземпляра.ErosURL-адрес домашней страницы настраивается в файле конфигурации через файл конфигурации, и клиент может напрямую получить домашнюю страницу и инициализировать ее напрямую.
клиент через_renderWithURL
загрузить домашнюю страницуURL
,этоURL
Независимо от того, размещены ли они локально или на сервере, на самом деле этоjs bundle
файл это спец.loader
упакованныйjs
файл, после загрузки этого файла вызовите его дляjs framework
серединаcreateInstance.
/*
id:Weex 实例的 id
code:js bundle 的代码
config:配置参数
data:参数
*/
function createInstance (id, code, config, data) {
// 判断当前实例是否已经创建过了
if (instanceTypeMap[id]) {
return new Error(`The instance id "${id}" has already been used!`)
}
// 获取当前 bundle 是那种框架
const bundleType = getBundleType(code)
instanceTypeMap[id] = bundleType
// 初始化 instance 的 config
config = JSON.parse(JSON.stringify(config || {}))
config.env = JSON.parse(JSON.stringify(global.WXEnvironment || {}))
config.bundleType = bundleType
// 获取当前的 DSL
const framework = runtimeConfig.frameworks[bundleType]
if (!framework) {
return new Error(`[JS Framework] Invalid bundle type "${bundleType}".`)
}
if (bundleType === 'Weex') {
console.error(`[JS Framework] COMPATIBILITY WARNING: `
+ `Weex DSL 1.0 (.we) framework is no longer supported! `
+ `It will be removed in the next version of WeexSDK, `
+ `your page would be crash if you still using the ".we" framework. `
+ `Please upgrade it to Vue.js or Rax.`)
}
// 获得对应的 WeexInstance 实例,提供 Weex.xx 相关的方法
const instanceContext = createInstanceContext(id, config, data)
if (typeof framework.createInstance === 'function') {
// Temporary compatible with some legacy APIs in Rax,
// some Rax page is using the legacy ".we" framework.
if (bundleType === 'Rax' || bundleType === 'Weex') {
const raxInstanceContext = Object.assign({
config,
created: Date.now(),
framework: bundleType
}, instanceContext)
// Rax 或者 Weex DSL 调用初始化的地方
return framework.createInstance(id, code, config, data, raxInstanceContext)
}
// Rax 或者 Weex DSL 调用初始化的地方
return framework.createInstance(id, code, config, data, instanceContext)
}
// 当前 DSL 没有提供 createInstance 支持
runInContext(code, instanceContext)
}
Вышеупомянутое является первым шагом вызова, различныхDSL
Различие уже началось здесь, порождая различныеWeex
пример. Следующим шагом является вызов соответствующихDSL
изcreateInstance
И передаются параметры, соответствующие потребностям прошлого
// /runtime/frameworks/legacy/static/create.js
export function createInstance (id, code, options, data, info) {
const { services } = info || {}
resetTarget()
let instance = instanceMap[id]
/* istanbul ignore else */
options = options || {}
let result
/* istanbul ignore else */
if (!instance) {
// 创建 APP 实例,并将实例放到 instanceMap 上
instance = new App(id, options)
instanceMap[id] = instance
result = initApp(instance, code, data, services)
}
else {
result = new Error(`invalid instance id "${id}"`)
}
return (result instanceof Error) ? result : instance
}
// /runtime/frameworks/legacy/app/instance.js
export default function App (id, options) {
this.id = id
this.options = options || {}
this.vm = null
this.customComponentMap = {}
this.commonModules = {}
// document
this.doc = new renderer.Document(
id,
this.options.bundleUrl,
null,
renderer.Listener
)
this.differ = new Differ(id)
}
Главное этоinitAPP
В этом методе есть много методов для завершения цепочки прототипов, таких какbundleDefine
,bundleBootstrap
Подождите, это все очень важно, вы можете взглянутьinitметод для выполнения вышеуказанных операций.
Самое главное следующий метод, который будет окончательным выполнениемjs bundle
Место. После того, как выполнение будет завершено,Weex
Экземпляр одной страницы помещается вinstanceMap
,new Function
это основной метод, вот и весьJS bundle
Генерируется от кода до выполненияVDOM
, а затем преобразуется вVNode
Отправлено в собственный модуль для рендеринга.
if (!callFunctionNative(globalObjects, functionBody)) {
// If failed to compile functionBody on native side,
// fallback to callFunction.
callFunction(globalObjects, functionBody)
}
// 真正执行 js bundle 的方法
function callFunction (globalObjects, body) {
const globalKeys = []
const globalValues = []
for (const key in globalObjects) {
globalKeys.push(key)
globalValues.push(globalObjects[key])
}
globalKeys.push(body)
// 所有的方法都是通过 new Function() 的方式被执行的
const result = new Function(...globalKeys)
return result(...globalValues)
}
выполнение пакета js
js bundle
Это написанный бизнес-код, вы можете написать простой код и сохранить его для просмотра.Weex
Связанныйloader
, конкретный код однозначно и условноjs
Код другой, после конвертации основной<template>
а также<style>
часть, две части будут преобразованы в двеJSON
, в двух замыканиях. Как упоминалось выше, он наконец выполняется.new Function
, конкретные шаги выполнения находятся вinit, так как код слишком длинный, мы в основном смотрим на основную часть.
const globalObjects = Object.assign({
define: bundleDefine,
require: bundleRequire,
bootstrap: bundleBootstrap,
register: bundleRegister,
render: bundleRender,
__weex_define__: bundleDefine, // alias for define
__weex_bootstrap__: bundleBootstrap, // alias for bootstrap
__weex_document__: bundleDocument,
__weex_require__: bundleRequireModule,
__weex_viewmodel__: bundleVm,
weex: weexGlobalObject
}, timerAPIs, services)
Приведенный выше код является основной частью, которая выполняется,bundleDefineРаздел, вот раздел для разборки компонентов, разборки, которые являются иWeex
соответствующийComponent
, которые определяются пользователемComponent
, вот рекурсивный процесс обхода.
bundleRequire
а такжеbundleBootstrap
, который здесь называетсяbootstrapа такжеVm, вот шаг, который я не совсем понимаю.bootstrap
Основная функция — проверка параметров и информации об окружении.Вы можете посмотреть исходный код этой части.
Vm
основывается наComponent
создать соответствующийViewModel
, эта часть делает много вещей, в основном анализируя весьVM
Основной. В основном завершен жизненный цикл инициализации, двойная привязка данных, шаблон построения,UI
рисовать.
// bind events and lifecycles
initEvents(this, externalEvents)
console.debug(`[JS Framework] "init" lifecycle in Vm(${this._type})`)
this.$emit('hook:init')
this._inited = true
// proxy data and methods
// observe data and add this to vms
this._data = typeof data === 'function' ? data() : data
if (mergedData) {
extend(this._data, mergedData)
}
initState(this)
console.debug(`[JS Framework] "created" lifecycle in Vm(${this._type})`)
this.$emit('hook:created')
this._created = true
// backward old ready entry
if (options.methods && options.methods.ready) {
console.warn('"exports.methods.ready" is deprecated, ' +
'please use "exports.created" instead')
options.methods.ready.call(this)
}
if (!this._app.doc) {
return
}
// if no parentElement then specify the documentElement
this._parentEl = parentEl || this._app.doc.documentElement
build(this)
жизненный цикл инициализации
Код; В этом процессе инициализируются 4 хука жизненного цикла,init
,created
,ready
,destroyed
. Помимо жизненного цикла, здесь также связанvm
механизм событий, способ взаимодействия компонентов друг с другом.
двойная привязка данных
Код;Vue DSL
Двойная привязка данных может относиться кVue
Принцип реализации двойной привязки данных,Rax
Это также похоже на проксирование данных, затем добавление мониторинга данных, инициализацию вычисляемых свойств и монтирование_method
метод, создатьgetter/setter
, методы переопределения массивов, рекурсивное связывание... Эта часть в основномVue
Содержание предыдущего блога также было подробно объяснено.Vue
Механизм двойной привязки данных.
Разбор шаблона
Код; здесь тожеVue
Один из механизмов разбора шаблонов дляVue
Разбор синтаксиса шаблона, такого какv-for
,:class
Процесс разбора грамматики представляет собой процесс глубокого обхода, после завершения которогоjs bundle
это становитсяVDOM
,этоVDOM
Это больше похоже на соответствие некоторому формату соглашенияJSON
данные, потому что клиент иjs framework
Существует не так много типов данных, которые можно использовать совместно.JSON
это лучший способ, поэтому, наконец, преобразуйте шаблон вJSON
Описание передается клиенту.
Нарисуйте собственный пользовательский интерфейс
Код;пройти черезdiffer.flush
звонок, сработаетVDOM
Процесс сравнения — это процесс сравнения того же уровня, и узелVNode
по одномуdiff
передано клиенту. Сначала сравните внешние компоненты, если есть дочерние узлы, а затем повторите дочерние узлы, различные части сравнения передаются клиенту, первый рендеринг полностью новый, а затем обновляетсяUI
будет полезно, когдаremove
,update
ЖдатьAPI
.
финальный розыгрышappendChild, который инкапсулирует все иnative
Есть интерактивные методы.DOM
Операция примерноaddElement
,removeElement
и т.д. метод, вызовtaskCenter.send
, вот планирование задач, и, наконец, все методы вызывают через this соответствующий интерфейс, предоставляемый клиентом.
send (type, params, args, options) {
const { action, component, ref, module, method } = params
// normalize args and options
args = args.map(arg => this.normalize(arg))
if (typof(options) === 'Object') {
options = this.normalize(options, true)
}
switch (type) {
case 'dom':
return this[action](this.instanceId, args)
case 'component':
return this.componentHandler(this.instanceId, ref, method, args, Object.assign({ component }, options))
default:
return this.moduleHandler(this.instanceId, module, method, args, options)
}
}
После звонка клиенту просмотрите передWeex SDK
При инициализацииaddElement
это метод, внедренный на стороне клиента, а затем соответствующийComponent
Сопоставляется с соответствующим собственным методом синтаксического анализа. Родной, а затем найти соответствующийComponent
рендерить.
из-заWeex
После рендеринга родителя будет рендериться дочерний, поэтому порядок прохождения таков: сначала передать родителя, а затем дочернего. инструкции дочерних узлов, чтобы можно было медленнее, упомянутая выше регистрацияComponent
Когда есть два параметраappend=tree
а такжеistemplate=true
, обе из которых являются схемами оптимизации производительности, как упоминалось выше вComponents
При регистрации есть два параметра.
append=tree
BOOL appendTree = !appendingInTree && [component.attributes[@"append"] isEqualToString:@"tree"];
// if ancestor is appending tree, child should not be laid out again even it is appending tree.
for(NSDictionary *subcomponentData in subcomponentsData){
[self _recursivelyAddComponent:subcomponentData toSupercomponent:component atIndex:-1 appendingInTree:appendTree || appendingInTree];
}
[component _didInserted];
if (appendTree) {
// If appending tree,force layout in case of too much tasks piling up in syncQueue
[self _layoutAndSyncUI];
}
Weex
Есть два способа рендеринга: одинnode
, одинtree
,node
заключается в том, чтобы сначала отобразить родительский узел, а затем отобразить дочерний узел, иtree
Сначала нужно отобразить дочерние узлы, а затем — последние.layout
Визуализируйте родительский узел. С точки зрения производительности рендеринга, начальное время рисования,append="node"
относительно быстро, но с точки зрения общего времени,append="tree"
Тратьте меньше времени.
если текущийComponent
имеют{@"append":@"tree"}
свойство и его родительComponent
Отсутствие этого свойства приведет к изменению макета страницы. Видно, что это сделано для предотвращенияUI
Слишком много задач рисования накапливаются вместе и влияют на выполнение задач синхронной очереди.
istemplate=true
WXComponentConfig *config = [WXComponentFactory configWithComponentName:type];
BOOL isTemplate = [config.properties[@"isTemplate"] boolValue] || (supercomponent && supercomponent->_isTemplate);
if (isTemplate) {
bindingProps = [self _extractBindingProps:&attributes];
bindingStyles = [self _extractBindings:&styles];
bindingAttibutes = [self _extractBindings:&attributes];
bindingEvents = [self _extractBindingEvents:&events];
}
Затем, когда клиент выполняет рендеринг, онComponent
Дочерний узел получен, а затем переданDataBinding
Преобразовано в выражение, существуетbindingMap
, соответствующий анализ находится вWXJSASTParser.m
В файле это включает в себя более сложный синтаксический анализ шаблона, синтаксический анализ и преобразование выражений, данные привязки и собственныеUI
Отношение.
Во время рендеринга клиент иjs framework
Есть также общение событий, которое пропускается через мостcreateFinished
а такжеrenderFinished
мероприятие,js framework
будет выполнятьWeex
Метод жизненного цикла, соответствующий экземпляру.
На данный момент страница была обработана. После того, как обработка страницы завершена, как происходит событие клика?
доставка события
глобальное событие
Прежде чем понять, как доставляются события, давайте рассмотрим несколько типов событий.ErosИнкапсулирует маршрутизируемые события, инкапсулирует эти события в компонентах, вVue
предоставить шаблонErosобъект, вWeex
При создании экземпляра свяжите эти методы для внедрения обратных вызовов и дождитесь обратного вызова клиента.Клиент уведомляется через глобальное событие, когда происходит соответствующее событие.js framework
воплощать в жизньweex
Метод обратного вызова экземпляра.
// app 前后台相关 start
appActive() {
console.log('appActive');
},
appDeactive() {
console.log('appDeactive');
},
// app 前后台相关 end
// 页面周期相关 start
beforeAppear (params, options) {
console.log('beforeAppear');
},
beforeBackAppear (params, options) {
console.log('beforeBackAppear');
},
appeared (params, options) {
console.log('appeared');
},
backAppeared (params, options) {
console.log('backAppeared');
},
beforeDisappear (options) {
console.log('beforeDisappear');
},
disappeared (options) {
console.log('disappeared');
},
// 页面周期相关 end
глобальное событиеErosчерез что-то вродеnode js
обработка, вjs core
Поместите в него глобальный объект, это также похоже на использованиеModule
способ использования, инкапсулируя что-то вродеjs
механизм события для срабатывания.
событие взаимодействия
В основном мы анализируем события взаимодействия со страницей, такие как клики, как клиент может выполнитьVue
Как насчет методов, определенных для экземпляров? В этом процессе событие щелчка должно быть зарегистрировано первым, то есть, когда оно инициализируется,js framework
Он уже сообщил клиенту, какие компоненты имеют обратные вызовы привязки событий.Если клиент получает какое-либо событие, оно будет передано вjs
, производительность определенно будет плохой.
создание события
js framework
Обнаружен тег события при разборе шаблона@xxx="callback"
, он будет передан при создании компонентаcallAddEvent
Будуevent
Перейти кnative
, но метод обратного вызова события не будет передан, потому что клиент вообще не распознает метод обратного вызова события.После того, как клиент найдет атрибут события, он привяжет нативное событие к событию.При рендеринге компонентов каждый компонент будет генерировать компонентID
,то естьref
,type
Это тип события, такой как:click
,longpress
Ждать.
// https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/vm/compiler.js
if (!vm._rootEl) {
vm._rootEl = element
// bind event earlier because of lifecycle issues
const binding = vm._externalBinding || {}
const target = binding.template
const parentVm = binding.parent
if (target && target.events && parentVm && element) {
for (const type in target.events) {
const handler = parentVm[target.events[type]]
if (handler) {
element.addEvent(type, bind(handler, parentVm))
}
}
}
}
// https://github.com/apache/incubator-weex/blob/master/runtime/vdom/Element.js
addEvent (type, handler, params) {
if (!this.event) {
this.event = {}
}
if (!this.event[type]) {
this.event[type] = { handler, params }
const taskCenter = getTaskCenter(this.docId)
if (taskCenter) {
taskCenter.send(
'dom',
{ action: 'addEvent' },
[this.ref, type]
)
}
}
}
Из вышеизложенного видно, что только одинref
В прошлом, после того как привязка была завершена до тех пор, пока все компоненты не были отрисованы, когда в представлении происходило соответствующее событие, клиент перехватывал событие и передавалfireEvent
Передайте соответствующее событие с четырьмя параметрами,ref
,type
,event
,domChanges
,пройти черезbridge
передать эти параметрыjs framework
изbridge
, но также будет нестиWeex
примерID
, так как их может быть несколькоweex
Экземпляр по Weex ID找到对应的
экземпляр weex.
Если имеется несколько привязок событийref
Также нужно посмотреть на рекурсивный обход, но и процесс глубокого обхода, а затем найти соответствующее событие, запуская соответствующее событие, событие, которое могут быть изменения в двойной связывать данные, а затем изменитьDOM
, поэтому после повторного запуска событияdiffer.flush
. Сравните и создайте новыеVDOM
, а затем визуализировать новый стиль страницы.
триггер события
// https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/app/ctrl/misc.js
export function fireEvent (app, ref, type, e, domChanges) {
console.debug(`[JS Framework] Fire a "${type}" event on an element(${ref}) in instance(${app.id})`)
if (Array.isArray(ref)) {
ref.some((ref) => {
return fireEvent(app, ref, type, e) !== false
})
return
}
const el = app.doc.getRef(ref)
if (el) {
const result = app.doc.fireEvent(el, type, e, domChanges)
app.differ.flush()
app.doc.taskCenter.send('dom', { action: 'updateFinish' }, [])
return result
}
return new Error(`invalid element reference "${ref}"`)
}
app.doc.fireEvent(el, type, e, domChanges)
Давайте взглянем на этот метод. Сначала получите обратный вызов события в это время, а затем выполните обратный вызов события. В нативном компоненте не будет всплытия событий, ноjs
Существует механизм всплытия событий, поэтому следующее имитирует механизм всплытия событий и продолжает запускать родительскийfireEvent
Один за другим пузырем для родителя, который частичноjs framework
завершено в.
// https://github.com/apache/incubator-weex/blob/master/runtime/vdom/Element.js
fireEvent (type, event, isBubble, options) {
let result = null
let isStopPropagation = false
const eventDesc = this.event[type]
if (eventDesc && event) {
const handler = eventDesc.handler
event.stopPropagation = () => {
isStopPropagation = true
}
if (options && options.params) {
result = handler.call(this, ...options.params, event)
}
else {
result = handler.call(this, event)
}
}
if (!isStopPropagation
&& isBubble
&& (BUBBLE_EVENTS.indexOf(type) !== -1)
&& this.parentNode
&& this.parentNode.fireEvent) {
event.currentTarget = this.parentNode
this.parentNode.fireEvent(type, event, isBubble) // no options
}
return result
}
Вышеупомянутое завершает полный триггер события, если это простое событие, что-то вродеclick
Такая передача завершает обратный вызов события, и особой проблемы нет, но если передача события прокручивается, неизбежно будут проблемы с производительностью, поэтому при обработке клиентом события прокрутки точно будет минимальный временной интервал, однозначно Не триггер на все времена.
Лучшая управляемостьWeex
также представилexpression binding
,Будуjs
Обратный вызов события обрабатывается в выражение, которое передается клиенту, когда он связан. Поскольку это выражение, клиент также может распознать выражение. Когда клиент слушает на триггере собственного события, он напрямую выполняет выражение. Это сохраняет процесс передачи.Weex
изbingdingX
Его также можно использовать для обработки подобных частых триггеров.js
Взаимодействие с клиентом, например анимация.
использование модуля
было сказано вышеmodule
Регистрация финального звонкаjs framework
изregisterModules
вводить всеmodule
метод и сохранить метод вnativeModules
На объекте процесс регистрации завершен.
// https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/static/register.js
export function registerModules (modules) {
/* istanbul ignore else */
if (typeof modules === 'object') {
initModules(modules)
}
}
// https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/app/register.js
export function initModules (modules, ifReplace) {
for (const moduleName in modules) {
// init `modules[moduleName][]`
let methods = nativeModules[moduleName]
if (!methods) {
methods = {}
nativeModules[moduleName] = methods
}
// push each non-existed new method
modules[moduleName].forEach(function (method) {
if (typeof method === 'string') {
method = {
name: method
}
}
if (!methods[method.name] || ifReplace) {
methods[method.name] = method
}
})
}
}
requireModule
мы проходимweex.requireModule('xxx')
получитьmodule
, сначала нам нужно понятьweex
Эта глобальная переменная появится, и она будет генерировать ее во время рендеринга.weex
Экземпляр, эта информация будет храниться в глобальной переменнойweexGlobalObject
,существуетcallFunction
, объект будет привязан вjs bundle
во время выполненияweex
По объекту подробности следующие.
const globalObjects = Object.assign({
...
weex: weexGlobalObject
}, timerAPIs, services)
weex
У этого объекта также есть множество методов и свойств, некоторые из которых можно вызывать дляmodule
путьrequireModule
, этот метод и приведенная выше клиентская инъекцияModule
Когда метод находится в том же модуле, то есть в том же замыкании, его можно использовать совместно.nativeModules
этот объект.
//https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/app/index.js
App.prototype.requireModule = function (name) {
return requireModule(this, name)
}
// https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/app/register.js
export function requireModule (app, name) {
const methods = nativeModules[name]
const target = {}
for (const methodName in methods) {
Object.defineProperty(target, methodName, {
configurable: true,
enumerable: true,
get: function moduleGetter () {
return (...args) => app.callTasks({
module: name,
method: methodName,
args: args
})
},
set: function moduleSetter (value) {
if (typeof value === 'function') {
return app.callTasks({
module: name,
method: methodName,
args: [value]
})
}
}
})
}
return target
}
Почему вышеприведенное не использовало простойcall
илиapply
метод? Вместо этого он выполняет аналогичную операцию двойной привязки для всех методов этого объекта при возврате. Прежде всего, это должно быть предотвращение загрязнения объекта, этоnativeModules
это всеweex
Если объекты, совместно используемые экземплярами, можно получить напрямую, то все интерфейсные объекты являются ссылками и могут быть переписаны, что определенно нехорошо.
Здесь также использовалиcallTasks
, это было объяснено в предыдущей инициализации, на самом деле это вызов соответствующегоnative
Методы,taskCenter.send
Он будет искать метод, соответствующий клиенту, у которого естьtaskCenter
Соответствующий код, наконец, принятcallNativeModule
Код для вызова клиенту.
// https://github.com/apache/incubator-weex/blob/master/runtime/frameworks/legacy/app/ctrl/misc.js
export function callTasks (app, tasks) {
let result
/* istanbul ignore next */
if (typof(tasks) !== 'array') {
tasks = [tasks]
}
tasks.forEach(task => {
result = app.doc.taskCenter.send(
'module',
{
module: task.module,
method: task.method
},
task.args
)
})
return result
}
После завершения вызова подождите, пока клиент обработает вызов, и вернитесь после завершения клиентского процесса. Хотя естьforEach
обход, но возвращаетсяresult
оба синхронизированы последнимиresult
. Здесь не очень строго, но мы видим, что с надстройкой проблем нет,tasks
Это вообще пошаговая задача, и она не будет пройдена.array
Приходите, и большинство методов клиентских вызовов асинхронны, и есть несколько синхронных обратных вызовов, поэтому можно только сказать, что это неточно.
Суммировать
Благодаря приведенному выше расчесыванию мы можем видеть, чтоWeex
Детали принципа работы и общий процесс также разобрались. Через год практики, чисто ли этоWeex
приложение все еще существуетAPP
Доступ был отработан, поддерживая наш бизнес на сотнях страниц, и в то же время эффективность разработки значительно повысилась, а также улучшилась наша база.Vue
стек передовых технологий.
СейчасWeex
Он также постоянно обновляется, по крайней мере, после запуска нашего бизнеса, будем верить.Weex
Осуществимо, хотя постоянно критикуются различные недостатки, но какая превосходная техника не испытала такого развития. Снимите очки презрения с наших передовых технологий и позвольте технологиям лучше служить бизнесу.
Наконец, после деловой практики и накопления, мы также обобщили на основеWeex
технические решенияErosИ откройте исходный код, решите экологические проблемы, которые все критиковали, и предоставьте более обильныеComponent
а такжеModule
Решайте реальные бизнес-задачи. В настоящее время тысячи разработчиков имеют опыт разработки. Они постоянно жалуются на улучшение нашего решения, стабилизацию базового решения и создание нового метода подключаемых модулей. В настоящее время некоторые разработчики предоставили несколько подключаемых модулей, а мы также собрали данные от разработчиков, которые имеют онлайн40+ APP
кейсы и многое другоеAPP
во время разработки. Надеюсь, наше решение поможетAPP
Вы в разработке.
Вот некоторые черезErosонлайнAPP
кейс