Vue2與Vue3的元件通訊對比

403·Forbidden發表於2021-12-19

Vue2

父傳子

父傳子比較簡單, 主要通過以下步驟實現

  1. 父在template中為子繫結屬性

    <Child :childData='pMsg'/>
    <!-- 也可以寫死 -->
    <Child childData='123'/>
    
  2. 子用props接收資料, props的值可以是陣列或物件

    props: ["childData"]
    
  3. 子在template中或其他地方任意使用接受到的資料

    <h2>我得到了{{childData}}</h2>
    

列出完整例子:

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
    </style>
  </head>
  <body>
    <div id="app">{{ message }}</div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  </body>
</html>

<!--@html-end-->

<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->

<!--@javascript-start-->
Vue.component("Parent", {
  data() {
    return {
      pMsg: "小樓昨夜又東風",
    };
  },
  //步驟一
  template: `<div><fieldset><legend>父元件</legend><input type="text" v-model="pMsg"/><Child :childData='pMsg'/></fieldset></div>`,
});
Vue.component("Child", {
  //步驟三
  template: `<div><fieldset><legend>子元件</legend>來自父元件的資料: {{ childData }}</fieldset></div>`,
  //步驟二
  props: ["childData"],
});
var vm = new Vue({
  el: "#app",
  data() {
    return {
      msg: "往input中輸入東西試試",
    };
  },
  template: `<div><fieldset><legend>App元件</legend>{{ msg }}<Parent/></fieldset></div>`,
});
<!--@javascript-end-->

子傳父

  1. 父元件中為子元件繫結一個自定義事件
    <h2> <Child @childHandler="childHandler" /></h2>`
    
  2. 父元件中為自定義事件寫函式,形參為要接收的值,假如要加到this中的話,最好在data中預留一個key
    methods: {
    	childHandler(val) {
    		this.ChildData = val
    	}
    }
    
  3. 子元件中繫結一個原生事件
    @input="change(data)"
    
    再在方法中使用$emit呼叫父元件中的方法
    this.$emit("childHandler", val)
    
    觸發父元件中的自定義事件

    $emit: 觸發當前例項上的事件。附加引數都會傳給監聽器回撥

完整例子:

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
  </head>
  <body>
    <div id="app">{{ message }}</div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  </body>
</html>

<!--@html-end-->
<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->
<!--@javascript-start-->
Vue.component("Parent", {
  data() {
    return {
      ChildData: "",
    };
  },
  //步驟一
  template: `<div><fieldset><legend>父元件</legend><p>來自子元件的資料: {{ ChildData }}</p><Child @childHandler="childHandler" /></fieldset></div>`,
  // 步驟二
  methods: {
    // 處理從子元件中獲取的資料
    childHandler(val) {
      this.ChildData = val;
    },
  },
});
Vue.component("Child", {
  data() {
    return {
      data: "故國不堪回首月明中",
    };
  },
  //步驟三
  template: `<div><fieldset><legend>子元件</legend><input type="text" v-model="data" @input="change(data)" /></fieldset></div>`,
  methods: {
    // 呼叫$emit方法
    change(val) {
      this.$emit("childHandler", val);
    },
  },
});
var vm = new Vue({
  el: "#app",
  data() {
    return {
      msg: "在input中輸入東西試試",
    };
  },
  template: `<div><fieldset><legend>App元件</legend>{{msg}}</h1><Parent/></fieldset></div>`,
});

<!--@javascript-end-->

父傳孫

父元件裡使用provide, 子元件裡使用inject
完整例子

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
  </head>
  <body>
    <div id="app">{{ message }}</div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  </body>
</html>
<!--@html-end-->

<!--@javascript-start-->
Vue.component("Parent", {
  data() {
    return { data: "小樓昨夜又東風" };
  },
  template: `<div><fieldset><legend>父元件</legend><p>父元件資料: {{ data }}</p><Child /></fieldset></div>`,
  // 步驟一
  provide() {
    return {
      data: this.data,
    };
  },
});
Vue.component("Child", {
  template: `<div><fieldset><legend>子元件</legend><GrandSon /></fieldset></div>`,
});
Vue.component("GrandSon", {
  // 步驟二
  // 接收祖輩的資料 data
  inject: ["data"],
  data() {
    return {
      // 通過this.x取值
      parentData: this.data,
    };
  },
  template: `<div><fieldset><legend>孫元件</legend><p>祖輩的資料: {{ parentData }}</p></fieldset></div>`,
});
var vm = new Vue({
  el: "#app",
  data() {
    return {
      msg: "觀察元件的資料",
    };
  },
  template: `<div><fieldset><legend>App元件</legend><p>{{ msg }}</p><Parent/></fieldset></div>`,
});

<!--@javascript-end-->
<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->

注意, 這種方法傳值不是響應資料
你可以把資料變為object型別, 讓其可以同步修改

兄弟之間互傳

  1. 在Vue的原型物件向上新增一個屬性叫$bus
    該屬性是一個Vue例項物件
  2. 傳送端, 呼叫this.$bus.$emit
  3. 接收端, 監聽對應事件, 處理資料

完整例子:

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
  </head>
  <body>
    <div id="app">{{ message }}</div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  </body>
</html>
<!--@html-end-->
<!--@javascript-start-->
// 步驟一 新增$bus屬性
Vue.prototype.$bus = new Vue();

Vue.component("Child1", {
  data() {
    return { data: "小樓昨夜又東風" };
  },
  methods: {
    update() {
      // 步驟二 使用$emit觸發自定義事件, 傳入資料
      this.$bus.$emit("handlerData", this.data);
    },
  },
  template: `<div><fieldset><legend>子元件</legend><p>子元件傳送的資料:  <input type="text" v-model="data" @input="update()"/></p></fieldset></div>`,
});
Vue.component("Child2", {
  data() {
    return {
      data: "",
    };
  },
  mounted() {
    // 步驟三 處理傳過來的資料
    this.$bus.$on("handlerData", (val) => {
      this.data = val;
    });
  },
  template: `<div><fieldset><legend>子元件</legend><p>子元件接收的資料: {{ data }}</p></fieldset></div>`
});
var vm = new Vue({
  el: "#app",
  data() {
    return {
      msg: "往input中輸入資料試試",
    };
  },
  template: `<div><fieldset><legend>App元件</legend><p>{{msg}}</p><Child1 /> <Child2 /></fieldset></div>`,
});
<!--@javascript-end-->
<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->

Vue3

由於vue3vue2的選項變為了組合API, 而且把datamethods集合到了setup中, 故而使用起來有所區別, 但也大差不差

父傳子

  1. 父元件使用refreactive將資料變為響應資料
  2. 子元件使用props接收

    關於props見: props

  3. 要在setup中使用, 使用如下方法:
    props: ["data"],
    setup(props, context) {
      props.data
    }
    

完整例子

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
  </head>
  <body>
    <div id="app">
      <fieldset>
        <legend>app元件</legend>
        {{ data }}
        <!-- 使用元件 -->
        <Parent />
      </fieldset>
    </div>

    <script src="https://unpkg.com/vue@3.2.26/dist/vue.global.js"></script>
  </body>
</html>
<!--@html-end-->
<!--@javascript-start-->
const AttributeBindingApp = {
  name: "App",
  setup() {
    const data = "往input中輸入東西試試";
    return {
      data,
    };
  },
};
const app = Vue.createApp(AttributeBindingApp);
app.component("Parent", {
  setup() {
    // 變為響應資料
    const parentData = Vue.ref("故國不堪回首月明中");
    return {
      parentData,
    };
  },
  template: `<fieldset><legend>父元件</legend> <input type="text" v-model="parentData" /> <Child :parentData="parentData" /></fieldset>`,
});
app.component("Child", {
  props: ["parentData"],
  setup() {
    const childData = "childData";

    return {
      childData,
    };
  },
  template: `<fieldset><legend>子元件</legend>{{ parentData }}</fieldset>`,
});
app.mount("#app");

<!--@javascript-end-->
<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->

子傳父

  1. 父元件中定義接收資料的方法
  2. template中為子元件繫結自定義事件
  3. 在子元件中觸發自定義事件, 執行context.emit方法
  4. 傳給父元件使用

總的來說, 原理與Vue2差不多, 但由於要在setup中獲取值, 故要使用引數接收

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
  </head>
  <body>
    <div id="app">
      <fieldset>
        <legend>app元件</legend>
        {{ data }}
        <!-- 使用元件 -->
        <Parent />
      </fieldset>
    </div>

    <script src="https://unpkg.com/vue@3.2.26/dist/vue.global.js"></script>
  </body>
</html>
<!--@html-end-->
<!--@javascript-start-->
const AttributeBindingApp = {
  name: "App",
  setup() {
    const data = "往input中輸入東西試試";
    return {
      data,
    };
  },
};
const app = Vue.createApp(AttributeBindingApp);
app.component("Parent", {
  setup(props, context) {
    const childData = Vue.ref("");
    // 步驟一 定義處理接收資料的方法
    const receive = (e) => {
      // 處理從子元件中傳來的資料

      childData.value = e;
    };
    return {
      receive,
      childData,
    };
  },
  // 步驟二 自定義事件 觸發處理接收資料的方法
  template: `<fieldset><legend>父元件</legend><p>子元件中的資料: {{ childData }}</p><Child @inputText="receive" /></fieldset>`,
});
app.component("Child", {
  props: ["parentData"],

  setup(props, context) {
    const data = Vue.ref("小樓昨夜又東風");
    // 步驟四 呼叫context.emit
    const toParent = () => {
      // input時呼叫
      // 呼叫inputText事件

      context.emit("inputText", data.value);
    };
    return {
      data,
      toParent,
    };
  },
  // 步驟三 觸發事件
  template: `<fieldset><legend>子元件</legend><input type="text" @input="toParent" v-model="data" /></fieldset>`,
});
app.mount("#app");
<!--@javascript-end-->
<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->

父傳孫

和vue2一樣, 同樣使用provideinject
但不同的是, 我們可以使用refreactive將資料轉換為響應式資料

<!--@html-start-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>title</title>
  </head>
  <body>
    <div id="app">
      <fieldset>
        <legend>app元件</legend>
        {{ data }}
        <!-- 使用元件 -->
        <Parent />
      </fieldset>
    </div>

    <script src="https://unpkg.com/vue@3.2.26/dist/vue.global.js"></script>
  </body>
</html>

<!--@html-end-->
<!--@javascript-start-->
const AttributeBindingApp = {
  name: "App",
  setup() {
    const data = "往兩個input中都輸入試試";
    return {
      data,
    };
  },
};
const app = Vue.createApp(AttributeBindingApp);
app.component("Parent", {
  setup() {
    // 響應的資料
    const data = Vue.ref("");
    // 步驟一 使用provide
    // 把data 標記為 "parentData"
    Vue.provide("parentData", data);
    return {
      data,
    };
  },
  template: `<fieldset><legend>父元件</legend>從子孫輩中獲取的資料: <input type="text" v-model="data" /> <Child /></fieldset>`,
});
app.component("Child", {
  template: `<fieldset><legend>子元件</legend><GrandSon /></fieldset>`,
});
app.component("GrandSon", {
  setup() {
    // 步驟二 接收資料
    // 接收 parentData
    const data = Vue.inject("parentData");
    return {
      data,
    };
  },
  template: `<fieldset><legend>孫元件</legend><p>從父輩中獲取的資料: <input type="text" v-model="data" /></p></fieldset>`,
});
app.mount("#app");

<!--@javascript-end-->

<!--@css-start-->
fieldset {
	margin-top: 30px;
}
<!--@css-end-->

相關文章