#reactjs #string #typescript
Вопрос:
Я работаю над приложением React with TypeScript. Я использую необходимую библиотеку, в которой есть компонент, который принимает свойство text и отображает его в виде SVG. Мы назовем этот библиотечный компонент <LibraryRenderer />
.
Я завернул этот компонент в другой компонент <Renderer />
, который передает дочерние элементы (в виде текста) в компонент библиотеки следующим образом:
function Renderer(props: { children: string }) {
return <LibraryRenderer text={props.children} />;
}
Теперь я хотел бы создать компонент, который преобразует текст внутри него в другую строку и использует в <Renderer />
компоненте. Например, этот новый компонент <Wrap />
может окружать существующий текст "BEGIN"
и "END"
соответственно. Вот что-то вроде того, как я хотел бы, чтобы код выглядел:
function Wrap(props: { children: string }) {
return `BEGIN ${props.children} END`;
}
Затем вы сможете использовать эти компоненты в такой иерархии, как эта:
<Renderer>
<Wrap>
<Wrap>
Some Inner Text
</Wrap>
</Wrap>
</Renderer>
И я бы хотел, чтобы это свелось к:
<LibraryRenderer text={"BEGIN BEGIN Some Inner Text END END"} />
Я мог бы написать компоненты преобразования текста как обычные старые функции, но, похоже, это лишило бы некоторую приятную гибкость синтаксиса JSX. Кроме того, если бы у меня когда-либо были реквизиты или состояние внутри любого из этих компонентов преобразования текста, я бы не смог использовать React для управления ими.
function Wrap(value: string) {
return `BEGIN ${props.children} END`;
}
<Renderer>
{Wrap(
Wrap(
"Some Inner Text"
)
)}
</Renderer>
Я чувствую, что это может быть не совсем правильное использование React, но я думаю, что это зависит от того, возможно ли это и является ли код «хакерским» или нет. Любая помощь в том, как я должен двигаться вперед, будет очень признательна!
Комментарии:
1. Я думаю, что использование JSX для создания завернутого текста возможно и не является хакерским. Пожалуйста, взгляните на следующую демонстрационную версию. Если это работает для вас, я могу добавить ответ, чтобы объяснить код.
2. @AWolf, ваша демо-версия работает для того
LibraryRenderer
, чтобы принимать элементы какtext
. Но, в моем случае,LibraryRenderer
нужно строго приниматьstring
только ценности.3. Хорошо, я обновил код. Теперь
input
для созданияtext
опоры, которая является значением, используется новая опора, называемаяstring
опорой.4. @AWolf, хорошо. Я могу адаптировать вашу демонстрационную версию для своих целей. Не стесняйтесь добавлять его в качестве ответа.
Ответ №1:
Можно сгенерировать текст с Wrap
помощью компонента.
Разметка в вашем методе визуализации приложения будет выглядеть следующим образом:
return (
<Renderer>
<Wrap>
<Wrap>Hello world</Wrap>
</Wrap>
</Renderer>
);
Это приведет к созданию текста BEGIN BEGIN Hello world END END
внутри Renderer
компонента.
Для Wrap
компонента мы используем a span
-тег, чтобы обернуть наш текст следующим кодом:
const Wrap: FunctionComponent = ({ children }) => {
return <span style={{ border: "1px solid red" }}>BEGIN {children} END</span>;
};
Renderer
Компонент получает текст с компонентами в качестве дочерних элементов со следующим кодом ( LibrarayRenderer
будет объяснено позже):
const Renderer: FunctionComponent = ({ children }) => {
const [finalText, setText] = useState("");
return (
<div>
<LibraryRenderer text={finalText} input={children} setText={setText} />
<p>Generated text: {finalText}</p>
</div>
);
};
Как вы можете видеть, мы создаем переменную состояния finalText
для нашего текста, которая будет сгенерирована нашим LibraryRenderer
компонентом.
input
Реквизит-это дети с завернутым текстом, и setText
он передается, чтобы мы могли обновить finalText
внутреннюю часть средства визуализации.
Он finalText
также передается в LibraryRender
, поскольку это необходимо там позже (Примечание: он будет доступен при втором рендеринге, потому что при первом рендеринге он не готов).
Как создать текст с разметкой JSX?
Теперь мы готовы сгенерировать LibraryRenderer
то, что создаст текст для нас.
type LibraryRendererProps = {
text: string;
input: ReactNode;
setText: Dispatch<SetStateAction<string>>;
};
const LibraryRenderer: FunctionComponent<LibraryRendererProps> = ({
text,
input,
setText
}) => {
const ref = useRef<HTMLHeadingElement>(null);
useEffect(() => {
setText(ref?.current?.innerText || "");
}, [ref, setText]);
console.log("text", text); // needed for renderer as string
// hiding the h1 with css because we're just using it to generate the text
return (
<h1 ref={ref} style={{ display: "none" }}>
{input}
</h1>
);
};
Мы возвращаем заголовок (любой другой встроенный элемент будет работать) со display: "none"
стилем и ссылкой. CSS просто используется, поэтому он не будет отображаться в DOM. Ссылка необходима, чтобы получить текст позже. Это {input}
сделает наших детей с завернутым текстом.
Теперь мы должны выполнить a useEffect
с параметром ref
as array — так что это useEffect
будет выполняться, если ссылка изменится. Внутри него мы устанавливаем значение finalText
для innerText
нашего отображаемого HTML с помощью нашего setText
метода. HTML в DOM выглядит так:
<h1 style="display: none;">
<span style="border: 1px solid red;">BEGIN <span style="border: 1px solid red;">BEGIN Hello world END</span> END</span>
</h1>
С innerText
помощью мы получаем каждый текст внутри h1
тега -. Стиль границы на span
нем предназначен только для отладки (если display: none
он удален).
Итак, теперь у нас есть текст BEGIN BEGIN Hello world END END
.
Печатание
Я использовал FunctionComponent для типа наших компонентов, поэтому children
там определена поддержка. Возможно, вы видели SFC
тип в других примерах, но это устарело, потому что компоненты функций React больше не считаются используемыми FunctionComponent
без сохранения состояния.
Если вы хотите добавить больше типов, вы можете добавить их в FunctionComponent
качестве универсального типа, как в приведенном выше фрагменте с FunctionComponent<LibraryRendererProps>
Тип LibraryRendererProps
:
input: ReactNode;
тот же набор текста, что и у детейtext: string;
наш сгенерированный текст в видеstring
setText: Dispatch<SetStateAction<string>>;
используетReact.Dispatch
иSetStateAction
вводит сsetState
типом в виде строки
ДЕМОНСТРАЦИЯ
Вы можете найти полный код ниже (без ввода) или в следующем поле Codesandbox с машинописным текстом.
const { useState, useRef, useEffect } = React;
const LibraryRenderer = ({ text, input, setText }) => {
const ref = useRef(null);
useEffect(() => {
setText(ref.current.innerText || "");
}, [ref, setText]);
console.log("text", text); // needed for renderer as string
// hiding the h1 with css because we're just using it to generate the text
return (
<h1
ref={ref}
style={{
display: "none",
}}
>{input}
</h1>
);
};
const Renderer = ({ children }) => {
const [finalText, setText] = useState("");
return (
<div>
<LibraryRenderer text={finalText} input={children} setText={setText} />
<p>Generated text: {finalText}</p>
</div>
);
};
const Wrap = ({ children }) => {
return (
<span
style={{
border: "1px solid red",
}}
> BEGIN {children} END
</span>
);
};
function App() {
return (
<div className="App">
<Renderer>
<Wrap>
<Wrap>Hello world</Wrap>
</Wrap>
</Renderer>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
body {
font-family: Arial, sans-serif;
}
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
Ответ №2:
React используется для создания пользовательского интерфейса, но в вашем случае вы работаете со строкой, поэтому использование чистых функций полностью имеет смысл.
В последнем примере я сделаю что-то вроде
const generatedText = wrap(wrap("Some Inner Text"))
return <Renderer>{generatedText}</Renderer>