Vue子组件向父组件数据传递,非父子组件数据传递,单向数据流&组件双向数据共享

子组件向父组件数据传递

技术实现: 事件绑定,事件触发
详细描述: == 在子组件标签上以事件绑定的方式定义父组件方法,在子组件对象
中 以属性 emits 方式进行 绑定事件的拦截,并在对应的执行区通过 $emit 实例
方法进行触发,并传递事件源参数 ==

+ 技术实现:事件绑定,事件触发

        + 详细描述:在子组件标签上以事件绑定的方式定义父组件方法,在子组件对象中 以属性 emits 方式进行 绑定事件的拦截,

                    并在对应的执行区通过 $emit 实例方法进行触发,并传递事件源参数

        + 核心功能的实现是通过 方法定义(形参)+方法调用(实参) 完成

                ==> 事件绑定:借助事件绑定可以定义事件的规则,为子组件构建多个可以用于数据传递的特殊事件名称,

                             事件所对应的回调执行方法,可由父组件提供,并完成形参接收和赋值操作,而被绑定的回调方法

                             必然可以接收一个事件源对象参数 $event , 该参数为子组件调用时回传的数据值

                ==> 事件触发:子组件标签上绑定的自定义事件,在子组件解析时被记录于 $attrs 中,所以用过 $attrs可以直接访问;

                            也可以通过 emits 配置属性实现方法的拦截加载,加载后的事件可以作为 $emit 方法的调用参数使用,

                            并且$attrs将删除拦截后的事件

<div id="app">
        <h4>父组件</h4>
        <p>info(通过子组件传递赋值的):{{ info }}</p>
        <p>text(通过子组件传递赋值的):{{ text }}</p>
        <!-- v-bind v-on 在构建规则中只有第一个:表示参数,后续表达式中的:均为名称的一部分 -->
        <comp-a v-on:update:text="updateText('定义在页面的参数')"></comp-a>
        <hr>
        <!-- 
            子组件绑定的事件在定义时,不建议定义带()回调,因为带()回调可能会导致回传数据丢失
        -->
        <comp-a v-on:update:text="updateText"></comp-a>
        <hr>
        <!-- 
            如果子组件绑定事件在定义时必须传递页面中构建的相关参数(例如循环的临时变量),可以以$event关键字代替子向父回传的数据
         -->
        <comp-a v-on:update:text="updateText($event,'定义在页面的参数')"></comp-a>

    </div>

    <script type="text/x-template" id="compA">
        <div class="box">
            <h4>子组件-CompA</h4>
            <p>msg:{{ msg }}</p>
            <input type="text" :value="msg" @input="setMsg($event.target.value)">
            <hr>
            <p>desc:{{ desc }}</p>
            <input type="button" value="传递desc数据" @click="sendDesc()">
        </div>
    </script>

    <script type="module">
        import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
        const app = createApp({
            data() {
                return {
                    info:"",
                    text:""
                }
            },
            methods: {
                updateInfo(params){
                    console.log("父组件:",params);
                    this.info = params;
                },
                updateText(nv,arg){
                    console.log("父组件-updateText:",nv,arg);
                    this.text = nv;
                },
                updateText2(){}
            },
        })

        app.component("CompA",{
            template:"#compA",
            data(){
                return {
                    msg:"子组件数据变量-msg",
                    desc:"子组件数据变量-desc"
                }
            },
            emits:["update:text"],
            methods:{
                setMsg(nv){
                    this.msg = nv;
                    this.$parent.updateInfo( this.msg )
                },
                sendDesc(){
                    console.log(this.$attrs);
                    // this.$attrs["onUpdate:text"]( this.desc )
                    // this.$emit(typeName,params)
                    //      typeName 组件中被 emits 拦截的事件名
                    //      params 是触发时的回调方法
                    //      同时该方法会在调试工具中产生日志
                    this.$emit("update:text",this.desc)
                }
            },
            created() {
                // vue每个实例都一个对应的取值的 $parent 属性,该属性是当前vue实例运行所在的父组件实例对象
                console.log(this.$parent);
                this.$parent.updateInfo( this.msg )
            },
        })

        app.mount("#app")
    </script>

vue2子传父

+ 技术实现:事件绑定,事件触发

        + 详细描述:在子组件标签上以事件绑定的方式定义父组件方法,在子组件对象中对应的执行区通过 $emit 触发

            => 子组件标签上绑定的自定义事件,在子组件解析时被记录于 this.$listeners 中 ,

               $emit方法只会调用其中的事件,不做删除

 <div id="app">
        <h4>父组件</h4>
        <p>info(通过子组件传递赋值的):{{ info }}</p>
        <comp-a @update:msg="updateInfo"></comp-a>
    </div>

    <script type="text/x-template" id="compA">
        <div class="box">
            <h4>子组件-CompA</h4>
            <p>msg:{{ msg }}</p>
            <input type="text" :value="msg" @input="setMsg($event.target.value)">
        </div>
    </script>

    <script type="module">
        import Vue from "../../assets/vue/2.0/vue.esm.browser.js";
        Vue.component("CompA",{
            template:"#compA",
            data(){
                return {
                    msg:"子组件数据变量-msg",
                }
            },
            methods:{
                setMsg(nv){
                    this.$emit("update:msg",this.msg)
                }
            }
        })
        new Vue({
            el:"#app",
            data() {
                return {
                    info:"",
                }
            },
            methods: {
                updateInfo(nv){
                    console.log("父组件方法updateInfo:",nv);
                    this.info = nv;
                }
            },
        })
    </script>

