slate.js v0.58

op操作流程和slate协同编辑 eventEmitter版本 50行代码实现相同,只是op的消息流向变成
客户端->服务端->客户端s

依赖版本

返回文档
JSON

  1. "express": "^4.17.1",
  1. "react": "^16.13.1",
  1. "react-dom": "^16.13.1",
  1. "react-scripts": "3.4.3",
  1. "slate": "^0.58.4",
  1. "slate-react": "^0.58.4",
  1. "socket.io": "^2.3.0",
  1. "socket.io-client": "^2.3.0"

协同编辑器

client

返回文档
JSX

  1. import React, { useMemo, useState, useRef, useEffect } from 'react';
  1. // 导入 Slate 编辑器的工厂函数。
  1. import { createEditor } from 'slate'
  1. // 导入 Slate 组件和 React 插件。
  1. import { Slate, Editable, withReact } from 'slate-react';
  1. import { initValue } from './slateInitialValue';
  1. import io from 'socket.io-client';
  1. const socket = io('http://localhost:3003');
  1. const SocketSyncEditor = () => {
  1. const editor = useMemo(() => withReact(createEditor()), [])
  1. // 当设置 value 状态时,添加初始化值。
  1. const [value, setValue] = useState(initValue)
  1. const id = useRef(`${Date.now()}`)
  1. const onChange = (value) => {
  1. setValue(value)
  1. const ops = editor.operations.filter( op => {
  1. if(op) {
  1. // 过滤选择,remote的op
  1. return (op.type !== 'set_selection' && op.type !== 'set_node' && !op.remote)
  1. }
  1. return false;
  1. })
  1. // 有操作时才触发
  1. if (ops.length) {
  1. socket.emit('new-operations', {editorId: id.current, ops: JSON.stringify(ops)})
  1. }
  1. }
  1. useEffect(() => {
  1. socket.on('new-remote-operations', ({editorId, ops}) => {
  1. // 触发的id和接收的id不等才有动作
  1. if (id.current !== editorId) {
  1. const parseOps = JSON.parse(ops);
  1. for (let i = 0; i < parseOps.length; i++) {
  1. const op = parseOps[i];
  1. // 给op加个remote,防止当前editor更新apply了,又再次 emit 到其他编辑器,导致死循环
  1. // 这里slate 0.47的版本apply是同步的,用一个变量就行
  1. op.remote = true;
  1. editor.apply(op);
  1. }
  1. }
  1. })
  1. }, [])
  1. return (
  1. <div className="slate-syncing-editor">
  1. <Slate editor={editor} value={value} onChange={onChange}>
  1. <Editable />
  1. </Slate>
  1. </div>
  1. )
  1. }
  1. export default SocketSyncEditor;

server

返回文档
JavaScript

  1. const app = require('express')()
  1. const http = require('http').Server(app);
  1. const io = require('socket.io')(http);
  1. const port = 3003;
  1. io.on('connection', (socket) => {
  1. socket.on('new-operations', (data) => {
  1. io.emit('new-remote-operations', data)
  1. })
  1. })
  1. http.listen(port, () => {
  1. console.log(`Example app listening at http://localhost:${port}`)
  1. })