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:
Riccardo Cipolleschi 2022-10-10 02:51:06 -07:00 коммит произвёл Facebook GitHub Bot
Родитель d7c41361dd
Коммит 1a9cceb20b
14 изменённых файлов: 520 добавлений и 2 удалений

Просмотреть файл

@ -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 &parameters)
: 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 &currentState = 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> = [