Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何在react hook中使用Vditor #1080

Closed
ghost opened this issue Sep 10, 2021 · 16 comments
Closed

如何在react hook中使用Vditor #1080

ghost opened this issue Sep 10, 2021 · 16 comments

Comments

@ghost
Copy link

ghost commented Sep 10, 2021

描述问题

react hook中使用Vditor时,出现以下问题:
代码:

import React,{ useRef } from 'react'
import Vditor from 'vditor'

const Markdown = () => {
  let editor = useRef(null)
  let vditor = new Vditor(editor, {
    height: 360,
    toolbarConfig: {
      pin: true
    },
    cache: {
      enable: false
    },
    after() {
      vditor.setValue('Hello, Vditor + React!')
    }
  })
  return (
      <div ref={editor} />
  )
}

export default Markdown

控制台报错:

Uncaught (in promise) TypeError: Cannot add property innerHTML, object is not extensible
    at initUI (index.min.js:6245)
    at index.min.js:15342

期待的结果

能否出一个简单的react hook使用示例代码

版本信息

  • 版本:3.8.5
  • 操作系统:macOS 11.2
  • 浏览器:google 92.0
@BeiyanYunyi
Copy link

我用 id 还行,ref 可能并不是一个 element。JSX.Element 和 DOM 的 Element 应该还是有差别的。

@ghost
Copy link
Author

ghost commented Sep 14, 2021 via email

@BeiyanYunyi
Copy link

BeiyanYunyi commented Sep 14, 2021

// App.tsx
import React from "react";
import Vditor from "vditor";
import "vditor/src/assets/scss/index.scss";

const App = () => {
  const [vd, setVd] = React.useState<Vditor>();
  React.useEffect(() => {
    if (!vd) {
      const vditor = new Vditor("vditor", {
        after: () => {
          setVd(vditor);
        },
      });
    }
  }, [vd]);
  React.useEffect(() => {
    if (vd) vd.setValue("`Vditor` 最小代码示例");
  }, [vd]);
  return <div id="vditor" className="vditor" />;
};

export default App;

依赖项上,需要安装 sass 来正确显示样式,当然也可以在代码里加载 jsdelivr 上的 css 。
这是我的代码,其中 vd 会被设置为 Vditor 的实例以进行其他操作,如果不在其它地方使用的话,也可以不 useState()
btw,react-vditorReadme 里也有用 hook 的示例代码。

@ghost
Copy link
Author

ghost commented Sep 14, 2021 via email

@BeiyanYunyi
Copy link

BeiyanYunyi commented Sep 14, 2021

你的代码里,直接在组件函数内把 vditor 实例化是错误的。react 里,若组件状态有更新,则整个组件会被刷新,这样你那个实例化的过程就会被执行多次,带来性能上的损失,以及可能存在的 bug,比如你的代码实际上在自己的 DOM (return 的内容)被渲染出来之前就开始将 vditor 实例化了,这时 vditor 找不到那个 DOM。
正确的方法是像我的代码那样使用 useEffect,这个 hook 在其第二个参数(任意数组)的内容发生变动时才会运行其第一个参数(回调函数)。如果数组为空(如我的代码),则回调会在组件每次加载完成后被调用一次。按 react-vditor 的方法,性能会比我的更好一些(只要 vditor 实例不发生变动,就不再调用那段函数,相当于只在组件第一次加载时运行回调)

@BeiyanYunyi
Copy link

BeiyanYunyi commented Sep 14, 2021

另外我也建议作者更新一下 React 的示例代码。现在 React 流行 hook 增强的函数式组件,原来的类组件已经不那么常见了。我学 React 用的教程里,类组件被放在比较靠后的位置,成为了某种“高阶技巧 / 拓展阅读”。
React 官方 是这么写的:We recommend trying Hooks in new code.

@ghost
Copy link
Author

ghost commented Sep 14, 2021 via email

@BeiyanYunyi
Copy link

setValue 是 vditor 的内置方法。我代码里没有出现这个。我给改一下。

@BeiyanYunyi
Copy link

改好了,应该改得比较清楚了。

@ghost
Copy link
Author

ghost commented Sep 14, 2021 via email

@ghost
Copy link
Author

ghost commented Sep 14, 2021

改好了,应该改得比较清楚了。

感谢你的回答,辛苦辛苦

@HengCC
Copy link

HengCC commented Feb 25, 2022

我也改好了。刚刚又试了一下,发现不在useEffect中是不能直接渲染的。我之前确实试过在不在useEffect中都是一样的,但现在的表现就是在useEffect中是可以的。😅难搞哦

// App.tsx
import React from "react";
import Vditor from "vditor";
import "vditor/src/assets/scss/index.scss";

const App = () => {
  const [vd, setVd] = React.useState<Vditor>();
  React.useEffect(() => {
    if (!vd) {
      const vditor = new Vditor("vditor", {
        after: () => {
          setVd(vditor);
        },
      });
    }
  }, [vd]);
  React.useEffect(() => {
    if (vd) vd.setValue("`Vditor` 最小代码示例");
  }, [vd]);
  return <div id="vditor" className="vditor" />;
};

export default App;

