【问题标题】:How can I dynamically wrap a substring with a component in Vue.js?如何在 Vue.js 中使用组件动态包装子字符串?
【发布时间】:2018-05-05 23:16:57
【问题描述】:

我想做什么

给定来自用户输入的字符串,我正在尝试使用包装在组件中的特定子字符串来呈现此字符串。在我的特殊情况下,匹配的子字符串是匹配正则表达式模式的日期,并且应该包装它的组件是来自Vuetifychip

我做了什么

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

<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>
`)

什么不工作

问题是使用自定义组件而不是纯 HTML 不能提供预期的输出。样式不渲染,组件不响应事件。

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

【问题讨论】:

  • “我给了一个带有日期的字符串。” 字符串从何而来?你怎么找到它? JS 方法何时运行——它是在特定的用户事件上运行,还是从组件方法中调用?你能分享整个组件的代码吗?
  • @wing,如图所示。它是一个带有文本区域的模态框。用户将输入消息。当用户单击加载按钮时,我调用该方法。它将显示如图所示的输出。
  • 如果我理解正确,您是在尝试呈现用户提交的字符串,其中 HTML 包裹着 RegEx 模式捕获的信息片段?
  • @wing 是的
  • 我在下面添加了一个答案:stackoverflow.com/a/47441007/3050293。如果您仍然遇到问题,或者答案是否解决了您的问题,请告诉我。

标签: javascript vue.js vuejs2 vue-component


【解决方案1】:

问题

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

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

——Vue guide on interpolating raw HTML

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

——Vue API documentation on the v-html directive

解决方案

组件是 UI 重用和组合的基本单元。我们现在必须构建一个能够识别特定子字符串并围绕它们包装组件的组件。 Vue 的组件/模板和指令本身无法处理这个任务——这是不可能的。但是 Vue 确实通过render functions 提供了一种在较低级别构建组件的方法。

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

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>

有一个SmartRenderer 组件通过一个道具接受string。在渲染函数中我们:

  1. 通过用空格字符分割字符串来标记字符串。
  2. 通过遍历每个标记和
    1. 检查令牌是否匹配规则(在我们的幼稚实现中查看字符串是否匹配foo)并将其包装在组件中(在我们幼稚的实现中,组件是Chip,这只是使foo 粗体)否则保持令牌不变。
    2. 将每次迭代的结果累积到一个数组中。
  3. 然后将 Step 3 的数组作为要创建的 div 元素的子元素传递给 createElement
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 将 HTML 标记名称、组件选项(或函数)作为其第一个参数,在我们的例子中,第二个参数需要一个或多个子级来呈现。您可以在docs 中阅读有关createElement 的更多信息。

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

  • 处理各种空白字符,例如换行符 (\n)。
  • 处理多次出现的空白字符,例如 (\s\s\s)。

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

进一步阅读

【讨论】:

  • 对不起,我没有注意到这一点。太感谢了。这是很好的解释。你的回答者帮助了我。我想到了用组件创建它。这是很好的解释。谢谢。
  • @EmJiMadhu:不用担心,很高兴它有帮助!
  • 对不起,@Wing。我有一个类似的帖子,请您帮帮我吗? stackoverflow.com/questions/53257153/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-28
  • 2018-07-15
  • 2018-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多