React useRef() 未定义

问题描述

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

我将 WebsocketProvider 设置为 useRef(),这样我们就不会在每次渲染组件时不断地重新创建它(在我启动数十个 websocket 之前)。但是,现在它甚至还没有被定义,因为 console.log(this.yXmlFragment,this.provider) 中的 get plugins() 都以 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 包含在 useEffectEditorContainer 的依赖项列表中。因为 sectionID 在初始加载时不会改变,所以这个 useEffect 永远不会触发。我在这里创建了一个最小的例子: https://codesandbox.io/s/focused-babbage-ilx4j?file=/src/App.js

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