Есть ли способ рассматривать компоненты реакции как строки?

#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>