Fabric: `Element<>`, a declarative way to describe a component hierarchy
Summary: `Element` is an abstraction layer that allows describing component hierarchy in a declarative way. Creating `Element`s themself does not create a component tree (aka `ShadowNode` tree) but describes some hierarchical structure that might be used to build an actual component tree (similar to XML Elements). `Element<>` provides some basic type-safety guarantees: all modifications of element objects require using objects (such as Props or State) of compatible type. For now, the only useful application of that is building tests. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D19512392 fbshipit-source-id: eb0711c2a537865fa5454dbede53412a135058cf
This commit is contained in:
Родитель
383934a06e
Коммит
6a1438c044
|
@ -0,0 +1,81 @@
|
|||
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_debug_preprocessor_flags")
|
||||
load(
|
||||
"//tools/build_defs/oss:rn_defs.bzl",
|
||||
"ANDROID",
|
||||
"APPLE",
|
||||
"CXX",
|
||||
"fb_xplat_cxx_test",
|
||||
"get_apple_compiler_flags",
|
||||
"get_apple_inspector_flags",
|
||||
"react_native_xplat_target",
|
||||
"rn_xplat_cxx_library",
|
||||
"subdir_glob",
|
||||
)
|
||||
|
||||
APPLE_COMPILER_FLAGS = get_apple_compiler_flags()
|
||||
|
||||
rn_xplat_cxx_library(
|
||||
name = "element",
|
||||
srcs = glob(
|
||||
["**/*.cpp"],
|
||||
exclude = glob(["tests/**/*.cpp"]),
|
||||
),
|
||||
headers = glob(
|
||||
["**/*.h"],
|
||||
exclude = glob(["tests/**/*.h"]),
|
||||
),
|
||||
header_namespace = "",
|
||||
exported_headers = subdir_glob(
|
||||
[
|
||||
("", "*.h"),
|
||||
],
|
||||
prefix = "react/element",
|
||||
),
|
||||
compiler_flags = [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-std=c++14",
|
||||
"-Wall",
|
||||
],
|
||||
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
|
||||
fbobjc_labels = ["supermodule:ios/isolation/infra.react_native"],
|
||||
fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(),
|
||||
force_static = True,
|
||||
macosx_tests_override = [],
|
||||
platforms = (ANDROID, APPLE, CXX),
|
||||
preprocessor_flags = [
|
||||
"-DLOG_TAG=\"ReactNative\"",
|
||||
# Systraces are temporary disabled.
|
||||
# "-DWITH_FBSYSTRACE=1",
|
||||
],
|
||||
tests = [":tests"],
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
"fbsource//xplat/fbsystrace:fbsystrace",
|
||||
react_native_xplat_target("fabric/components/view:view"),
|
||||
react_native_xplat_target("fabric/uimanager:uimanager"),
|
||||
react_native_xplat_target("fabric/core:core"),
|
||||
react_native_xplat_target("fabric/debug:debug"),
|
||||
react_native_xplat_target("utils:utils"),
|
||||
],
|
||||
)
|
||||
|
||||
fb_xplat_cxx_test(
|
||||
name = "tests",
|
||||
srcs = glob(["tests/**/*.cpp"]),
|
||||
headers = glob(["tests/**/*.h"]),
|
||||
compiler_flags = [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-std=c++14",
|
||||
"-Wall",
|
||||
],
|
||||
contacts = ["oncall+react_native@xmail.facebook.com"],
|
||||
platforms = (ANDROID, APPLE, CXX),
|
||||
deps = [
|
||||
"fbsource//xplat/third-party/gmock:gtest",
|
||||
":element",
|
||||
react_native_xplat_target("fabric/components/root:root"),
|
||||
react_native_xplat_target("fabric/components/view:view"),
|
||||
],
|
||||
)
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ComponentBuilder.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
ComponentBuilder::ComponentBuilder(
|
||||
ComponentDescriptorRegistry::Shared const &componentDescriptorRegistry)
|
||||
: componentDescriptorRegistry_(componentDescriptorRegistry){};
|
||||
|
||||
ShadowNode::Shared ComponentBuilder::build(
|
||||
ElementFragment const &elementFragment) const {
|
||||
auto &componentDescriptor =
|
||||
componentDescriptorRegistry_->at(elementFragment.componentHandle);
|
||||
|
||||
auto children = ShadowNode::ListOfShared{};
|
||||
children.reserve(elementFragment.children.size());
|
||||
for (auto const &childFragment : elementFragment.children) {
|
||||
children.push_back(build(childFragment));
|
||||
}
|
||||
|
||||
auto eventEmitter =
|
||||
componentDescriptor.createEventEmitter(nullptr, elementFragment.tag);
|
||||
|
||||
auto shadowNode = componentDescriptor.createShadowNode(
|
||||
ShadowNodeFragment{
|
||||
elementFragment.props,
|
||||
std::make_shared<ShadowNode::ListOfShared const>(children),
|
||||
elementFragment.state},
|
||||
ShadowNodeFamilyFragment{
|
||||
elementFragment.tag, elementFragment.surfaceId, eventEmitter});
|
||||
|
||||
if (elementFragment.referenceCallback) {
|
||||
elementFragment.referenceCallback(shadowNode);
|
||||
}
|
||||
|
||||
if (elementFragment.finalizeCallback) {
|
||||
elementFragment.finalizeCallback(const_cast<ShadowNode &>(*shadowNode));
|
||||
}
|
||||
|
||||
return shadowNode;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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 <memory>
|
||||
|
||||
#include <react/core/ComponentDescriptor.h>
|
||||
#include <react/core/ShadowNode.h>
|
||||
#include <react/core/ShadowNodeFamilyFragment.h>
|
||||
#include <react/core/ShadowNodeFragment.h>
|
||||
#include <react/uimanager/ComponentDescriptorRegistry.h>
|
||||
|
||||
#include <react/element/Element.h>
|
||||
#include <react/element/ElementFragment.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* Build `ShadowNode` trees with a given given `Element` trees.
|
||||
*/
|
||||
class ComponentBuilder final {
|
||||
public:
|
||||
ComponentBuilder(
|
||||
ComponentDescriptorRegistry::Shared const &componentDescriptorRegistry);
|
||||
|
||||
/*
|
||||
* Builds a `ShadowNode` tree with given `Element` tree using stored
|
||||
* `ComponentDescriptorRegistry`.
|
||||
*/
|
||||
template <typename ShadowNodeT>
|
||||
std::shared_ptr<ShadowNodeT const> build(Element<ShadowNodeT> element) const {
|
||||
return std::static_pointer_cast<ShadowNodeT const>(
|
||||
build(element.fragment_));
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
* Internal, type-erased version of `build`.
|
||||
*/
|
||||
ShadowNode::Shared build(ElementFragment const &elementFragment) const;
|
||||
|
||||
ComponentDescriptorRegistry::Shared componentDescriptorRegistry_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "Element.h"
|
||||
|
||||
// Intentionally empty.
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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 <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <react/core/ShadowNode.h>
|
||||
|
||||
#include <react/element/ElementFragment.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* `Element<>` is an abstraction layer that allows describing component
|
||||
* hierarchy in a declarative way. Creating `Element`s themself does not create
|
||||
* a component tree (aka `ShadowNode` tree) but describes some hierarchical
|
||||
* structure that might be used to build an actual component tree (similar to
|
||||
* XML Elements).
|
||||
* `Element` provides some basic type-safety guarantees: all modifications
|
||||
* of element objects require using objects (such as Props or State) of
|
||||
* compatible type.
|
||||
*/
|
||||
template <typename ShadowNodeT>
|
||||
class Element final {
|
||||
public:
|
||||
using ConcreteProps = typename ShadowNodeT::ConcreteProps;
|
||||
using SharedConcreteProps = typename ShadowNodeT::SharedConcreteProps;
|
||||
using ConcreteEventEmitter = typename ShadowNodeT::ConcreteEventEmitter;
|
||||
using ConcreteShadowNode = ShadowNodeT;
|
||||
using ConcreteSharedShadowNode = std::shared_ptr<ConcreteShadowNode const>;
|
||||
|
||||
using ConcreteReferenceCallback =
|
||||
std::function<void(std::shared_ptr<ShadowNodeT const> const &shadowNode)>;
|
||||
|
||||
/*
|
||||
* Constructs an `Element`.
|
||||
*/
|
||||
Element() {
|
||||
fragment_.componentHandle = ShadowNodeT::Handle();
|
||||
fragment_.componentName = ShadowNodeT::Name();
|
||||
fragment_.props = ShadowNodeT::defaultSharedProps();
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets `tag`.
|
||||
*/
|
||||
Element &tag(Tag tag) {
|
||||
fragment_.tag = tag;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets `surfaceId`.
|
||||
*/
|
||||
Element &surfaceId(SurfaceId surfaceId) {
|
||||
fragment_.surfaceId = surfaceId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets `props`.
|
||||
*/
|
||||
Element &props(SharedConcreteProps props) {
|
||||
fragment_.props = props;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets `props` using callback.
|
||||
*/
|
||||
Element &props(std::function<SharedConcreteProps()> callback) {
|
||||
fragment_.props = callback();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets children.
|
||||
*/
|
||||
Element &children(std::vector<Element> children) {
|
||||
auto fragments = ElementFragment::List{};
|
||||
fragments.reserve(children.size());
|
||||
for (auto const &child : children) {
|
||||
fragments.push_back(child.fragment_);
|
||||
}
|
||||
fragment_.children = fragments;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls the callback during component construction with a pointer to the
|
||||
* component which is being constructed.
|
||||
*/
|
||||
Element &reference(
|
||||
std::function<void(ConcreteSharedShadowNode const &shadowNode)>
|
||||
callback) {
|
||||
fragment_.referenceCallback = callback;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* During component construction, assigns a given pointer to a component
|
||||
* that is being constructed.
|
||||
*/
|
||||
Element &reference(ConcreteSharedShadowNode &inShadowNode) {
|
||||
fragment_.referenceCallback = [&](ShadowNode::Shared const &shadowNode) {
|
||||
inShadowNode =
|
||||
std::static_pointer_cast<ConcreteShadowNode const>(shadowNode);
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls the callback with a reference to a just constructed component.
|
||||
*/
|
||||
Element &finalize(
|
||||
std::function<void(ConcreteShadowNode &shadowNode)> finalizeCallback) {
|
||||
fragment_.finalizeCallback = [=](ShadowNode &shadowNode) {
|
||||
return finalizeCallback(static_cast<ConcreteShadowNode &>(shadowNode));
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ComponentBuilder;
|
||||
ElementFragment fragment_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ElementFragment.h"
|
||||
|
||||
// Intentionally empty.
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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 <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <react/core/ShadowNode.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* This is an implementation detail, do not use it directly.
|
||||
* A type-erased version of `Element<>`.
|
||||
* `ElementFragment` carries all information that is stored inside `Element<>`
|
||||
* in some generalized, type-erased manner.
|
||||
*/
|
||||
class ElementFragment final {
|
||||
public:
|
||||
using Shared = std::shared_ptr<ElementFragment>;
|
||||
using List = std::vector<ElementFragment>;
|
||||
using ListOfShared = std::vector<Shared>;
|
||||
using ReferenceCallback =
|
||||
std::function<void(ShadowNode::Shared const &shadowNode)>;
|
||||
using FinalizeCallback = std::function<void(ShadowNode &shadowNode)>;
|
||||
|
||||
/*
|
||||
* ComponentDescriptor part (describes the type)
|
||||
*/
|
||||
ComponentHandle componentHandle;
|
||||
ComponentName componentName;
|
||||
|
||||
/*
|
||||
* ShadowNodeFamily part (describes the family)
|
||||
*/
|
||||
Tag tag;
|
||||
SurfaceId surfaceId;
|
||||
|
||||
/*
|
||||
* ShadowNode part (describes the instance)
|
||||
*/
|
||||
Props::Shared props;
|
||||
State::Shared state;
|
||||
List children;
|
||||
|
||||
/*
|
||||
* Other
|
||||
*/
|
||||
ReferenceCallback referenceCallback;
|
||||
FinalizeCallback finalizeCallback;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <react/components/view/ViewComponentDescriptor.h>
|
||||
#include <react/element/ComponentBuilder.h>
|
||||
#include <react/element/Element.h>
|
||||
#include <react/uimanager/ComponentDescriptorProviderRegistry.h>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
TEST(ElementTest, testNormalCases) {
|
||||
ComponentDescriptorProviderRegistry componentDescriptorProviderRegistry{};
|
||||
auto eventDispatcher = EventDispatcher::Shared{};
|
||||
auto componentDescriptorRegistry =
|
||||
componentDescriptorProviderRegistry.createComponentDescriptorRegistry(
|
||||
ComponentDescriptorParameters{eventDispatcher, nullptr, nullptr});
|
||||
|
||||
componentDescriptorProviderRegistry.add(
|
||||
concreteComponentDescriptorProvider<ViewComponentDescriptor>());
|
||||
|
||||
auto builder = ComponentBuilder{componentDescriptorRegistry};
|
||||
|
||||
auto shadowNodeA = std::shared_ptr<ViewShadowNode const>{};
|
||||
auto shadowNodeAA = std::shared_ptr<ViewShadowNode const>{};
|
||||
auto shadowNodeAB = std::shared_ptr<ViewShadowNode const>{};
|
||||
auto shadowNodeABA = std::shared_ptr<ViewShadowNode const>{};
|
||||
|
||||
auto propsAA = std::make_shared<ViewProps const>();
|
||||
const_cast<std::string &>(propsAA->nativeId) = "node AA";
|
||||
|
||||
// clang-format off
|
||||
auto element =
|
||||
Element<ViewShadowNode>()
|
||||
.reference(shadowNodeA)
|
||||
.tag(1)
|
||||
.props([]() {
|
||||
auto props = std::make_shared<ViewProps const>();
|
||||
const_cast<int &>(props->zIndex) = 42;
|
||||
const_cast<std::string &>(props->nativeId) = "node A";
|
||||
return props;
|
||||
})
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
shadowNode.sealRecursive();
|
||||
})
|
||||
.children({
|
||||
Element<ViewShadowNode>()
|
||||
.reference(shadowNodeAA)
|
||||
.tag(2)
|
||||
.props(propsAA),
|
||||
Element<ViewShadowNode>()
|
||||
.reference(shadowNodeAB)
|
||||
.tag(3)
|
||||
.props([]() {
|
||||
auto props = std::make_shared<ViewProps const>();
|
||||
const_cast<std::string &>(props->nativeId) = "node AB";
|
||||
return props;
|
||||
})
|
||||
.children({
|
||||
Element<ViewShadowNode>()
|
||||
.reference(shadowNodeABA)
|
||||
.tag(4)
|
||||
.props([]() {
|
||||
auto props = std::make_shared<ViewProps const>();
|
||||
const_cast<std::string &>(props->nativeId) = "node ABA";
|
||||
return props;
|
||||
})
|
||||
})
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
auto shadowNode = builder.build(element);
|
||||
|
||||
EXPECT_EQ(shadowNode, shadowNodeA);
|
||||
|
||||
// Tags
|
||||
EXPECT_EQ(shadowNodeA->getTag(), 1);
|
||||
EXPECT_EQ(shadowNodeAA->getTag(), 2);
|
||||
EXPECT_EQ(shadowNodeAB->getTag(), 3);
|
||||
EXPECT_EQ(shadowNodeABA->getTag(), 4);
|
||||
|
||||
// Children
|
||||
EXPECT_EQ(shadowNodeA->getChildren().size(), 2);
|
||||
EXPECT_EQ(shadowNodeAA->getChildren().size(), 0);
|
||||
EXPECT_EQ(shadowNodeAB->getChildren().size(), 1);
|
||||
EXPECT_EQ(shadowNodeABA->getChildren().size(), 0);
|
||||
EXPECT_EQ(
|
||||
shadowNodeA->getChildren(),
|
||||
(ShadowNode::ListOfShared{shadowNodeAA, shadowNodeAB}));
|
||||
EXPECT_EQ(
|
||||
shadowNodeAB->getChildren(), (ShadowNode::ListOfShared{shadowNodeABA}));
|
||||
|
||||
// Props
|
||||
EXPECT_EQ(shadowNodeA->getProps()->nativeId, "node A");
|
||||
EXPECT_EQ(shadowNodeABA->getProps()->nativeId, "node ABA");
|
||||
EXPECT_EQ(shadowNodeAA->getProps(), propsAA);
|
||||
|
||||
// Finalize
|
||||
EXPECT_TRUE(shadowNodeA->getSealed());
|
||||
EXPECT_TRUE(shadowNodeAA->getSealed());
|
||||
EXPECT_TRUE(shadowNodeAB->getSealed());
|
||||
EXPECT_TRUE(shadowNodeABA->getSealed());
|
||||
}
|
Загрузка…
Ссылка в новой задаче