如何在 Vue.js 中使用组件动态包装子字符串? [英] How can I dynamically wrap a substring with a component in Vue.js?

查看:27
本文介绍了如何在 Vue.js 中使用组件动态包装子字符串?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想做什么

给定一个来自用户输入的字符串,我试图用包裹在组件中的特定子字符串来呈现这个字符串.在我的特殊情况下,匹配的子字符串是匹配正则表达式模式的日期,应该包装它的组件是

以上是我目前取得的成绩的截图.textarea 的输入在其下方呈现,某些子字符串包含在 Vuetify 的芯片组件中.以上是通过用 HTML 替换正则表达式模式匹配的子字符串来呈现组件并将此字符串提供给 v-html 指令进行呈现来实现的.下面是一些代码,显示了这是如何完成的.

<div style="line-height:40px;"v-html="messageOutput"></div>

let finalStr = ''let str = '你的空调号码.XXXXXXXXXX85 记入 15-11-17.让 dateReg =/((?=\d{4})\d{4}|(?=[a-zA-Z]{3})[a-zA-Z]{3}|\d{2})((?=\/)\/|-)((?=[0-9]{2})[0-9]{2}|(?=[0-9]{1,2})[0-9]{1,2}|[a-zA-Z]{3})((?=\/)\/|-)((?=[0-9]{4})[0-9]{4}|(?=[0-9]{2})[0-9]{2}|[a-zA-Z]{3})/const date = dateReg.exec(str)finalStr = str.replace(date[0], `<div class="md-chip md-chip-clickable"><div class="md-chip-icon primary"><i class="material-icons">date_range</i></div>${日期[0]}

`)

什么不起作用

问题是使用自定义组件而不是纯 HTML 没有提供预期的输出.样式不会呈现,组件不会对事件做出反应.

如何在 Vue.js 中使用组件动态包装子字符串?

解决方案

问题

自定义组件无法按预期工作的问题源于尝试将它们包含在 v-html 指令中.由于 v-html 指令的值被插入为 plain HTML,通过设置元素的 innerHTML,数据和事件不会被动绑定.

<块引用>

请注意,您不能使用 v-html 来组合模板部分,因为 Vue 不是基于字符串的模板引擎.相反,组件更适合作为 UI 重用和组合的基本单元.

插入原始 HTML 的 Vue 指南

<块引用>

[v-html updates] 元素的 innerHTML.请注意,内容是作为纯 HTML 插入的 - 它们不会被编译为 Vue 模板.如果您发现自己尝试使用 v-html 组合模板,请尝试重新考虑改用组件来解决.

关于 v-html 指令的 Vue API 文档

解决方案

组件是 UI 重用和组合的基本单元.我们现在必须构建一个组件,该组件能够识别特定的子字符串并将组件包裹在它们周围.Vue 的组件/模板和指令本身无法处理这个任务——这是不可能的.然而,Vue 确实提供了一种通过渲染函数在较低级别构建组件的方法.

使用渲染函数,我们可以接受一个字符串作为道具,对它进行标记化并构建一个包含包裹在组件中的匹配子字符串的视图.以下是这种解决方案的简单实现:

const Chip = {模板:`<div class="chip"><插槽></插槽>

`,};const SmartRenderer = {道具: ['细绳',],渲染(创建元素){const TOKEN_DELIMITER_REGEX =/(\s+)/;const 令牌 = this.string.split(TOKEN_DELIMITER_REGEX);const children = tokens.reduce((acc, token) => {if (token === 'foo') return [...acc, createElement(Chip, token)];返回 [...acc, 令牌];}, []);return createElement('div', children);},};常量智能输入 = {成分: {智能渲染器},数据:() =>({价值: '',}),模板:`<div class="smart-input"><文本区域类=输入"v-model="值"></textarea><SmartRenderer :string="value"/>

`,};新的 Vue({el: '#root',成分: {智能输入,},模板:`<智能输入/>`,数据:() =>({}),});

.chip {显示:内联块;字体粗细:粗体;}.smart-input .input {字体:继承;调整大小:垂直;}.smart-input .output {溢出包装:断字;断字:断断续续;}

<头><meta charset="utf-8"><meta name="viewport" content="width=device-width"><link rel="stylesheet" href="https://unpkg.com/milligram@1.3.0/dist/milligram.css"><身体><p>在下面开始输入.在某些时候包括 <strong>foo</strong>,与其他单词分开,至少有一个空格字符.</p><div id="root"></div><script src="https://unpkg.com/vue@2.5.8/dist/vue.min.js"></script></html>

有一个 SmartRenderer 组件,它通过一个 prop 接受一个 string.在渲染函数中,我们:

  1. 通过用空格字符分割字符串来对字符串进行标记.
  2. 通过迭代每个标记来构建要呈现的元素数组,然后

    1. 检查令牌是否与规则匹配(在我们的原始实现中查看字符串是否与 foo 匹配)并将其包装在一个组件中(在我们的原始实现中,组件是一个 Chip,这只会使 foo bold) 否则保持原样.
    2. 在数组中累积每次迭代的结果.

  3. 然后将 Step 3 的数组作为要创建的 div 元素的子元素传递给 createElement.

render(createElement) {const TOKEN_DELIMITER_REGEX =/(\s+)/;const 令牌 = this.string.split(TOKEN_DELIMITER_REGEX);const children = tokens.reduce((acc, token) => {if (token === 'foo') return [...acc, createElement(Chip, token)];返回 [...acc, 令牌];}, []);return createElement('div', children);},

createElement 将 HTML 标记名称、组件选项(或函数)作为其第一个参数,在我们的示例中,第二个参数需要一个或多个子项来呈现.您可以在文档<中阅读有关createElement的更多信息/a>.

发布的解决方案有一些未解决的问题,例如:

它检查令牌是否需要包装以及如何包装它的方式也很幼稚——它只是一个带有硬编码的包装组件的 if 语句.您可以实现一个名为 rules 的道具,它是一个对象数组,指定要测试的规则和如果测试通过则将令牌包装在其中的组件.但是,此解决方案应该足以让您入门.

进一步阅读

What am I trying to do

Given a string from a user input, I am attempting to render this string with particular substrings wrapped in a component. In my particular case the substring being matched is a date that matches a regex pattern and the component that is supposed to wrap it is a chip from Vuetify.

What I have done

Above is a screenshot of what I have achieved so far. The input of the textarea is rendered below it with certain substrings wrapped in the chip component from Vuetify. The above was achieved by replacing the substring matched by a regex pattern with the HTML to render the component and giving this string to a v-html directive for rendering. Below is some code showing how this was done.

<div style="line-height:40px;" v-html="messageOutput"></div>

let finalStr = ''
let str = 'Your a/c no. XXXXXXXXXX85 is credited on 15-11-17.'

let dateReg = /((?=\d{4})\d{4}|(?=[a-zA-Z]{3})[a-zA-Z]{3}|\d{2})((?=\/)\/|-)((?=[0-9]{2})[0-9]{2}|(?=[0-9]{1,2})[0-9]{1,2}|[a-zA-Z]{3})((?=\/)\/|-)((?=[0-9]{4})[0-9]{4}|(?=[0-9]{2})[0-9]{2}|[a-zA-Z]{3})/

const date = dateReg.exec(str)
finalStr = str.replace(date[0], `
 <div class="md-chip md-chip-clickable">
  <div class="md-chip-icon primary"><i class="material-icons">date_range</i></div>
   ${date[0]}
 </div>
