Add sample component with state in RNTester (#34909)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/34909 This Diff introduces a Sample component in RNTester which uses the NativeState to load some images. It is an example on how to use CustomNativeState In this first diff, I focused on the iOS side of things. The next diff will make this work with Android. ## Changelog [iOS][Added] - Introduce sample component which work with the native state. Reviewed By: cortinico Differential Revision: D39884926 fbshipit-source-id: 9323d751fd06a1bb8ff93af836d97010c2095833
This commit is contained in:
Родитель
d7c41361dd
Коммит
1a9cceb20b
|
@ -51,6 +51,7 @@ rn_library(
|
|||
"js",
|
||||
"NativeModuleExample",
|
||||
"NativeComponentExample",
|
||||
"NativeComponentWithState",
|
||||
"RCTTest",
|
||||
],
|
||||
excludes = [
|
||||
|
@ -85,6 +86,7 @@ fb_native.filegroup(
|
|||
],
|
||||
exclude = [
|
||||
"NativeComponentExample/**/*",
|
||||
"NativeComponentWithState/**/*",
|
||||
],
|
||||
),
|
||||
visibility = ["PUBLIC"],
|
||||
|
@ -321,3 +323,42 @@ rn_xplat_cxx_library2(
|
|||
"//xplat/js/react-native-github:RCTFabricComponentViewsBase",
|
||||
],
|
||||
)
|
||||
|
||||
rn_xplat_cxx_library2(
|
||||
name = "NativeComponentWithState",
|
||||
plugins_only = True,
|
||||
srcs = glob(
|
||||
[
|
||||
"NativeComponentWithState/ios/*.m",
|
||||
"NativeComponentWithState/ios/*.mm",
|
||||
"NativeComponentWithState/ios/*.cpp",
|
||||
],
|
||||
),
|
||||
headers = glob(
|
||||
[
|
||||
"NativeComponentWithState/ios/*.h",
|
||||
],
|
||||
),
|
||||
header_namespace = "",
|
||||
compiler_flags = [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-std=c++17",
|
||||
"-Wall",
|
||||
],
|
||||
contacts = ["oncall+react_native@xmail.facebook.com"],
|
||||
labels = [
|
||||
"pfh:ReactNative_CommonInfrastructurePlaceholder",
|
||||
"supermodule:xplat/default/public.react_native.infra",
|
||||
],
|
||||
plugins = [
|
||||
react_fabric_component_plugin_provider("RNTNativeComponentWithStateView", "RNTNativeComponentWithStateCls"),
|
||||
],
|
||||
plugins_header = "RCTFabricComponentsPlugins.h",
|
||||
reexport_all_header_dependencies = False,
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
":generated_components-AppSpecs",
|
||||
"//xplat/js/react-native-github:RCTFabricComponentViewsBase",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
require "json"
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "../" "package.json")))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "NativeComponentWithState"
|
||||
s.version = package["version"]
|
||||
s.summary = package["description"]
|
||||
s.description = "native-component-with-state"
|
||||
s.homepage = "https://github.com/sota000/my-native-view.git"
|
||||
s.license = "MIT"
|
||||
s.platforms = { :ios => "12.4", :tvos => "12.4" }
|
||||
s.compiler_flags = '-Wno-documentation -Wno-nullability-completeness'
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.source = { :git => "https://github.com/facebook/my-native-view.git", :tag => "#{s.version}" }
|
||||
s.pod_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/boost\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Codegen/React_Codegen.framework/Headers\"",
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
||||
}
|
||||
|
||||
s.source_files = "ios/**/*.{h,m,mm,cpp}"
|
||||
s.requires_arc = true
|
||||
|
||||
install_modules_dependencies(s)
|
||||
|
||||
# Enable codegen for this library
|
||||
use_react_native_codegen!(s, {
|
||||
:library_name => "NativeComponentWithStateSpec",
|
||||
:react_native_path => "../../../",
|
||||
:js_srcs_dir => "./js",
|
||||
:library_type => "components"
|
||||
})
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
||||
#include "RNTNativeComponentWithStateCustomShadowNode.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* Descriptor for <RNTNativeComponentWithStateCustomComponentDescriptor>
|
||||
* component.
|
||||
*/
|
||||
class RNTNativeComponentWithStateCustomComponentDescriptor final
|
||||
: public ConcreteComponentDescriptor<
|
||||
RNTNativeComponentWithStateCustomShadowNode> {
|
||||
public:
|
||||
RNTNativeComponentWithStateCustomComponentDescriptor(
|
||||
ComponentDescriptorParameters const ¶meters)
|
||||
: ConcreteComponentDescriptor(parameters),
|
||||
imageManager_(std::make_shared<ImageManager>(contextContainer_)) {}
|
||||
|
||||
void adopt(ShadowNode::Unshared const &shadowNode) const override {
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
|
||||
auto compShadowNode =
|
||||
std::static_pointer_cast<RNTNativeComponentWithStateCustomShadowNode>(
|
||||
shadowNode);
|
||||
|
||||
// `RNTNativeComponentWithStateCustomShadowNode` uses `ImageManager` to
|
||||
// initiate image loading and communicate the loading state
|
||||
// and results to mounting layer.
|
||||
compShadowNode->setImageManager(imageManager_);
|
||||
}
|
||||
|
||||
private:
|
||||
const SharedImageManager imageManager_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "RNTNativeComponentWithStateCustomShadowNode.h"
|
||||
|
||||
#include <react/renderer/core/LayoutContext.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
extern const char RNTNativeComponentWithStateComponentName[] =
|
||||
"RNTNativeComponentWithState";
|
||||
|
||||
void RNTNativeComponentWithStateCustomShadowNode::setImageManager(
|
||||
const SharedImageManager &imageManager) {
|
||||
ensureUnsealed();
|
||||
imageManager_ = imageManager;
|
||||
}
|
||||
|
||||
void RNTNativeComponentWithStateCustomShadowNode::updateStateIfNeeded() {
|
||||
const auto &newImageSource = getImageSource();
|
||||
|
||||
auto const ¤tState = getStateData();
|
||||
|
||||
auto imageSource = currentState.getImageSource();
|
||||
|
||||
bool anyChanged = newImageSource != imageSource;
|
||||
|
||||
if (!anyChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we are about to mutate the Shadow Node.
|
||||
ensureUnsealed();
|
||||
|
||||
// It is not possible to copy or move image requests from SliderLocalData,
|
||||
// so instead we recreate any image requests (that may already be in-flight?)
|
||||
// TODO: check if multiple requests are cached or if it's a net loss
|
||||
auto state = RNTNativeComponentWithStateState{
|
||||
newImageSource,
|
||||
imageManager_->requestImage(newImageSource, getSurfaceId())};
|
||||
setStateData(std::move(state));
|
||||
}
|
||||
|
||||
ImageSource RNTNativeComponentWithStateCustomShadowNode::getImageSource()
|
||||
const {
|
||||
return getConcreteProps().imageSource;
|
||||
}
|
||||
|
||||
void RNTNativeComponentWithStateCustomShadowNode::layout(
|
||||
LayoutContext layoutContext) {
|
||||
updateStateIfNeeded();
|
||||
ConcreteViewShadowNode::layout(layoutContext);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/renderer/components/AppSpecs/EventEmitters.h>
|
||||
#include <react/renderer/components/AppSpecs/Props.h>
|
||||
#include <react/renderer/components/AppSpecs/States.h>
|
||||
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
||||
|
||||
#include <react/renderer/imagemanager/ImageManager.h>
|
||||
#include <react/renderer/imagemanager/primitives.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
JSI_EXPORT extern const char RNTNativeComponentWithStateComponentName[];
|
||||
|
||||
/*
|
||||
* `ShadowNode` for <Slider> component.
|
||||
*/
|
||||
class RNTNativeComponentWithStateCustomShadowNode final
|
||||
: public ConcreteViewShadowNode<
|
||||
RNTNativeComponentWithStateComponentName,
|
||||
RNTNativeComponentWithStateProps,
|
||||
RNTNativeComponentWithStateEventEmitter,
|
||||
RNTNativeComponentWithStateState> {
|
||||
public:
|
||||
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
||||
|
||||
// Associates a shared `ImageManager` with the node.
|
||||
void setImageManager(const SharedImageManager &imageManager);
|
||||
|
||||
static RNTNativeComponentWithStateState initialStateData(
|
||||
ShadowNodeFragment const &fragment,
|
||||
ShadowNodeFamilyFragment const &familyFragment,
|
||||
ComponentDescriptor const &componentDescriptor) {
|
||||
auto imageSource = ImageSource{ImageSource::Type::Invalid};
|
||||
return {imageSource, {imageSource, nullptr}};
|
||||
}
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
void layout(LayoutContext layoutContext) override;
|
||||
|
||||
private:
|
||||
void updateStateIfNeeded();
|
||||
|
||||
ImageSource getImageSource() const;
|
||||
|
||||
SharedImageManager imageManager_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewComponentView.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RNTNativeComponentWithStateView : RCTViewComponentView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RNTNativeComponentWithStateView.h"
|
||||
|
||||
#import <react/renderer/components/AppSpecs/ComponentDescriptors.h>
|
||||
#import <react/renderer/components/AppSpecs/EventEmitters.h>
|
||||
#import <react/renderer/components/AppSpecs/Props.h>
|
||||
#import <react/renderer/components/AppSpecs/RCTComponentViewHelpers.h>
|
||||
#import "RNTNativeComponentWithStateCustomComponentDescriptor.h"
|
||||
|
||||
#import <React/RCTImageResponseDelegate.h>
|
||||
#import <React/RCTImageResponseObserverProxy.h>
|
||||
|
||||
#import "RCTFabricComponentsPlugins.h"
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
@interface RNTNativeComponentWithStateView () <RCTRNTNativeComponentWithStateViewProtocol, RCTImageResponseDelegate>
|
||||
@end
|
||||
|
||||
@implementation RNTNativeComponentWithStateView {
|
||||
UIView *_view;
|
||||
UIImageView *_imageView;
|
||||
UIImage *_image;
|
||||
ImageResponseObserverCoordinator const *_imageCoordinator;
|
||||
RCTImageResponseObserverProxy _imageResponseObserverProxy;
|
||||
}
|
||||
|
||||
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
||||
{
|
||||
return concreteComponentDescriptorProvider<RNTNativeComponentWithStateCustomComponentDescriptor>();
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
static const auto defaultProps = std::make_shared<const RNTNativeComponentWithStateProps>();
|
||||
_props = defaultProps;
|
||||
|
||||
_view = [[UIView alloc] init];
|
||||
_view.backgroundColor = [UIColor redColor];
|
||||
|
||||
_imageView = [[UIImageView alloc] init];
|
||||
[_view addSubview:_imageView];
|
||||
|
||||
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[_imageView.topAnchor constraintEqualToAnchor:_view.topAnchor constant:10],
|
||||
[_imageView.leftAnchor constraintEqualToAnchor:_view.leftAnchor constant:10],
|
||||
[_imageView.bottomAnchor constraintEqualToAnchor:_view.bottomAnchor constant:-10],
|
||||
[_imageView.rightAnchor constraintEqualToAnchor:_view.rightAnchor constant:-10],
|
||||
]];
|
||||
_imageView.image = _image;
|
||||
|
||||
_imageResponseObserverProxy = RCTImageResponseObserverProxy(self);
|
||||
|
||||
self.contentView = _view;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)prepareForRecycle
|
||||
{
|
||||
[super prepareForRecycle];
|
||||
self.imageCoordinator = nullptr;
|
||||
_image = nil;
|
||||
}
|
||||
|
||||
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
||||
{
|
||||
[super updateProps:props oldProps:oldProps];
|
||||
}
|
||||
|
||||
- (void)updateState:(facebook::react::State::Shared const &)state
|
||||
oldState:(facebook::react::State::Shared const &)oldState
|
||||
{
|
||||
auto _state = std::static_pointer_cast<RNTNativeComponentWithStateShadowNode::ConcreteState const>(state);
|
||||
auto _oldState = std::static_pointer_cast<RNTNativeComponentWithStateShadowNode::ConcreteState const>(oldState);
|
||||
|
||||
auto data = _state->getData();
|
||||
|
||||
bool havePreviousData = _oldState != nullptr;
|
||||
|
||||
auto getCoordinator = [](ImageRequest const *request) -> ImageResponseObserverCoordinator const * {
|
||||
if (request) {
|
||||
return &request->getObserverCoordinator();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
if (!havePreviousData || data.getImageSource() != _oldState->getData().getImageSource()) {
|
||||
self.imageCoordinator = getCoordinator(&data.getImageRequest());
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setImageCoordinator:(const ImageResponseObserverCoordinator *)coordinator
|
||||
{
|
||||
if (_imageCoordinator) {
|
||||
_imageCoordinator->removeObserver(_imageResponseObserverProxy);
|
||||
}
|
||||
_imageCoordinator = coordinator;
|
||||
if (_imageCoordinator) {
|
||||
_imageCoordinator->addObserver(_imageResponseObserverProxy);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image
|
||||
{
|
||||
if ([image isEqual:_image]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_imageView.image = image;
|
||||
}
|
||||
|
||||
#pragma mark - RCTImageResponseDelegate
|
||||
|
||||
- (void)didReceiveImage:(UIImage *)image metadata:(id)metadata fromObserver:(void const *)observer
|
||||
{
|
||||
if (observer == &_imageResponseObserverProxy) {
|
||||
self.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didReceiveProgress:(float)progress fromObserver:(void const *)observer
|
||||
{
|
||||
}
|
||||
|
||||
- (void)didReceiveFailureFromObserver:(void const *)observer
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class<RCTComponentViewProtocol> RNTNativeComponentWithStateCls(void)
|
||||
{
|
||||
return RNTNativeComponentWithStateView.class;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface RNTNativeComponentWithStateViewManager : RCTViewManager
|
||||
@end
|
||||
|
||||
@implementation RNTNativeComponentWithStateViewManager
|
||||
|
||||
RCT_EXPORT_MODULE(RNTNativeComponentWithState)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(imageSource, UIImage *)
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[UIView alloc] init];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
||||
|
||||
import type {ImageSource} from 'react-native/Libraries/Image/ImageSource';
|
||||
import type {HostComponent} from 'react-native/Libraries/Renderer/shims/ReactNativeTypes';
|
||||
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
|
||||
|
||||
type NativeProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
imageSource: ImageSource,
|
||||
|}>;
|
||||
|
||||
// The NativeState is not used in JS-land, so we don't have to export it.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type ComponentNativeState = $ReadOnly<{|
|
||||
imageSource: ImageSource,
|
||||
//$FlowFixMe[cannot-resolve-name]: this type is not exposed in JS but we can use it in the Native State.
|
||||
imageRequest: ImageRequest,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<NativeProps>(
|
||||
'RNTNativeComponentWithState',
|
||||
): HostComponent<NativeProps>);
|
|
@ -28,6 +28,7 @@ def pods(options = {}, use_flipper: !IN_CI && !USE_FRAMEWORKS)
|
|||
if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
|
||||
# Custom fabric component is only supported when using codegen discovery.
|
||||
pod 'MyNativeView', :path => "NativeComponentExample"
|
||||
pod 'NativeComponentWithState', :path => "NativeComponentWithState"
|
||||
end
|
||||
|
||||
use_react_native!(
|
||||
|
|
|
@ -961,6 +961,6 @@ SPEC CHECKSUMS:
|
|||
Yoga: 1b1a12ff3d86a10565ea7cbe057d42f5e5fb2a07
|
||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||
|
||||
PODFILE CHECKSUM: e86c02825ce4e267e6fb3975bae791feb32a94a0
|
||||
PODFILE CHECKSUM: 6e26aac84dafab208188a8c6e45de8b3cf2a9f62
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
|
|
@ -63,7 +63,6 @@
|
|||
#endif
|
||||
|
||||
#import <ReactCommon/RCTTurboModuleManager.h>
|
||||
|
||||
#import "RNTesterTurboModuleProvider.h"
|
||||
|
||||
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import NativeComponentWithState from '../../../NativeComponentWithState/js/NativeComponentWithState';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
component: {
|
||||
marginLeft: 150,
|
||||
width: 100,
|
||||
height: 100,
|
||||
marginTop: 20,
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = 'Component with State';
|
||||
exports.description =
|
||||
'Codegen discovery must be enabled for iOS. See Podfile for more details. Component with State';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Component with State',
|
||||
description:
|
||||
'Change the image source in the Examples/NewArchitecture/ComponentWithState.js file',
|
||||
render(): React.Element<any> {
|
||||
return (
|
||||
<>
|
||||
<NativeComponentWithState
|
||||
imageSource={{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
}}
|
||||
style={styles.component}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
|
@ -161,6 +161,12 @@ const Components: Array<RNTesterModuleInfo> = [
|
|||
module: require('../examples/NewArchitecture/NewArchitectureExample'),
|
||||
supportsTVOS: false,
|
||||
},
|
||||
{
|
||||
key: 'ComponentWithState',
|
||||
category: 'UI',
|
||||
module: require('../examples/NewArchitecture/ComponentWithState'),
|
||||
supportsTVOS: false,
|
||||
},
|
||||
];
|
||||
|
||||
const APIs: Array<RNTesterModuleInfo> = [
|
||||
|
|
Загрузка…
Ссылка в новой задаче