反应 useRef() 没有被定义

React useRef() not getting defined

我有一个基于 prosemirror 的编辑器,我想在上面启用实时协作。所以我将套接字服务器设置为 described here

我将 WebsocketProvider 设置为 useRef(),这样我们就不会在每次渲染组件时都不断地重新创建它(在我启动数十个 websocket 之前)。但是,现在它甚至没有被定义,因为 get plugins() 中的 console.log(this.yXmlFragment, this.provider) 返回为 undefined

我想要的行为是我希望 provideryXmlFragment 仅在 sectionID 更改时更新,而不是任何其他重新渲染。但这甚至不是第一次设置。谁能解释我错在哪里?

import React, { useContext, useEffect, useRef } from "react";
import Editor, { Extension } from "rich-markdown-editor";
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { ySyncPlugin, yCursorPlugin } from 'y-prosemirror';
import { AuthUserContext } from "../Util/AuthUser";

type EditorInput = {
  id?: string;
  readOnly?: boolean;
  defaultValue?: string;
  value?: string;
  placeholder?: string;
  sectionID?: string;
  onChange(e: string): void;
};

const EditorContainer: React.FC<EditorInput> = (props) => {
  const {
    id,
    readOnly,
    placeholder,
    sectionID,
    defaultValue,
    value,
    onChange,
  } = props;
  const { currentUser } = useContext(AuthUserContext);

  const provider = useRef<WebsocketProvider>();
  const yXmlFragment = useRef<Y.XmlFragment>();
  
  useEffect(() => {
    const ydoc = new Y.Doc();
    yXmlFragment.current = ydoc.getXmlFragment('prosemirror');
    provider.current = new WebsocketProvider('wss://my_socket_server.herokuapp.com', `${sectionID}`, ydoc);
  }, [sectionID]);
  
  return (
    <div className="Editor-Container" id={id}>
      <Editor
        onChange={(e: any) => onChange(e)}
        defaultValue={defaultValue}
        value={value}
        readOnly={readOnly}
        placeholder={placeholder}
        extensions={[
          new yWebsocketExt(provider.current, yXmlFragment.current, currentUser)
        ]}
      />
    </div>
  );
};

export default EditorContainer;

class yWebsocketExt extends Extension {
  provider?: WebsocketProvider;
  yXmlFragment?: Y.XmlFragment;
  currentUser: User;

  constructor(provider: WebsocketProvider | undefined, yXmlFragment: Y.XmlFragment | undefined, currentUser: User) {
    super();  

    if (provider?.shouldConnect) {
      provider?.connect()
    } else {
      provider?.disconnect()
    }  

    this.provider = provider;
    this.yXmlFragment = yXmlFragment;
    this.currentUser = currentUser;
  }

  get name() {
    return "y-websocket sync";
  }

  get plugins() {
    console.log(this.yXmlFragment, this.provider);
    if (this.yXmlFragment && this.provider) {
      this.provider.awareness.setLocalStateField('user', {
        name: currentUser.name,
        color: '#1be7ff',
      });
      return [
        ySyncPlugin(this.yXmlFragment),
        yCursorPlugin(this.provider.awareness),
      ]
    }
    return []
  }
};

发生这种情况是因为您将 sectionID 包含在 EditorContaineruseEffect 的依赖项列表中。因为 sectionID 在初始加载时不会改变,所以这个 useEffect 永远不会触发。我在这里创建了一个最小的例子: https://codesandbox.io/s/focused-babbage-ilx4j?file=/src/App.js

我建议将 useEffect 更改为 useMemo,因为它在 EditorContainer 的初始渲染时至少运行一次。而且您仍然可以获得性能优势,因为它不应重新运行,除非 sectionID 发生变化。