mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Merge RawText sequences (#47278)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47278 When we have multiple spans of text inside a <Text> element, React will emit these as separate RawText ShadowNodes. RawText shadow nodes cannot have any properties beyond the text they contain, yet our current AttributedText logic will generate a separate span for each and duplicate all the relevant properties. This can be particularly inefficient when JSX is used to interpolate strings, e.g. `<Text styles={styles.text}>Example {i}/{count}</Text>` results in 4 raw text elements with duplicated properties. Changelog: [General][Changed] Improved AttributedText generation for raw text nodes. Reviewed By: NickGerleman Differential Revision: D65134912 fbshipit-source-id: dba4296ebe9987e989760cdee66f1e076fbb7a98
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ee7b4e2763
commit
2f7957f2fd
+18
-9
@@ -29,24 +29,33 @@ void BaseTextShadowNode::buildAttributedString(
|
||||
const ShadowNode& parentNode,
|
||||
AttributedString& outAttributedString,
|
||||
Attachments& outAttachments) {
|
||||
bool lastFragmentWasRawText = false;
|
||||
for (const auto& childNode : parentNode.getChildren()) {
|
||||
// RawShadowNode
|
||||
auto rawTextShadowNode =
|
||||
dynamic_cast<const RawTextShadowNode*>(childNode.get());
|
||||
if (rawTextShadowNode != nullptr) {
|
||||
auto fragment = AttributedString::Fragment{};
|
||||
fragment.string = rawTextShadowNode->getConcreteProps().text;
|
||||
fragment.textAttributes = baseTextAttributes;
|
||||
const auto& rawText = rawTextShadowNode->getConcreteProps().text;
|
||||
if (lastFragmentWasRawText) {
|
||||
outAttributedString.getFragments().back().string += rawText;
|
||||
} else {
|
||||
auto fragment = AttributedString::Fragment{};
|
||||
fragment.string = rawText;
|
||||
fragment.textAttributes = baseTextAttributes;
|
||||
|
||||
// Storing a retaining pointer to `ParagraphShadowNode` inside
|
||||
// `attributedString` causes a retain cycle (besides that fact that we
|
||||
// don't need it at all). Storing a `ShadowView` instance instead of
|
||||
// `ShadowNode` should properly fix this problem.
|
||||
fragment.parentShadowView = shadowViewFromShadowNode(parentNode);
|
||||
outAttributedString.appendFragment(fragment);
|
||||
// Storing a retaining pointer to `ParagraphShadowNode` inside
|
||||
// `attributedString` causes a retain cycle (besides that fact that we
|
||||
// don't need it at all). Storing a `ShadowView` instance instead of
|
||||
// `ShadowNode` should properly fix this problem.
|
||||
fragment.parentShadowView = shadowViewFromShadowNode(parentNode);
|
||||
outAttributedString.appendFragment(fragment);
|
||||
lastFragmentWasRawText = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
lastFragmentWasRawText = false;
|
||||
|
||||
// TextShadowNode
|
||||
auto textShadowNode = dynamic_cast<const TextShadowNode*>(childNode.get());
|
||||
if (textShadowNode != nullptr) {
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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/PropsParserContext.h>
|
||||
#include <react/renderer/element/ComponentBuilder.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <react/renderer/element/Element.h>
|
||||
#include <react/renderer/element/testUtils.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
namespace {
|
||||
|
||||
Element<RawTextShadowNode> rawTextElement(const char* text) {
|
||||
auto rawTextProps = std::make_shared<RawTextProps>();
|
||||
rawTextProps->text = text;
|
||||
return Element<RawTextShadowNode>().props(rawTextProps);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BaseTextShadowNodeTest, fragmentsWithDifferentAttributes) {
|
||||
ContextContainer contextContainer{};
|
||||
PropsParserContext parserContext{-1, contextContainer};
|
||||
|
||||
auto builder = simpleComponentBuilder();
|
||||
auto shadowNode = builder.build(Element<ParagraphShadowNode>().children({
|
||||
Element<TextShadowNode>()
|
||||
.props([]() {
|
||||
auto props = std::make_shared<TextProps>();
|
||||
props->textAttributes.fontSize = 12;
|
||||
return props;
|
||||
})
|
||||
.children({
|
||||
rawTextElement("First fragment. "),
|
||||
}),
|
||||
Element<TextShadowNode>()
|
||||
.props([]() {
|
||||
auto props = std::make_shared<TextProps>();
|
||||
props->textAttributes.fontSize = 24;
|
||||
return props;
|
||||
})
|
||||
.children({
|
||||
rawTextElement("Second fragment"),
|
||||
}),
|
||||
}));
|
||||
|
||||
auto baseTextAttributes = TextAttributes::defaultTextAttributes();
|
||||
AttributedString output;
|
||||
BaseTextShadowNode::Attachments attachments;
|
||||
BaseTextShadowNode::buildAttributedString(
|
||||
baseTextAttributes, *shadowNode, output, attachments);
|
||||
|
||||
EXPECT_EQ(output.getString(), "First fragment. Second fragment");
|
||||
|
||||
const auto& fragments = output.getFragments();
|
||||
EXPECT_EQ(fragments.size(), 2);
|
||||
EXPECT_EQ(fragments[0].textAttributes.fontSize, 12);
|
||||
EXPECT_EQ(
|
||||
fragments[0].parentShadowView.tag,
|
||||
shadowNode->getChildren()[0]->getTag());
|
||||
EXPECT_EQ(fragments[1].textAttributes.fontSize, 24);
|
||||
EXPECT_EQ(
|
||||
fragments[1].parentShadowView.tag,
|
||||
shadowNode->getChildren()[1]->getTag());
|
||||
}
|
||||
|
||||
TEST(BaseTextShadowNodeTest, rawTextIsMerged) {
|
||||
ContextContainer contextContainer{};
|
||||
PropsParserContext parserContext{-1, contextContainer};
|
||||
|
||||
auto builder = simpleComponentBuilder();
|
||||
auto shadowNode = builder.build(Element<TextShadowNode>().children({
|
||||
rawTextElement("Hello "),
|
||||
rawTextElement("World"),
|
||||
}));
|
||||
|
||||
auto baseTextAttributes = TextAttributes::defaultTextAttributes();
|
||||
AttributedString output;
|
||||
BaseTextShadowNode::Attachments attachments;
|
||||
BaseTextShadowNode::buildAttributedString(
|
||||
baseTextAttributes, *shadowNode, output, attachments);
|
||||
|
||||
EXPECT_EQ(output.getString(), "Hello World");
|
||||
EXPECT_EQ(output.getFragments().size(), 1);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
Reference in New Issue
Block a user