V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yezheyu
V2EX  ›  程序员

请教一个 vue 中组件复用的生成的页面间切换的问题

  •  
  •   yezheyu · 2022-10-01 17:50:02 +08:00 · 1598 次点击
    这是一个创建于 544 天前的主题,其中的信息可能已经有所发展或是发生改变。

    标题描述的可能不太好,请看下面 demo

    router.js

    const routes = [
        {
            path: '/board/:boardId',
            component: board
        }
    ]
    

    app.vue

    <template>
        <router-link to="/board/1">画板 1</router-link>
        <router-link to="/board/2">画板 2</router-link>
        <router-view />
    </template>
    

    board.vue

    <template>
        <div>画板 {{ $route.params.boardId }}</div>
        <button @click="addP">添加一个段落</button>
        <div class="container"></div>
    </template>
    
    <script setup>
        function addP() {
            let p = document.createElement('P')
            p.innerText = '新增内容'
            document.querySelector('.container').appendChild(p)
        }
    </script>
    

    大概就是有两个画板,点击按钮就会切换画板,画板都是由 board 组件复用生成的,在画板中可以添加文本

    我需要的功能是两个画板互不影响,在画板 1 中添加文字后,切换到画板 2 ,画板 2 上是没有画板 1 中文字

    即下图中效果:

    但实际的效果:

    原因我也知道,复用组件生成的页面之间切换,代码不会重复执行

    那怎样才能保持两个画板的独立呢?


    之前请教的一个问题中,@vinsony 老哥提供了一种绑定 key 的思路

    我改了下 demo 中 app.vue 实现了预定的效果

    <template>
        <router-link to="/board/1">画板 1</router-link>
        <router-link to="/board/2">画板 2</router-link>
        <router-view v-slot="{ Component, route }">
            <keep-alive>
                <component :is="Component" :key='route.fullPath'/>
            </keep-alive>
        </router-view>
    </template>
    

    但是 @Zzzz77 老哥说绑定 key 这种骚操作有点野路子,那不绑定 key 怎么解决这个问题呢?


    14 条回复    2022-10-04 11:57:00 +08:00
    signalas1
        1
    signalas1  
       2022-10-01 17:54:21 +08:00
    如果你不能把两个画板的所有 UI 状态都数据化,就只能用显隐来做。
    renmu
        2
    renmu  
       2022-10-01 18:35:35 +08:00 via Android
    加 key 解决成本最低,要么你就 watch route.path 然后重新处理初始化逻辑
    bojackhorseman
        3
    bojackhorseman  
       2022-10-01 19:11:37 +08:00 via iPhone
    绑定 key 并不是野路子,vue router 官方文档有提过
    bojackhorseman
        4
    bojackhorseman  
       2022-10-01 19:21:25 +08:00 via iPhone
    @bojackhorseman 抱歉我记错了,不过官方的建议是 watch 路由信息
    tyx1703
        5
    tyx1703  
       2022-10-01 20:21:18 +08:00   ❤️ 2
    首先你这个添加段落的方式就不对,不要直接操作 DOM ,而是用一个数组保存起来,在模板中渲染。要理解 UI 就是数据,数据就是 UI 。

    根据你的需求,可以用一个 boardId 为键,段落列表 为值的对象保存,然后用计算属性获取当前路由下的段落列表再进行渲染。
    WhateverYouLike
        6
    WhateverYouLike  
       2022-10-01 20:31:41 +08:00 via Android   ❤️ 1
    这是 vue3 的 feature 😄,名为 static hoisting 。
    今天刚写了一篇文章:
    https://jaufey-blog.vercel.app/blog/static-hoisting/
    gouflv
        7
    gouflv  
       2022-10-01 22:36:46 +08:00 via iPhone
    展开说说怎么个野路子、骚炒作? 这不就是基本操作吗
    liyang5945
        8
    liyang5945  
       2022-10-01 23:13:04 +08:00 via Android
    在 vue 里操作 dom ,你这才是骚操作野路子
    arnosolo
        9
    arnosolo  
       2022-10-02 07:46:48 +08:00
    可是为什么要放在两个路由下?
    RabbitDR
        10
    RabbitDR  
       2022-10-02 11:42:48 +08:00
    可以把状态提升到父组件,或者其它地方,然后把状态传到子组件
    也可以 keep-alive + key 组合,但挂载多个组件
    还可以如 5 楼 所说,自己管理状态,根据路径渲染不同的列表
    Zzzz77
        11
    Zzzz77  
       2022-10-02 12:26:47 +08:00   ❤️ 1
    1 、从上个问题和这个问题的示例就能看出来,OP 完完全全不理解 MV*,正确的做法 #5 已经说的非常非常非常清楚了。
    2 、首先明确一个点:OP 绑定 key 的目的是让子组件重新渲染,以此到达重新执行生命周期的目的。且不说你的例子中是否真的有重新执行生命周期的需求,即使真的有,也不该使用这种手段,举个例子:

    正常的做法:
    ```
    // 父组件
    <template>
    <ChildA :count="count" />

    <button @click="count = count + 1">add</button>
    </template>

    <script lang="ts" setup>
    import { ref } from 'vue'
    import ChildA from './ChildA.vue'

    const count = ref(1)
    </script>
    ```

    ```
    // 子组件
    <template>
    <div>{{count}}</div>
    </template>

    <script lang="ts" setup>
    import { watch } from 'vue'

    const props = defineProps({
    count: {
    type: Number,
    required: true,
    },
    })

    const func = () => {
    console.log('render')
    }

    watch(() => props.count, func, { immediate: true })
    </script>
    ```

    OP 的做法:
    ```
    // 父组件
    <template>
    <ChildA :count="count" :key="count" />

    <button @click="count = count + 1">add</button>
    </template>

    <script lang="ts" setup>
    import { ref } from 'vue'
    import ChildA from './ChildA.vue'

    const count = ref(1)
    </script>
    ```

    ```
    // 子组件
    <template>
    <div>{{count}}</div>
    </template>

    <script lang="ts" setup>
    defineProps({
    count: {
    type: Number,
    required: true,
    },
    })

    const func = () => {
    console.log('render')
    }

    func()
    </script>
    ```

    后者的问题:
    ①不易理解(特别是新手)。
    ②性能问题。
    ③最重要的一点,由于可控的粒度过大,很容易导致 BUG 。因此能 watch 解决的一般都不会选择这样去操作。

    3 、继续强调刚才提到的一个点,OP 很多 [重新执行生命周期] 的需求根本就是伪需求,在之前的例子中完全没有必要,在本帖的例子中也没有必要(如#5 所讲的)。其实只是一个子组件接收父组件响应式数据的简单问题,就更没有必要绑定 key 强行让子组件重复渲染了。
    Zzzz77
        12
    Zzzz77  
       2022-10-02 12:46:05 +08:00
    simple233
        13
    simple233  
       2022-10-02 17:17:47 +08:00 via iPhone
    在画板组件里直接监听路由,路由改变清空数据就行了,我记得官方文档也是这么做的
    yezheyu
        14
    yezheyu  
    OP
       2022-10-04 11:57:00 +08:00
    @Zzzz77
    @tyx1703

    还有上面的其它老哥
    谢谢大家

    新手让大家见笑了

    现在基本明白了

    首先 vue 中还是尽量少操作 dom

    然后 UI 数据化,根据多个画板的数据存在一起,使用路由配合 watch 或 computed 选择性渲染
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1194 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 18:25 · PVG 02:25 · LAX 11:25 · JFK 14:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.