Vue2 爆改 Vue3 + TS

Siona

Vue2 爆改 Vue3 + TS

挂载 Vue 组件

Vue2

import {HtmlNode, HtmlNodeModel, h} from '@logicflow/core'
import baseNode from './baseNode.vue'

class BaseNodeView extends HtmlNode {
    constructor(props) {
        super(props)
        this.root = document.createElement('div')
        this.vueComponent = baseNode
    }

    setHtml(rootEl) {
        rootEl.appendChild(this.root)
        if (this.vm) {
            this.vm.$mount(this.root)
        } else {
            this.vm = new Vue({
                render: (h) =>
                    h(this.vueComponent, {
                        props: {
                            model: this.props.model,
                            graphModel: this.props.graphModel,
                            disabled: this.props.graphModel.editConfigModel.isSilentMode,
                            isSelected: this.props.model.isSelected,
                            isHovered: this.props.model.isHovered,
                            properties: this.props.model.getProperties()
                        }
                    })
            })
            // 挂载
            this.vm.$mount(this.root)
        }
    }
}

Vue3

Vue3 不再使用 new Vue() 来创建一个新的 Vue 应用实例,而是使用 createApp 函数。 此外,h 函数现在是 vue 包的一部分,不再是 vue 实例的一部分。

Vue3 中 h 函数,第一个参数是……,第二个参数:传递给组件的所有属性,组件中用 props 接收。

// 注意,如果 h 已被其他组件用了,则 Vue 中需要用别名 h as vueH
import {App, createApp, h as vueH} from "vue";
import {HtmlNode, HtmlNodeModel, h} from '@logicflow/core'
import baseNode from '@/components/processRouteDesign/kwChart/node/BaseNode.vue'


class BaseNodeView extends HtmlNode {

    // Vue app instance
    declare vueApp: App<Element>;
    declare rootElement: any;
    declare vueComponent: any;

    constructor(props) {
        super(props)
        this.rootElement = document.createElement('div')
        this.vueComponent = baseNode
    }

    setHtml(rootEl: HTMLElement) {
        rootEl.appendChild(this.rootElement);

        if (this.vueApp) {
            this.vueApp.mount(this.rootElement);
        } else {
            const component = {
                render: () =>
                    vueH(this.vueComponent, {
                        model: this.props.model,
                        graphModel: this.props.graphModel,
                        disabled: this.props.graphModel.editConfigModel.isSilentMode,
                        isSelected: this.props.model.isSelected,
                        isHovered: this.props.model.isHovered,
                        properties: this.props.model.getProperties()
                    })
            };
            this.vueApp = createApp(component);
            this.vueApp.mount(this.rootElement);
        }
    }
}

props

Vue2

<!-- vue2 -->
<script>
    export default {
        props: {
            model: Object,
            graphModel: Object,
            isSelected: Boolean,
            isHovered: Boolean,
            disabled: Boolean,
            properties: Object
        },
    }
</script>

Vue3

<!-- vue3 -->
<script setup lang="ts">
    const props = defineProps<{
        model: any,
        graphModel: any
        isSelected: any,
        isHovered: any,
        disabled: any,
        properties: any
    }>();
</script>

watch 监听

Vue2 代码

<!-- vue2 -->
<script>
    export default {
        props: {
            model: Object,
            graphModel: Object,
            isSelected: Boolean,
            isHovered: Boolean,
            disabled: Boolean,
            properties: Object
        },
        watch: {
            isHovered(nv) {
                // console.log('nv --->>>', nv);
                if (nv) {
                    this.enterNode()
                } else {
                    this.leaveNode()
                }
            }
        }
    }
</script>

Vue3

在 Vue 3 中,当你在 watch 函数中直接使用 props.isHovered 时,可能无法正确地监听到其变化。 这是因为 props 对象本身并没有被 Vue 3 设计为响应式的,而是 props 对象中的各个属性是响应式的。 因此,你需要使用一个函数来返回 props.isHovered,这样 watch 函数就能正确地监听其变化了。

<!-- vue3 -->
<script setup lang="ts">
    import {watch} from "vue";

    const props = defineProps<{
        model: any,
        graphModel: any
        isSelected: any,
        isHovered: any,
        disabled: any,
        properties: any
    }>();
    watch(
        () => props.isHovered,
        (val) => {
            console.log('val --->>>', val);
            if (val) {
                enterNode();
            } else {
                leaveNode();
            }
        }
    )

    // 注意,如果直接使用 watch(props.isHovered, (val) => {}) 不会生效,浏览器控制台会有警告。
</script>

emit

vue2

<!-- Vue2 -->
<script>
    export default {
        methods: {
            handleChange(e) {
                this.val = e
                const option = this.options.find((item) => item.value === e) || {}
                const data = {
                    type: 'option',
                    value: e,
                    valueDesc: option.label
                }
                this.$emit('change', data)
            },
        }
    }
</script>

vue3

<!-- Vue3 -->
<script setup lang="ts">
    const emit = defineEmits(['change']);
    
    const someFunction = (data: any) => {
        emit('change', data);
    }
</script>

常见报错

实际运行时浏览器控制台会有一个 [Vue warn]

ERROR

[Vue warn]: App has already been mounted.

If you want to remount the same app, move your app creation logic into a factory function and create fresh app instances for each mount - e.g. const createMyApp = () => createApp(App)

这个警告是因为你尝试多次挂载同一个 Vue 应用实例。 在 Vue.js 中,一个应用实例只能被挂载一次。 如果你需要重新挂载应用,你需要创建一个新的应用实例。

问题描述:上述代码试图检查 vueApp 是否已经存在,如果存在就尝试重新挂载它。但是,Vue 不允许同一个应用实例被挂载多次,所以当 setHtml 被第二次调用时,你的代码就会抛出错误。 所以,我们需要修改这部分的逻辑。当 setHtml 被调用时,我们应该始终创建一个新的 Vue 应用实例并挂载它,而不是试图重新挂载现有的应用实例。

class BaseNodeView extends HtmlNode {

    // Vue app instance
    declare vueApp: App<Element>;
    declare rootElement: any;
    declare vueComponent: any;

    constructor(props) {
        super(props)
        this.rootElement = document.createElement('div')
        this.vueComponent = baseNode
    }

    setHtml(rootEl: HTMLElement) {
        rootEl.appendChild(this.rootElement);

        const component = {
            render: () =>
                vueH(this.vueComponent, {
                    model: this.props.model,
                    graphModel: this.props.graphModel,
                    disabled: this.props.graphModel.editConfigModel.isSilentMode,
                    isSelected: this.props.model.isSelected,
                    isHovered: this.props.model.isHovered,
                    properties: this.props.model.getProperties()
                })
        };
        this.vueApp = createApp(component);
        this.vueApp.mount(this.rootElement);
    }
}

// 在这个版本中,保留了 vueApp 属性,并在 setHtml 方法中更新了它。
// 这样,每次 setHtml 被调用时,都会创建一个新的 Vue 应用实例并挂载它,避免了重复挂载的问题。

ERROR

[Vue warn]: Failed to resolve component: el-popconfirm

If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.

Vue 无法解析 el-popconfirm 组件。

Last Updated 5/17/2024, 9:41:27 AM