保存到数据库
现在已经学习了如何向 Slate 编辑器添加功能的基础知识,那如何保存正在编辑的内容,以便稍后回到应用程序后还能继续加载呢。
在本教程中,将展示如何添加逻辑以便将 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 />
</Slate>
)
}
在页面上渲染一个基础的编辑器且当输入内容时会发生改变。但是如果刷新页面,一切都会恢复到最开始的样子 —— 没有保存任何内容!
这里需要做的是将更改保存到一个地方。在本示例中,虽然只是使用Local Storage(本地存储),但是会了解需要在哪里添加数据库钩子。
所以在 onChange
处理程序中需要添加在除了 set_selection
之外的内容更改时保存 value
:
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}
onChange={value => {
const isAstChange = editor.operations.some(
op => 'set_selection' !== op.type
)
if (isAstChange) {
// 保存值到本地存储。
const content = JSON.stringify(value)
localStorage.setItem('content', content)
}
}}
>
<Editable />
</Slate>
)
}
现在每当编辑页面,如果查看本地存储,应该会看到 content
值发生变化。
但。。。如果刷新页面,一切都会重置。这是因为需要确保从同一个本地存储位置拉取初始值,如下所示:
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
// 如果存在,则从本地存储拉取初始化内容。
const initialValue = useMemo(
() =>
JSON.parse(localStorage.getItem('content')) || [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
],
[]
)
return (
<Slate
editor={editor}
initialValue={initialValue}
onChange={value => {
const isAstChange = editor.operations.some(
op => 'set_selection' !== op.type
)
if (isAstChange) {
// 保存值到本地存储。
const content = JSON.stringify(value)
localStorage.setItem('content', content)
}
}}
>
<Editable />
</Slate>
)
}
现在应该在刷新时保存更改了!
成功 —— 在数据库中已包含 JSON。
但是如果你想要 JSON 意外的东西呢?是的,需要用不同的方式序列化值。例如将内容保存为纯文本而不是 JSON,可以编写一些序列化和反序列化纯文本值:
// 从 Slate 中导入 `Node` 助手接口。
import { Node } from 'slate'
// 定义接受值并返回字符串的序列化函数。
const serialize = value => {
return (
value
// 在值的子项中返回每个段落的字符串内容。
.map(n => Node.string(n))
// 用表示段落的换行符将其串联起来。
.join('\n')
)
}
// 定义接受字符串返回值的反序列化函数。
const deserialize = string => {
// 通过拆分字符串提取子级值数组。
return string.split('\n').map(line => {
return {
children: [{ text: line }],
}
})
}
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
// 使用反序列化函数从本地存储中读取数据。
const initialValue = useMemo(
deserialize(localStorage.getItem('content')) || '',
[]
)
return (
<Slate
editor={editor}
initialValue={initialValue}
onChange={value => {
const isAstChange = editor.operations.some(
op => 'set_selection' !== op.type
)
if (isAstChange) {
// 序列化值并将字符串保存到本地存储。
localStorage.setItem('content', serialize(value))
}
}}
>
<Editable />
</Slate>
)
}
棒!现在正在使用纯文本。
可以对喜欢的格式模仿此策略。可以序列化为 HTML、 Markdown 或者为案例量身定制的自定义 JSON 格式。
🤖 注意,即使可以按照个人喜好序列化内容,也要权衡取舍。序列化过程有成本,某些格式可能会比其它格式更难处理。一般来说,仅建议案例有特殊需求时才编写自己的格式。否则,最好将数据保存为 Slate 使用的格式。
如果你想更新编辑器的内容时响应 Slate 之外的事件,需要直接修改 children 属性。最简单的方式是替换 editor.children 的值 editor.children = newValue
并触发重新渲染(例如,在上面的示例中调用 editor.onChange()
)。或者,可以使用 Slate 内部操作来转换值,例如:
/**
* resetNodes 重置编辑器的值。
* 需要注意的是,传递 `at` 参数可能会导致 “Cannot resolve a DOM point from Slate point” 错误。
*/
resetNodes<T extends Node>(
editor: Editor,
options: {
nodes?: Node | Node[],
at?: Location
} = {}
): void {
const children = [...editor.children]
children.forEach((node) => editor.apply({ type: 'remove_node', path: [0], node }))
if (options.nodes) {
const nodes = Node.isNode(options.nodes) ? [options.nodes] : options.nodes
nodes.forEach((node, i) => editor.apply({ type: 'insert_node', path: [i], node: node }))
}
const point = options.at && Point.isPoint(options.at)
? options.at
: Editor.end(editor, [])
if (point) {
Transforms.select(editor, point)
}
}