Open3
共同編集のためのライブラリ Yjs が面白そう
Yjs とは
Yjs: https://github.com/yjs/yjs
Documantation: https://docs.yjs.dev/
Yjs は CRDT の実装。CRDT は a conflict-free replicated data type の略で、分散コンピューティングに特化したデータ構造のこと。
Yjs は、Shared types と呼ばれる、Map や Array などの型を持っている。この型の変更が自動的に他の peer に伝えられてコンフリクトを起こさずマージできる。
どこで使われているか
主要なテキストエディタの編集にはYjsのbindingが実装されていそう。最近話題になっている、tiptapの共同編集の内部で Yjs が使われているのを見て、自分は興味を持った。
Draft.js の binding は残念ながらない。Draft.js は Immutable.js を使っている特性上実装が厳しい?
Yjs を使って、簡単なTODOアプリを作ってみた。
TODOアプリのロジックは以下のような感じ。
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import { useCallback, useEffect, useState } from "react";
const yDoc = new Y.Doc();
// Sync clients with the y-websocket provider
const wsProvider = new WebsocketProvider(
"ws://localhost:3001",
"todo-demo",
yDoc
);
wsProvider.on("status", (event: { status: any }) => {
console.log(event.status); // logs "connected" or "disconnected"
});
const yTodoArray = yDoc.getArray<string>("todo-demo:todo-list");
function useYArrayValue<T extends unknown>(yArray: Y.Array<T>) {
const [value, setValue] = useState<T[]>(yArray.toArray());
useEffect(() => {
yArray.observe((_) => {
setValue(yArray.toArray());
});
}, [yArray]);
return value;
}
export const useYTodo = () => {
const [currentTodo, setCurrentTodo] = useState("");
const todos = useYArrayValue<string>(yTodoArray);
const addTodo = useCallback(
(todo: string) => {
if (todos.includes(todo)) {
console.error(`This todo has beed stacked!`);
return;
}
yTodoArray.push([todo]);
},
[todos]
);
return { currentTodo, setCurrentTodo, addTodo, todos } as const;
};
やってることは以下。
-
y-websocket
で起動したサーバーとソケット通信を行う -
YDoc
からYArray
を取得する -
YArray.observe
で、変更を検知して反映する-
useYArrayValue
がその処理に該当
-
確かに、ちゃんと TODO の追加が他のブラウザに伝搬されている。
アルゴリズムはどうなってる?
CRDT は変更を可換な操作にだけ限定し、その操作列をデータ構造に保持することで、 Peer 同士でデータマージを Conflict-free に行うことができる理論のこと。
Yjs はさらに CRDT の最適化を行っているらしい。