定义自定义元素

在之前的示例中,在之前的段落中,但是并未真正告诉过 Slate 任何关于段落块类型的信息。只是让其使用内部默认渲染器,一个普通的老式 <div>

但是能做的不仅仅是这些。Slate 允许定义任何自定义块类型,像引用、代码块、列表项等等。

下面会介绍如何做。先从之前的应用开始吧:

const initialValue = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
]

const App = () => {
  const [editor] = useState(() => withReact(createEditor()))

  return (
    <Slate editor={editor} initialValue={initialValue}>
      <Editable
        onKeyDown={event => {
          if (event.key === '&') {
            event.preventDefault()
            editor.insertText('and')
          }
        }}
      />
    </Slate>
  )
}

现在添加 “代码块” 到编辑器中。

有个问题,就是代码块不能只使用普通的段落去渲染,还需要以不同的方式渲染。为了实现这一点,需要为 code 元素节点定义个“渲染器”。

元素渲染器只是简单的 React 组件,如下所示:

// 为代码块定义 React 组件渲染器。
const CodeElement = props => {
  return (
    <pre {...props.attributes}>
      <code>{props.children}</code>
    </pre>
  )
}

是不是很容易。

看到 props.attributes 引用了吗?Slate 传递应该在块的最上层元素上渲染的属性,所以不必自己构建它们。必须将属性组合到组件中。

看到 props.children 引用了吧? Slate 会自动渲染块下面的所有子元素,然后像是其它 React 组件一样通过 props.children 传递。这样就不必费心渲染正确的文本节点或者类似的东西。必须将组件中的子元素作为最低叶子进行渲染。

这是 “default” 元素组件:

const DefaultElement = props => {
  return <p {...props.attributes}>{props.children}</p>
}

现在,添加渲染器到 Editor

const initialValue = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
]

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} initialValue={initialValue}>
      <Editable
        // 传入 `renderElement` 函数renderElement={renderElement}
        onKeyDown={event => {
          if (event.key === '&') {
            event.preventDefault()
            editor.insertText('and')
          }
        }}
      />
    </Slate>
  )
}

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

const DefaultElement = props => {
  return <p {...props.attributes}>{props.children}</p>
}

好,但现在需要一种方式让用户真正的将块转化为代码块。所以改变 onKeyDown 函数,添加 Ctrl-` 快捷键来做这件事:

// 从 Slate 导入 `Editor` 和 `Transforms` 助手。
import { Editor, Transforms, Element } from 'slate'

const initialValue = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
]

const App = () => {
  const [editor] = useState(() => withReact(createEditor()))

  const renderElement = useCallback(props => {
    switch (props.element.type) {
      case 'code':
        return <CodeElement {...props} />
      default:
        return <DefaultElement {...props} />
    }
  }, [])

  return (
    <Slate editor={editor} initialValue={initialValue}>
      <Editable
        renderElement={renderElement}
        onKeyDown={event => {
          if (event.key === '`' && event.ctrlKey) {
            // 默认防止插入 “`”。
            event.preventDefault()
            // 否则,将当前已选块类型设置为 “code”。
            Transforms.setNodes(
              editor,
              { type: 'code' },
              { match: n => Element.isElement(n) && Editor.isBlock(editor, n) }
            )
          }
        }}
      />
    </Slate>
  )
}

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

const DefaultElement = props => {
  return <p {...props.attributes}>{props.children}</p>
}

现在,如果按下 Ctrl-` ,现在光标所在的块会变成代码块!神奇吧!

但是忘记了一件事。就是当再次点击 Ctrl-` 时,应该将代码块改回段落。为此,需要添加逻辑来根据当前选择的块是否是代码块来更改设置的类型:

const initialValue = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
]

const App = () => {
  const [editor] = useState(() => withReact(createEditor()))

  const renderElement = useCallback(props => {
    switch (props.element.type) {
      case 'code':
        return <CodeElement {...props} />
      default:
        return <DefaultElement {...props} />
    }
  }, [])

  return (
    <Slate editor={editor} initialValue={initialValue}>
      <Editable
        renderElement={renderElement}
        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 => Element.isElement(n) && Editor.isBlock(editor, n) }
            )
          }
        }}
      />
    </Slate>
  )
}

这很简单!如果代码块中按 Ctrl-` ,应该要变回段落!

results matching ""

    No results matching ""