id: advanced-topics-decorators

title: 装饰器

内联和块样式不是我们可能要添加到编辑器中的唯一一种丰富样式。例如,Facebook评论输入提供用于mentions(提及)和hashtags(主题标签)的蓝色背景高亮显示。

为了支持自定义富文本格式的灵活性,Draft提供了一个“装饰器”系统。该tweet example提供了一个实际的装饰器示例。

CompositeDecorator 复合装饰器

装饰器的概念基于扫描给定ContentBlock的内容以 查找与定义的策略匹配的文本范围,然后使用指定的React组件渲染它们。

您可以使用CompositeDecorator该类来定义所需的装饰器行为。此类允许您提供多个DraftDecorator对象,并且将依次搜索每种策略的一段文本。

装饰器存储在EditorState记录中。在创建新EditorState对象时,(例如通过)EditorState.createEmpty(),可以选择提供装饰器。

Under the hood 引擎盖下

在Draft编辑器中更改内容时,生成的EditorState对象将ContentState使用其装饰器评估新对象,并标识要装饰的范围。此时,将形成一个完整的块,装饰器和内联样式树,并作为渲染输出的基础。

这样,我们始终确保随着内容的更改,渲染的装饰与我们的EditorState同步。

例如,在“ Tweet”编辑器示例中,我们使用一个CompositeDecorator搜索@-handle字符串以及hashtag(#)字符串:

  1. const compositeDecorator = new CompositeDecorator([
  2. {
  3. strategy: handleStrategy,
  4. component: HandleSpan,
  5. },
  6. {
  7. strategy: hashtagStrategy,
  8. component: HashtagSpan,
  9. },
  10. ]);

该复合装饰器将首先扫描给定的文本块以查找@-handle匹配项,然后扫描hashtag(#)匹配项。

  1. // 注意:这些不是很好的正则表达式,请不要使用它们!
  2. const HANDLE_REGEX = /\@[\w]+/g;
  3. const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
  4. function handleStrategy(contentBlock, callback, contentState) {
  5. findWithRegex(HANDLE_REGEX, contentBlock, callback);
  6. }
  7. function hashtagStrategy(contentBlock, callback, contentState) {
  8. findWithRegex(HASHTAG_REGEX, contentBlock, callback);
  9. }
  10. function findWithRegex(regex, contentBlock, callback) {
  11. const text = contentBlock.getText();
  12. let matchArr, start;
  13. while ((matchArr = regex.exec(text)) !== null) {
  14. start = matchArr.index;
  15. callback(start, start + matchArr[0].length);
  16. }
  17. }

策略函数使用文本匹配范围的startend值执行提供的回调。

Decorator Components 装饰器组件

对于修饰的文本范围,必须定义一个React组件以用于渲染它们。这些往往是简单的span元素,并带有CSS类或样式。

在我们当前的示例中,CompositeDecorator对象名称HandleSpanHashtagSpan作为用于装饰的组件。这些是基本的无状态组件:

  1. const HandleSpan = props => {
  2. return (
  3. <span {...props} style={styles.handle}>
  4. {props.children}
  5. </span>
  6. );
  7. };
  8. const HashtagSpan = props => {
  9. return (
  10. <span {...props} style={styles.hashtag}>
  11. {props.children}
  12. </span>
  13. );
  14. };

装饰器组件将在props中接收各种元数据,包括contentState的副本,entityKey(如果有)和blockKey。 有关提供给装饰器组件的props的完整列表,请参见DraftDecoratorComponentProps 类型

请注意,props.children被传递到渲染输出。 这样做是为了确保在装饰范围内渲染文本。

您可以对链接使用相同的方法,如我们的link example所示。

Beyond CompositeDecorator 超越复合装饰器

提供给EditorState的装饰器对象只需要符合DraftDecoratorTypeFlow类型定义的期望,这意味着您可以创建所需的任何装饰器类,只要它们与期望的类型匹配即可-您不受CompositeDecorator的约束。

Setting new decorators 设置新的装饰器

此外,可以接受的是,在正常状态传播期间,通过不可变的方法在EditorState上动态设置新的装饰器值。

这意味着在您的应用工作流程中,如果您的装饰器变得无效或需要修改,则可以创建一个新的装饰器对象(或使用null删除所有装饰),并创建EditorState.set()以使用新的装饰器设置。

例如,如果由于某种原因我们希望在用户与编辑器交互时禁止创建@-handle装饰,则可以执行以下操作:

  1. function turnOffHandleDecorations(editorState) {
  2. const onlyHashtags = new CompositeDecorator([
  3. {
  4. strategy: hashtagStrategy,
  5. component: HashtagSpan,
  6. },
  7. ]);
  8. return EditorState.set(editorState, {decorator: onlyHashtags});
  9. }

editorStateContentState将使用新的装饰器重新评估,并且@-handle装饰将不再出现在下一个渲染通道中。

同样,由于跨不可变对象的数据持久性,这仍然保持内存效率。