Decorators 装饰器
Inline and block styles aren't the only kind of rich styling that we might want to add to our editor. The Facebook comment input, for example, provides blue background highlights for mentions and hashtags.
内联和块级样式不是我们希望添加到编辑器中的唯一一种富文本样式。比如Facebook的评论输入框,给@和标签提供了蓝色背景高亮。
To support flexibility for custom rich text, Draft provides a "decorator" system. The tweet example offers a live example of decorators in action.
为了支持自定义富文本的灵活性,Draft提供了一个“decorator”系统。tweet的例子提供了一个执行中的decorator的在线实例。
复合装饰器
The decorator concept is based on scanning the contents of a given ContentBlock for ranges of text that match a defined strategy, then rendering them with a specified React component.
decorator 的概念是基于扫描一个被给予的文本范围的ContentBlock的内容,它们应该匹配一个被定义的策略,然后,用一个指定的React渲染它们。
You can use the
CompositeDecorator
class to define your desired decorator behavior. This class allows you to supply multipleDraftDecorator
objects, and will search through a block of text with each strategy in turn.
你可以使用CompositeDecorator
类来定义你想要的decorator行为。这个类允许你提供多个DraftDecorator
对象,并且将轮流在一个文本块中依照每个策略进行搜索。
Decorators are stored within the
EditorState
record. When creating a newEditorState
object, e.g. viaEditorState.createEmpty()
, a decorator may optionally be provided.
装饰器被保存在EditorState
记录中。当创建一个新的EditorState
对象时,例如通过EditorState.createEmpty()
,一个装饰器或许会被可选的提供。
Under the hood
When contents change in a Draft editor, the resulting
EditorState
object will evaluate the newContentState
with its decorator, and identify ranges to be decorated. A complete tree of blocks, decorators, and inline styles is formed at this time, and serves as the basis for our rendered output.In this way, we always ensure that as contents change, rendered decorations are in sync with our
EditorState
.
在底层
当Draft编辑器中的内容变化时,作为结果的EditorState
对象会使用它的装饰器求得新的ContentState
值,并且识别需要被装饰的范围。一个完整的块级,装饰器和行内样式的树将在这时形成,并且作为我们输出渲染的基础。
通过这种方式,我们可以始终确保内容的变化,使得装饰器和我们的EditorState同步渲染。
In the "Tweet" editor example, for instance, we use a
CompositeDecorator
that searches for @-handle strings as well as hashtag strings:
在“Tweet”编辑器的例子中,打个比方,我们使用一个CompositeDecorator
来搜索@句柄字符串以及#标签的字符串:
const compositeDecorator = new CompositeDecorator([
{
strategy: handleStrategy,
component: HandleSpan,
},
{
strategy: hashtagStrategy,
component: HashtagSpan,
},
]);
This composite decorator will first scan a given block of text for @-handle matches, then for hashtag matches.
这种复合Decorator首先会扫描一个给定的文本块是否匹配@句柄,然后扫描其是否匹配#标签。
// Note: these aren't very good regexes, don't use them!
// 注意:这些不是很好的正则,不要用它们!
const HANDLE_REGEX = /\@[\w]+/g;
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
function handleStrategy(contentBlock, callback, contentState) {
findWithRegex(HANDLE_REGEX, contentBlock, callback);
}
function hashtagStrategy(contentBlock, callback, contentState) {
findWithRegex(HASHTAG_REGEX, contentBlock, callback);
}
function findWithRegex(regex, contentBlock, callback) {
const text = contentBlock.getText();
let matchArr, start;
while ((matchArr = regex.exec(text)) !== null) {
start = matchArr.index;
callback(start, start + matchArr[0].length);
}
}
The strategy functions execute the provided callback with the start and end values of the matching range of text.
策略函数使用匹配文本范围的起始和终止的值,执行提供的回调。
装饰器组件
For your decorated ranges of text, you must define a React component to use to render them. These tend to be simple
span
elements with CSS classes or styles applied to them.
对于你要装饰的文本范围,你必须定义一个React组件用来渲染它们。通常是具有CSS类或样式的简单span
元素被应用到他们上面。
In our current example, the
CompositeDecorator
object namesHandleSpan
andHashtagSpan
as the components to use for decoration. These are just basic stateless components:
在我们现在的例子里,CompositeDecorator
对象命名了HandleSpan
和HashtagSpan
作为装饰组件。这些只是基础的无状态组件:
const HandleSpan = (props) => {
return <span {...props} style={styles.handle}>{props.children}</span>;
};
const HashtagSpan = (props) => {
return <span {...props} style={styles.hashtag}>{props.children}</span>;
};
Note that
props.children
is passed through to the rendered output. This is done to ensure that the text is rendered within the decoratedspan
.
注意,props.children
是通过渲染输出的。这样做是为了确保文本在被装饰的span
内呈现。
You can use the same approach for links, as demonstrated in our link example.
你可以对link使用相同的方法,如我们的link示例中所示。
超越混合装饰器
The decorator object supplied to an
EditorState
need only match the expectations of the DraftDecoratorType Flow type definition, which means that you can create any decorator classes you wish, as long as they match the expected type -- you are not bound byCompositeDecorator
.
被提供给EditorState
的Decorator对象仅需要匹配DraftDecoratorType流类型定义的期望,这意味着你能创建任何你期望的装饰器类,只要他们匹配期望类型——你不会被CompositeDecorator约束
。
设置新装饰器
Further, it is acceptable to set a new
decorator
value on theEditorState
on the fly, during normal state propagation -- through immutable means, of course.
此外,在动态的EditorState
上设置一个新decorator
值是被允许的,当然,是通过正常状态的传播,通过不可变的方法。
This means that during your app workflow, if your decorator becomes invalid or requires a modification, you can create a new decorator object (or use
null
to remove all decorations) andEditorState.set()
to make use of the new decorator setting.
这意味着,在你的程序工作过程中,如果你的装饰器变成无效的或需要修改,你可以创建一个新的装饰器对象(或使用null
移除所有装饰)和EditorState.set()
来使用新的装饰器设置。
For example, if for some reason we wished to disable the creation of @-handle decorations while the user interacts with the editor, it would be fine to do the following:
比方说,如果出于某种原因,我们希望在用户和编辑器交互时,禁止@句柄的装饰的创建,可以这样做:
function turnOffHandleDecorations(editorState) {
const onlyHashtags = new CompositeDecorator([{
strategy: hashtagStrategy,
component: HashtagSpan,
}]);
return EditorState.set(editorState, {decorator: onlyHashtags});
}
The
ContentState
for thiseditorState
will be re-evaluated with the new decorator, and @-handle decorations will no longer be present in the next render pass.
这个editorState
的ContentState
将被新的装饰器重新执行,并且@句柄装饰将不再出现在下一个渲染通道。
Again, this remains memory-efficient due to data persistence across immutable objects.
同样的,由于不可变的对象上的数据持久化,这保持了内存的效率。