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

请教一个 vue 中组件复用的问题

  •  
  •   yezheyu · 2022-09-27 16:33:28 +08:00 · 2228 次点击
    这是一个创建于 789 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如何让复用的组件内部代码随着切换多次执行呢?

    App.vue 组件:有两个按钮,点击按钮下面 div 显示对应按钮中的文字

    <template>
        <button @click="changeData($event)">user1</button>
        <button @click="changeData($event)">user2</button>
        <User :name="data"></User>
    </template>
    
    <script setup>
        import { ref } from 'vue';
        import User from './components/user.vue';
        let data = ref('')
    
        function changeData(e) {
            data.value = e.target.innerText
        }
    </script>
    

    User.vue 组件:

    <template>
        <div> {{ name }} </div>
        <!-- <div> {{ attrs.name }} </div> -->
    </template>
    
    <script setup>
        import { useAttrs } from 'vue';
        let attrs = useAttrs()
        let name = attrs.name
        console.log(name);
    </script>
    

    User 组件内部的代码只会执行一次,`console.log` 不会随着 button 的点击事件执行多次,不符合预期

    如果在 template 中直接使用 attrs.name 是没问题的,不会丢失响应式

    如果使用变量 name 接收,因为组件内部代码只执行一次的缘故,就不能实现切换文字效果

    如何才能让 User 组件内部的代码随着 button 点击多次执行呢?

    23 条回复    2022-10-02 12:40:16 +08:00
    sunny1688
        1
    sunny1688  
       2022-09-27 16:35:40 +08:00
    定义 props
    optional
        2
    optional  
       2022-09-27 16:41:09 +08:00 via iPhone
    name 定义成 computed
    musi
        3
    musi  
       2022-09-27 16:42:39 +08:00
    props + watch
    cydysm
        4
    cydysm  
       2022-09-27 16:45:37 +08:00
    凑活看吧
    <template>
    <div> {{ name }} </div>
    </template>

    <script setup>
    import {watch} from 'vue'
    let props = defineProps({
    name:String
    })
    watch(props.name, (val) => {
    console.log(val)
    })
    </script>
    HugoChao
        5
    HugoChao  
       2022-09-27 16:49:25 +08:00
    看了半天都怀疑自己有没有学 vue 了,发现是 vue3 语法
    EyebrowsWhite
        6
    EyebrowsWhite  
       2022-09-27 16:51:39 +08:00
    接 4 楼, 可以不用 watch, 用 toRef 或者 toRefs,
    `const name = toRef(props, 'name')`
    EyebrowsWhite
        7
    EyebrowsWhite  
       2022-09-27 16:53:20 +08:00
    额, 好吧, 没看清楚, 如果要是想运行 console 的话, 还是要 watch 的
    vinsony
        8
    vinsony  
       2022-09-27 16:53:20 +08:00
    <User :name="data" :key="data" />
    yezheyu
        9
    yezheyu  
    OP
       2022-09-27 16:54:30 +08:00
    @cydysm
    @EyebrowsWhite

    就是说把需要变化的逻辑写在 watch 中是吗
    EyebrowsWhite
        10
    EyebrowsWhite  
       2022-09-27 16:57:40 +08:00 via iPhone
    @yezheyu 是的,如果你想在某个值变化之后执行一些逻辑,那么这些逻辑要写在 watch 里面
    cydysm
        11
    cydysm  
       2022-09-27 16:57:50 +08:00
    @yezheyu setup 简单理解取代了 beforecate 和 created ,只会触发一次
    你写成选项式,就明白了
    export default defineComponent({
    setup(){},
    mounted(){}
    })
    setup 中只会走一次 mounted 也是
    yezheyu
        12
    yezheyu  
    OP
       2022-09-27 16:59:22 +08:00
    @vinsony
    好使,多谢

    能稍微解释下吗
    cydysm
        13
    cydysm  
       2022-09-27 17:03:01 +08:00
    @yezheyu #8 加 key 属性,每次只要变了都会认为是一个新的组件 而不是复用
    在这个基础上的话 我觉得 key 可以改成 symbol
    cydysm
        14
    cydysm  
       2022-09-27 17:04:07 +08:00
    yezheyu
        15
    yezheyu  
    OP
       2022-09-27 17:09:56 +08:00 via iPhone
    @cydysm
    多谢老哥,明白了
    bebop
        16
    bebop  
       2022-09-27 18:20:55 +08:00
    我觉得最合适的办法是,在 User.vue 中定义一个方法,然后暴露给父组件。父组件每次点击按钮的时候,调用这个方法,把新的数据传递给子组件。
    horizon
        17
    horizon  
       2022-09-27 20:16:56 +08:00
    这明显是 Props 。。
    yoghurtoreo
        18
    yoghurtoreo  
       2022-09-27 20:41:55 +08:00
    @bebop 俺也觉得🐼
    Zzzz77
        19
    Zzzz77  
       2022-09-27 21:35:24 +08:00   ❤️ 1
    这就是最基础的父子组件啊,首先 props 的定义是必须的,不理解 OP 为什么会选择用 attrs.name 这种方式来取父组件的值..

    其次`User 组件内部的代码只会执行一次,`console.log` 不会随着 button 的点击事件执行多次`,这是当然的,和响应式无关,不管 name 变不变,console.log 肯定都只会执行一次。如果需要在 name 变化时执行一些副作用,watch 是最正统的方式,#8 的方法不建议...#16 的方法可行,但这明显更倾向 React 的思维
    yezheyu
        20
    yezheyu  
    OP
       2022-09-29 09:59:15 +08:00
    @Zzzz77
    那复用的组件生成的页面之间不希望有任何的关联,是不是用绑定 key 的方式最合适。

    例如有下面一个场景:
    user 组件中有个输入框,点击按钮切换页面时,希望表单数据只停留在当前页面,新切换的页面不能包含上一个页面的表单数据。

    这种是不是最好就是借助绑定 key 渲染出的页面,同时使用 keepAlive 缓存

    `<keep-alive> <User :name="data" :key="data" /> </keep-alive>`
    Zzzz77
        21
    Zzzz77  
       2022-09-30 23:31:19 +08:00
    @yezheyu #20 这种情况你不需要绑定这个 key 啊,父组件传递的 data 发生了改变,User 中接收的 data 这个 prop 自然会跟着变。楼上让绑定 key 只是为了让组件重新渲染从而达到重新执行生命周期这个目的,这是一种偏门骚操作,除非有什么极其特殊的需求(反正我没遇到过),不然绝对不推荐这么做,而你的需求只是最基本的父子组件传值而已,当然也不该这么做,如果有取值以外的副作用需要执行,正常 watch 就行。
    BaiLinfeng
        22
    BaiLinfeng  
       2022-10-02 01:28:44 +08:00
    @Zzzz77 这句话是说组件只会运行一次吗?不理解,可以详细解说下,感谢。“楼上让绑定 key 只是为了让组件重新渲染从而达到重新执行生命周期这个目的,”
    Zzzz77
        23
    Zzzz77  
       2022-10-02 12:40:16 +08:00
    @BaiLinfeng 当然呀,一般情况组件只有被载入的时候会初始化一次,例如第一次加载,例如从 v-if="false"到 v-if="true"后。各个生命周期也就是在这时候开始执行的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2794 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 09:41 · PVG 17:41 · LAX 01:41 · JFK 04:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.