依赖项上,需要安装 sass 来正确显示样式,当然也可以在代码里加载 jsdelivr 上的 css 。 这是我的代码,其中 vd 会被设置为 Vditor 的实例以进行其他操作,如果不在其它地方使用的话,也可以不 useState()。 btw,react-vditorReadme 里也有用 hook 的示例代码。

遇到了个比较奇怪的问题. 初始化过程和你一样. 初始化的时候加了定制的toolbar. 如下

{
    name: 'cancel',
    tipPosition: 's',
    tip: '取消',
    icon: '',
    click: () => {
      cancelEdit();
    },
 },

以上click被执行的时候会调用

 const cancelEdit = () => {
    console.log('markdown:', markdown);
    console.log('get value:', editor, editor?.getValue());
    editor?.setValue(markdown, true);
    setPreviewOnly(true);
  };

但是这个位置打印出来的editor是空的.. editor声明如下:
const [editor, setEditor] = useState<Vditor>();

after后set值的

after: () => {
      setEditor(vd);
},

诡异的是下面这样的effect中. 是能够获取到editor的值, 但是上面的取消触发后就无法获取

  useEffect(() => {
    console.log('change mark edit:', markdown);
    console.log('change editor:', editor);
    editor?.setValue(markdown);
  }, [editor, markdown]);

@1zumii
Copy link

1zumii commented Mar 6, 2022

// App.tsx
import React from "react";
import Vditor from "vditor";
import "vditor/src/assets/scss/index.scss";

... ...

依赖项上,需要安装 sass 来正确显示样式,当然也可以在代码里加载 jsdelivr 上的 css 。... ...

@lixiang810 我发现不用去 src/ 目录下引用 scss,我在 dist/ 目录下找到了打包好的 index.css。引入后也正常显示了。这样就省去了还需要使用 scss 的步骤。

.
├── LICENSE
├── README.md
├── dist
│   ├── css
│   ├── images
│   ├── index.css    # import 这个
│   ├── index.d.ts
│   ├── index.min.js
│   ├── js
│   ├── method.d.ts
│   ├── method.min.js
│   ├── ts
│   └── types
├── package.json
└── src

@HengCC
Copy link

HengCC commented Mar 7, 2022

我也改好了。刚刚又试了一下,发现不在useEffect中是不能直接渲染的。我之前确实试过在不在useEffect中都是一样的,但现在的表现就是在useEffect中是可以的。😅难搞哦

// App.tsx
import React from "react";
import Vditor from "vditor";
import "vditor/src/assets/scss/index.scss";

const App = () => {
  const [vd, setVd] = React.useState<Vditor>();
  React.useEffect(() => {
    if (!vd) {
      const vditor = new Vditor("vditor", {
        after: () => {
          setVd(vditor);
        },
      });
    }
  }, [vd]);
  React.useEffect(() => {
    if (vd) vd.setValue("`Vditor` 最小代码示例");
  }, [vd]);
  return <div id="vditor" className="vditor" />;
};

export default App;

依赖项上,需要安装 sass 来正确显示样式,当然也可以在代码里加载 jsdelivr 上的 css 。 这是我的代码,其中 vd 会被设置为 Vditor 的实例以进行其他操作,如果不在其它地方使用的话,也可以不 useState()。 btw,react-vditorReadme 里也有用 hook 的示例代码。

遇到了个比较奇怪的问题. 初始化过程和你一样. 初始化的时候加了定制的toolbar. 如下

{
    name: 'cancel',
    tipPosition: 's',
    tip: '取消',
    icon: '',
    click: () => {
      cancelEdit();
    },
 },

以上click被执行的时候会调用

 const cancelEdit = () => {
    console.log('markdown:', markdown);
    console.log('get value:', editor, editor?.getValue());
    editor?.setValue(markdown, true);
    setPreviewOnly(true);
  };

但是这个位置打印出来的editor是空的.. editor声明如下: const [editor, setEditor] = useState<Vditor>();

after后set值的

after: () => {
      setEditor(vd);
},

诡异的是下面这样的effect中. 是能够获取到editor的值, 但是上面的取消触发后就无法获取

  useEffect(() => {
    console.log('change mark edit:', markdown);
    console.log('change editor:', editor);
    editor?.setValue(markdown);
  }, [editor, markdown]);

我说的这个问题我找到问题点了. 主要是effect机制以及在其中初始化的问题. 需要监听editor的变更. 不然第一次after后执行获取的editor永远为空.

@BeiyanYunyi
Copy link

@HengCC 和 Effect 确实有关,但是我觉得核心在于,setState 这个函数本质是异步的,你 setState 完了并不能直接用。

@HengCC
Copy link

HengCC commented Mar 8, 2022

本质上是effect的机制决定的. 副作用如果不指定依赖的话,那么在当时的执行环境中如果editor没被初始化. 那就是获取不到值. 即使是依赖了也得这么写才能有值.

useEffect(()=>{
    if(!editor){
       // 初始化 editor
    }else{
     //上面初始化后还会触发这个effect. 在这里可以做配置覆盖等. 或者调用方法. 本质上这个副作用只和editor相关.和上面的赋值markdown没啥关系. 这可能也是React官方推荐这样玩吧. 不同的依赖不同的副作用. 分离或者耦合组成不同的副作用.
    }

},[editor]);

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants