Slate.js 入门(三)- 自定义元素

1,314 阅读3分钟

我正在参加「掘金·启航计划」

前言

Slate 入门系列,有兴趣可戳 -->

在我们之前关于 Slate 的入门文章中,我们都是从段落 paragraph 开始,未让 Slate 在段落中渲染其他类型的元素区块。此时,Slate 会使用内部的默认渲染器,即老式的 div

然而,Slate 可以渲染你需要的自定义类型元素,比如引号块、代码块、列表元素等等...

自定义元素

现在,我们在之前的例子的基础上,添加「代码类型」的元素区块到编辑器中。

添加自定义-代码区块

需要注意的是,Slate 并不会直接识别代码区块按照你所设想的那样去渲染,这需要你手动去定义实现「代码块」元素节点的渲染器。

而元素渲染器可以立即为简单的 React 组件,例如"代码"组件:

const CodeElement = props => {
    return (
        <pre {...props.attributes}>
            <code>{props.children}</code>
        </pre>
    )
}

关于 props

泛指父组件传入子组件的参数,其中分为 props.attributesprops.children

props.attributes

Slate 渲染过程中所需的内置特性,即对应区块最顶层元素上所呈现的属性,有了它开发者就不用自己去建立它们了。

比如,我们定义的普通文字最顶层元素的属性为 data-slate-node=element,这是 Slate 的自定义内置特性。

image-20220926162238218.png

那么 props.attributes 的值则为 :

image-20220926162512790.png

Slate 的自定义内置特性关于 Element 的还有很多,比如

  • data-slate-node:取值有 'element'|'value'|'text',分别代表元素、文档值(适用于 Editable )、文本节点(适用于 isInline 的元素)
  • data-slate-void:若为空元素则取值为 true,否则不存在
  • data-slate-inline:若为内联元素则取值为 true,否则不存在
  • contentEditable:若不可编辑则取值为 false,否则不存在
  • dir: 若编辑方向为从右到左则取值 'rtl',否则不存在
  • ......

props.children

指 Slate 接管并负责渲染的文本组件。Slate 会自动为你渲染一个区块的所有子元素,就像 React 组件一样通过 props.children 将它们传递给你。这样,你就不需要把精力放在渲染节点上。

添加渲染器

现在我们添加「代码块」的渲染器到编辑器中,为了方便区分,我们修改下「代码块」的颜色样式

const CodeElement = props => {
    // 添加代码块背景颜色 便于区别
    return (
        <pre {...props.attributes} style={{ background: "#dadada" }}>
            <code>{props.children}</code>
        </pre>
    )
}
​
const DefaultElement = props => {
    return <p {...props.attributes}>{props.children}</p>
}
​
const App = () => {
    const [editor] = useState(() => withReact(createEditor()))
​
    // 根据传递给 `props` 的元素定义渲染对应组件
    // 这里我们用 `useCallback` 包裹,用于记忆后续渲染的函数
    const renderElement = useCallback(props => {
        switch (props.element.type) {
            case 'code':
                return <CodeElement {...props} />
                default:
                return <DefaultElement {...props} />
        }
    }, [])
​
    return (
        <Slate editor={editor} value={initialValue}>
            <Editable
                // Pass in the `renderElement` function.
                renderElement={renderElement}
            />
        </Slate>
    )
}

这时候,我们往初始值添加「代码」的文本

const initialValue = [
    {
        type: "code",
        children: [{ text: "这是一段初始文字。" }],
    },
];

刷新页面后就可以看到「代码」的样子了~

image-20220926170946829.png

辅助快捷键

但是,这样定义代码块很不方便,用过 Typora 软件的朋友应该知道,我们只需要选择文本,然后使用快捷键就能实现将普通文本转换为「代码」的格式。那么在 Slate 中,我们可以在 onKeyDown 事件中去实现它:

onKeyDown={event => {
           if (event.key === '`' && event.ctrlKey) {
               // 阻止 ` 按键的默认插入
               event.preventDefault()
               // 替换当前的节点类型为"代码"
               Transforms.setNodes(
                   editor,
                   { type: 'code' },
                   { match: n => Editor.isBlock(editor, n) }
               )
           }
}}

接下来,我们使用快捷键 ctrl + ` 将普通文本替换为「代码」:

2022-09-26-17-44-21.gif

但是呢,这样还是很不方便,我们忘记了一件事。当你再次点击 Ctrl + ` 时,它应该将代码块恢复到之前的状态。为此,我们需要添加一些逻辑来根据当前文本块的状态来更改我们将要切换的类型:

onKeyDown={event => {
           if (event.key === '`' && event.ctrlKey) {
               event.preventDefault()
               // 判断当前选择的任何块是否是代码块
               const [match] = Editor.nodes(editor, {
                   match: n => n.type === 'code',
               })
               // 根据当前文本类型来匹配将要切换的类型
               Transforms.setNodes(
                   editor,
                   { type: match ? 'paragraph' : 'code' },
                   { match: n => Editor.isBlock(editor, n) }
               )
           }
}}

这样的话,我们就能自由地控制「普通文本-代码」类型的来回切换了。

2022-09-26-17-50-51.gif

当然,除了「代码」,你还可以定义许多你想要的类型,本文将不再赘述~

参考

juejin.cn/post/708681…

docs.slatejs.org/walkthrough…