事件穿透

<!-- html的 on事件名 本质上就是 DOM的 一个特殊属性 -->

        <!-- <input type="button" value="事件绑定" v-bind:οnclick="updateMsg"> -->

        <!--

            Vue3中子组件标签上定义没有被拦截的事件,将转为 “on事件名” 格式存储于$attrs 中,此时这些事件同时具有属性穿透功能

                场景1:子组件有且仅有一个根元素

                        => 未被拦截的事件将被自动的绑定于当前组件的根元素上(自动排除非w3c规范的事件属性)

                场景2:子组件有多个根元素

                        => 事件的传递和属性的传递一样,需要开发者自行描述绑定位置

         -->

<div id="app">
        <h4>父组件</h4>
        <comp-a @update:msg="updateMsg" @click="updateMsg" @focus="updateMsg"></comp-a>
    </div>

    <script type="text/x-template" id="compA">
        <div class="box" v-bind="$attrs">
            <h4>子组件-CompA-1</h4>
            <input type="text" @click.stop @focus="$emit('focus')">
        </div>
        <div class="box" v-bind="$attrs">
            <h4>子组件-CompA-2</h4>
        </div>
    </script>

    <script type="module">
        import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
        createApp({
            data(){
                return {
                
                }
            },
            methods: {
                updateMsg(){
                    console.log("父组件:updateMsg")
                }
            },
        })
        .component("CompA",{
            template:"#compA",
            // emits:["update:msg"]
            emits:["focus"]
        })
        .mount("#app")
    </script>

非父子组件数据传递

1、借助共有顶级组件

Vue 组件构成结构无论多复杂,都必须基于 Vue.createApp() 开始项目构成,
因此 在同一个构成结构中,组件间必然存在一个共同的上层组件
借助上层组件,使用 => 子、子 => 数据技术,进行层层数据传递

  vm.$root 用于执行当前vue程序运行时 createApp 构建启动根组件(ROOT组件)

        ==> 小范围可以使用,但不要大量使用

          + 根组件仓库臃肿

          + 组件数据独立性消失

          + 可能会产生副作用的响应式

 <div id="app">
        <h4>父组件</h4>
        <p>msg: <span class="data">{{ msg }}</span> </p>
        <hr>
        <comp-a></comp-a>
        <comp-b></comp-b>
    </div>

    <script type="text/x-template" id="compA">
        <div class="box">
            <h4>子组件-CompA</h4>
            <p>msg: <span class="data">{{ $root.msg }}</span> </p>
        </div>
    </script>
    <script type="text/x-template" id="compB">
        <div class="box">
            <h4>子组件-compB</h4>
            <p>msg: <span class="data">{{ $root.msg }}</span> </p>
            <input type="text" v-model="$root.msg">
        </div>
    </script>

    <script type="module">
        import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";

        const CompA = {
            template:"#compA",
            methods: {
                aaa(){
                    this.$root.$on("aaa",function(){})
                }
            },
        }

        const CompB = {
            template:"#compB",
            created() {
                this.$root.msg = "组件B赋值的初始数据"
            },
        }

        createApp({
            components:{
                CompA,CompB
            },
            data(){
                return {
                    msg:""
                }
            }
        })
        .mount("#app")
    </script>

2、中央事件总线(event bus)

中央事件总线是基于在组件中可访问的 事件管理器 作为跳板,以方法定义和方法
执行的位置变化,通过形参和实参方式传递数据
Tips:
2.x 中, Vue 实例可用于触发由事件触发器 API 通过指令式方式添加
的处理函数 ( $on $off $once ) 。这可以用于创建一个事件总线。
3.x 中, Vue 完全移除了 $on $off $once 方法,但可以通过
三方模块 mitt tiny - emitter 实现事件总线。
 <div id="app">
        <h4>父组件</h4>
        <hr>
        <comp-b></comp-b>
        <comp-a></comp-a>
    </div>

    <script type="text/x-template" id="compA">
        <div class="box">
            <h4>子组件-CompA</h4>
            <p>msg: <span class="data">{{ msg }}</span> </p>
        </div>
    </script>
    <script type="text/x-template" id="compB">
        <div class="box">
            <h4>子组件-compB</h4>
            <p>msg: <span class="data">{{ msg }}</span> </p>
            <input type="text" v-model="msg">
        </div>
    </script>

    <script src="../../assets/miit/mitt.umd.js"></script>
    <script type="module">
        import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";

        const eventBus = mitt();
        console.log(eventBus);

        const CompA = {
            template:"#compA",
            data() {
                return {
                    msg:""
                }
            },
            created() {
                eventBus.on("update:msg",(nv)=>{
                    this.msg = nv;
                })
            },
        }

        const CompB = {
            template:"#compB",
            data(){
                return {
                    msg:"组件B的msg"
                }
            },
            watch:{
                msg(){
                    eventBus.emit("update:msg",this.msg)
                }
            },
           
            mounted() {
                console.log("compB====>");
                console.log(eventBus.all);
                eventBus.emit("update:msg",this.msg)
                console.log("<====compB");
            },
        }

        createApp({
            components:{
                CompA,CompB
            }
        })
        .mount("#app")
    </script>

