這篇文章來記錄一下,當父層元件傳遞資料給子層元件的語法:props
我們已經知道在模版中要使用元件,會是這樣的語法:
<div id="app">
<my-component></my-component>
</div>
要讓元件有辦法接收變數,需要先在元件中多加一個 props
屬性:
app.component("my-component", {
template: `
<div>
<div> {{ message }} </div>
</div>`,
// message 是自己定義的變數
props: ["message"],
setup() {},
});
這段定義好之後,就可以在父層的模板,把值傳到這個參數中,以上面定義的 message
參數為例子:
<div id="app">
<my-component message="簡單的小情歌"></my-component>
</div>
完整的程式可以到 這裡看
上面的例子可以看出, props
的寫法就好像在定義 HTML 標籤的屬性,既然想到 HTML 屬性就不能不提到一個指令 v-bind
沒錯, props
如果搭配 v-bind
,就可以在父層傳入變數給子層渲染。
如果父層的資料有更變,元件的資料也是有辦法響應式的更動,十分方便!
<div id="app">
<my-component :message="msg"></my-component>
<button @click="changeData">更換資料</button>
</div>
const app = Vue.createApp({
setup() {
const msg = Vue.ref("這是一筆資料");
const changeData = () => {
msg.value = "這是抽換的資料";
};
return {
msg,
changeData,
};
},
});
app.component("my-component", {
template: `
<div>
<div> {{ message }} </div>
</div>`,
// message 是自己定義的變數
props: ["message"],
});
app.mount("#app");
可以到這玩玩看。
上面 props
的宣告是採用 陣列 宣告,由於陣列並不是 key-value 的方式紀錄資料,使得這種宣告方式只能宣告名稱,沒辦法對 props
的需求定義得更細一點。
Vue 同時也提供 物件 的 props
宣告方式:
app.component("my-component", {
template: `
<div>
<h1> {{ name }} </h1>
<p> {{ age }} </p>
</div>`,
// 以物件定義 props ,可以宣告資料的型別
props: {
name: String,
phone: Number,
},
});
用上面的方法,就可以再定義 props
的「型別」,這個型別是給開發人員看的,讓我們開發時可以迅速得知資料可能呈現的內容。
以上面例子來說,如果 phone
參數傳入了 非 Number 類型的資料:
<div id="app">
<my-component name="imall" phone="一段文字"></my-component>
</div>
那開發者工具會跑出這樣的訊息:
從圖中可以注意到,Vue 並不會因為定義的型別,與實際的型別不同而無法渲染,他只是跳出一個 warning
,跟我們說型別好像不太對勁!
如果一個元件定義了太多資料,要從父層傳資料到元件,用上面的語法可能會寫一脫拉庫的內容:
<div id="app">
<my-component
name="imall"
phone="0987654321"
:age="18"
hobby="釣魚"
img="https://XXX.XXX.XXX"
...
...
...
></my-component>
</div>
我們都知道在模版中寫太多資料,很容易造成維護的困難,所以 Vue 為了這種狀況提供一種方式給我們處理:定義一個物件,使用 v-bind
把資料傳進去。
舉例來說,元件的內容長這樣:
app.component("my-component", {
template: `
<div>
<h1> {{ name }} </h1>
<p> {{ age }} </p>
</div>`,
props: {
name: String,
age: Number,
},
});
父層可以這樣定義變數:
const app = Vue.createApp({
setup() {
// 這是預計要傳入元件的資料
const msg = {
name: imall,
age: 18,
};
return {
msg,
};
},
});
接著在模板這麼寫,資料就會自動在元件解構:
<div id="app">
<my-component v-bind="msg"></my-component>
</div>
可以到這邊玩玩看。
元件能接收外部資料後,資料不一定每次都是直接渲染在模板上而不加工,總會有程式需要使用的狀況。
例如從外部透過元件的 props
傳入 ID ,每個元件拿到不同 ID 後去呼叫 API 取得資料這類的行為。
要在元件中取得資料,需要在元件的 setup
傳入 props
參數,就可以用物件取值的方式抓到元件的資料了。
app.components("my-component", {
template: `
<div>
<h1> {{ name }} </h1>
<p> {{ age }} </p>
</div>`,
props: {
name: String,
age: Number,
},
// 這裡傳入 props 參數
setup(props) {
console.log(props.name);
},
});
語法寫完之後,props 最重要的觀念,就是 props 應該要是一個 readonly
只能讀的資料。
也就是我們不可以在子元件更改 props 的資料,像是這樣的做法:
app.components("my-component", {
template: `
<div>
<h1> {{ name }} </h1>
<p> {{ age }} </p>
</div>`,
props: {
name: String,
age: Number,
},
setup(props) {
props.name = props.name.toUpperCase();
},
});
這是因為,假如我們定義了十個元件好了,每個元件都能使用父層的某一個變數,然後父層還真的一次使用十個元件,並且傳入這個變數。
我們已經知道 props
會因為父層資料的異動產生響應的改變,如果子層能亂改資料的話,上面那個情境中,只要一個子元件改動資料,其餘十個元件的資料都會被更改。
雖然有些情況可能會是個蠻方便的特性,但絕大多數的情況只會造成資料的難以追溯。
但實際的情境中,或多或少還是會有需要對父層傳入的 props
進行加工,再渲染的可能,有這種情況的話,官方建議多加一個 computed
:
app.components("my-component", {
template: `
<div>
<h1> {{ name }} </h1>
<p> {{ age }} </p>
</div>`,
props: {
name: String,
age: Number,
},
setup(props) {
// 應該使用 computed 把 props 包裝起來之後再使用
const UpperCaseName = Vue.computed(() => props.name.toUpperCase());
return {
UpperCaseName,
};
},
});
我們不直接更改 props
的資料,而是多包一層 computed
,把加工的內容回傳出來使用,這樣才能避免從元件內更改資料後,直接到影響元件外部的資料,導致變動難以追蹤的後果。
這篇文章紀錄了從外層傳入資料給元件的語法 props
,以及一個重要的觀念:props
應該要是 readonly
,不可在元件內部隨意改動 props
的資料。
知道不能透過 props
來改動內部的資料,造成外部資料變動的概念之後,接著大概會遇到另一個問題:如果元件「需要」外部的資料變動怎麼辦?
下一篇文章預計來記錄,透過 emit
去告訴元件外的內容更改傳入的資料。