`)

What is not working

The problem is using custom components as opposed to plain HTML does not give the expected output. The styling is not rendered and the component does not react to events.

How can I dynamically wrap a substring with a component in Vue.js?

解决方案

Problem

The problem of custom components not working as expected stems from the attempt of including them inside a v-html directive. Due to the value of the v-html directive being inserted as plain HTML, by setting an element's innerHTML, data and events are not reactively bound.

Note that you cannot use v-html to compose template partials, because Vue is not a string-based templating engine. Instead, components are preferred as the fundamental unit for UI reuse and composition.

Vue guide on interpolating raw HTML

[v-html updates] the element’s innerHTML. Note that the contents are inserted as plain HTML - they will not be compiled as Vue templates. If you find yourself trying to compose templates using v-html, try to rethink the solution by using components instead.

Vue API documentation on the v-html directive

Solution

Components are the fundamental units for UI reuse and composition. We now must build a component that is capable of identifying particular substrings and wrapping a component around them. Vue's components/templates and directives by themselves would not be able to handle this task – it just isn't possible. However Vue does provide a way of building components at a lower level through render functions.

With a render function we can accept a string as a prop, tokenize it and build out a view with matching substrings wrapped in a component. Below is a naive implementation of such a solution:

const Chip = {
  template: `
    <div class="chip">
      <slot></slot>
    </div>
  `,
};

const SmartRenderer = {
  props: [
    'string',
  ],

  render(createElement) {
    const TOKEN_DELIMITER_REGEX = /(\s+)/;
    const tokens = this.string.split(TOKEN_DELIMITER_REGEX);
    const children = tokens.reduce((acc, token) => {
      if (token === 'foo') return [...acc, createElement(Chip, token)];
      return [...acc, token];
    }, []);

    return createElement('div', children);
  },
};

const SmartInput = {
  components: {
    SmartRenderer
  },

  data: () => ({
    value: '',
  }),

  template: `
    <div class="smart-input">
      <textarea
        class="input"
        v-model="value"
      >
      </textarea>
      <SmartRenderer :string="value" />
    </div>
  `,
};

new Vue({
  el: '#root',

  components: {
    SmartInput,
  },

  template: `
    <SmartInput />
  `,

  data: () => ({}),
});

.chip {
  display: inline-block;
  font-weight: bold;
}

.smart-input .input {
  font: inherit;
  resize: vertical;
}

.smart-input .output {
  overflow-wrap: break-word;
  word-break: break-all;
}

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <link rel="stylesheet" href="https://unpkg.com/milligram@1.3.0/dist/milligram.css">
</head>
<body>
  <p>Start typing below. At some point include <strong>foo</strong>, separated from other words with at least one whitespace character.</p>
  <div id="root"></div>
  <script src="https://unpkg.com/vue@2.5.8/dist/vue.min.js"></script>
</body>
</html>

There is a SmartRenderer component which accepts a string through a prop. Within the render function we:

  1. Tokenize the string by splitting it by whitespace characters.
  2. Build up an array of elements to be rendered by iterating through each token and

    1. Checking if the token matches a rule (in our naive implementation seeing if the string matches foo) and wrapping it in a component (in our naive implementation the component is a Chip, which just makes the foo bold) otherwise leave the token as is.
    2. Accumulating the result of each iteration in an array.

  3. The array of Step 3 is then passed to createElement as the children of a div element to be created.

render(createElement) {
  const TOKEN_DELIMITER_REGEX = /(\s+)/;
  const tokens = this.string.split(TOKEN_DELIMITER_REGEX);
  const children = tokens.reduce((acc, token) => {
    if (token === 'foo') return [...acc, createElement(Chip, token)];
    return [...acc, token];
  }, []);

  return createElement('div', children);
},

createElement takes an HTML tag name, component options (or a function) as its first argument and in our case the second argument takes a child or children to render. You can read more about createElement in the docs.

The solution as posted has some unsolved problems such as:

  • Handling a variety of whitespace characters, such as newlines (\n).
  • Handling multiple occurrences of whitespace characters, such as (\s\s\s).

It is also naive in the way it checks if a token needs to be wrapped and how it wraps it – it's just an if statement with the wrapping component hard-coded in. You could implement a prop called rules which is an array of objects specifying a rule to test and a component to wrap the token in if the test passes. However this solution should be enough to get you started.

Further reading

这篇关于如何在 Vue.js 中使用组件动态包装子字符串?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