3、组件的生命周期执行顺序

当项目由多个组件,进行嵌套方式完成页面构成,每个组件在项目运行时都会
立执行生命周期
因项目由多个组件组成,因此组件生命周期执行具有 先后关系

 

总结
1. 嵌套组件 beforeMount 之前包含 beforeMount 生命周期, 由页面定义顺序
由上而下,由外而内进行执行
2. 当项目中所有的 beforeMount 生命周期函数执行完成, vue 将以 元素定义
的顺序有上而下,有内而外依次执行 mounted 函数,根组件的 mounted
永远最后执行
3. 由上述两个结论可知,在组件化构成的 vue 结构中,所有的组件都
beforeMount 都将优先于 mounted 执行
应用:中央事件总线的 事件绑定和触发的定义描述
beforeMount 优先于 mounted 函数执行,所以可以在
beforeMount 完成事件绑定,保证无论什么组件结构,事件绑定
是先执行
mounted 中完成事件触发,保证在触发事件时,事件已经完成了绑
定操作

单向数据流&组件双向数据共享

1、单向数据流

单向数据流实际上只是一种框架语法限制特性的描述
Vue 组件间所有的父子 prop 之间形成了一个 单向下行绑定
父级 prop 的更新会 主动 向下流动到子组件中,但是反过来则不行。
防止从子组件意外改变父级组件的数据状态,从而导致你的应用的数据流向
难以理解。
有上述框架限制也进一步的描述了 props 拦截的变量为只读变量的特性

 

2、计算属性的双向共享操作

技术原理:通过计算属性整合 => 子、子 => 的数据传递特性,同时对一个变
量进行数据操作,从而实现组件间的双向数据共享

3、组件自定义v-model

vue 组件构成中通过属性 model 的配置可以快速完成,组件间共享数据的快速装
载和定义

 Vue的v-model绑定在组件标签上和绑定在表单标签上功能一致,用于完成属性绑定+事件绑定

            对组件标签而言:

                属性绑定 针对的属性 是v-model指令 : 所描述的属性

                事件绑定 v-model的:后面的变量名, 和固定关键字update:, 组成自定义事件名 update:变量名,

                        在vue组件标签上进行绑定

                例如  <itany-test v-model:title="取值变量" >

                ==>   <itany-test :title="取值变量" @update:title="取值变量=$event" >

               

                v-model 在组件标签上进行定义时,也可以不用定义 :变量名  【注意:一个组件标签只能定义一个】

                       v-model无:参数定义在组件标签上,属于默认属性 modelValue 的双向操作

 <div id="app">
        <h4>父组件</h4>
        <p>info:{{ info }}</p>
        <input type="text" v-model="info">
        <p>text:{{ text }}</p>
        <input type="text" v-model="text">
        <!-- v-model: 语法只在组件标签有效  vue3+ -->
        <comp-a v-model:msg="info" v-model="text"></comp-a>
    </div>

    <script type="text/x-template" id="compA">
        <div class="box">
            <h5>组件</h5>
            <p>msg:{{ msg }}</p>
            <input type="text" v-model="argMsg">
            <hr>
            <p>modelValue:{{ modelValue }}</p>
            <input type="text" v-model="argModelValue">

        </div>
    </script>
    <script type="module">
        import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
        createApp({
            data(){
                return {
                    info:"父组件数据info",
                    text:"父组件数据text"
                }
            }
        })
        .component("CompA",{
            template:"#compA",
            props:["msg","modelValue"],
            emits:["update:msg","update:modelValue"],
            computed:{
                argMsg:{
                    get(){
                        return this.msg
                    },
                    set(nv){
                        this.$emit("update:msg",nv)
                    }
                },
                argModelValue:{
                    get(){
                        return this.modelValue
                    },
                    set(nv){
                        this.$emit("update:modelValue",nv)
                    }
                }
            }
        })
        .mount("#app")
    </script>

4JS引用类型的双向共享操作

技术原理:采用 JS 内存中变量存储时,引用类型堆栈分离构建的地址指向特性,
实现数据的双向共享

实际开发中请小心使用

            + 基于JS引用地址指向的特性,可以在不该变props变量的情况下,修改引用地址指向的堆中数据,

              但这类操作是违背单向数据流的

            + 在vue继承的UI组件库中,为了实现UI组件库的高效应用,UI组件库存在大量基于JS引用方式得回传数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值