Skip to content

Commit

Permalink
Fabric: Element<>, a declarative way to describe a component hierarchy
Browse files Browse the repository at this point in the history
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
  • Loading branch information
shergin authored and facebook-github-bot committed Jan 22, 2020
1 parent 383934a commit 6a1438c
Show file tree
Hide file tree
Showing 8 changed files with 511 additions and 0 deletions.
81 changes: 81 additions & 0 deletions ReactCommon/fabric/element/BUCK
Original file line number Diff line number Diff line change
@@ -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"),
],
)
51 changes: 51 additions & 0 deletions ReactCommon/fabric/element/ComponentBuilder.cpp
Original file line number Diff line number Diff line change
@@ -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
52 changes: 52 additions & 0 deletions ReactCommon/fabric/element/ComponentBuilder.h
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions ReactCommon/fabric/element/Element.cpp
Original file line number Diff line number Diff line change
@@ -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.
136 changes: 136 additions & 0 deletions ReactCommon/fabric/element/Element.h
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions ReactCommon/fabric/element/ElementFragment.cpp
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit 6a1438c

Please sign in to comment.