新聞中心
前言
幾年前,我決定試著分別在 React 和 Vue 中構(gòu)建一個(gè)相當(dāng)標(biāo)準(zhǔn)的 To Do(待辦事項(xiàng))應(yīng)用。這兩個(gè)應(yīng)用都是使用默認(rèn)的 CLI 構(gòu)建的(React 的 create-react-app 和 Vue 的 vue-cli)。我想盡量保持中立,通過(guò)這樣的例子來(lái)告訴大家這兩種技術(shù)執(zhí)行特定任務(wù)是是怎樣做的。

創(chuàng)新互聯(lián)公司專(zhuān)注于企業(yè)網(wǎng)絡(luò)營(yíng)銷(xiāo)推廣、網(wǎng)站重做改版、合肥網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5響應(yīng)式網(wǎng)站、成都做商城網(wǎng)站、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性?xún)r(jià)比高,為合肥等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
當(dāng) React Hooks 發(fā)布時(shí),我為這篇文章更新了“2019 版”,用函數(shù)式 Hooks 取代了類(lèi)組件。隨著 Vue 3 及其組合(Composition)API 的發(fā)布,現(xiàn)在是時(shí)候更新這篇文章的“2020 版”了。
2019 版:我用 React 和 Vue 構(gòu)建了同款應(yīng)用,來(lái)看看哪里不一樣
先來(lái)大致看一下兩款應(yīng)用的外觀(guān):
兩款應(yīng)用的 CSS 代碼完全相同,但代碼所處的位置有所不同。記住這一點(diǎn),接下來(lái)讓我們看一下它們的文件結(jié)構(gòu):
你會(huì)發(fā)現(xiàn)它們的結(jié)構(gòu)也幾乎相同。唯一的區(qū)別是 React 應(yīng)用有兩個(gè) CSS 文件,而 Vue 應(yīng)用沒(méi)有任何 CSS 文件。這是因?yàn)樵?create-react-app 中,默認(rèn)每個(gè) React 組件都會(huì)附帶一個(gè)單獨(dú)文件來(lái)保存其樣式,而 Vue CLI 用單一的文件來(lái)為默認(rèn)組件包含 HTML、CSS 和 JavaScript。
最后它們倆都達(dá)成了同樣的目標(biāo),也沒(méi)什么可多說(shuō)的,因?yàn)樵?React 或 Vue 中你都不能改變文件結(jié)構(gòu)。選擇哪個(gè)確實(shí)取決于個(gè)人喜好。開(kāi)發(fā)社區(qū)關(guān)于 CSS 的結(jié)構(gòu)化方式這個(gè)話(huà)題有大量的討論,尤其是 React 這塊,因?yàn)橛性S多 CSS-in-JS 解決方案,諸如樣式化組件和 emotion 等。順便說(shuō)一句,CSS-in-JS 就是字面上的意思。雖然這些都很有用,但這里我們只用兩邊的 CLI 給出的結(jié)構(gòu)。
在進(jìn)一步深入之前,我們先來(lái)看一下典型的 Vue 和 React 組件長(zhǎng)什么樣:
典型的 React 文件:
典型的 Vue 文件:
看過(guò)之后我們來(lái)深入了解細(xì)節(jié)吧!
我們?nèi)绾瓮蛔償?shù)據(jù)?
首先,“突變數(shù)據(jù)”到底是什么意思呢?聽(tīng)起來(lái)是不是有點(diǎn)高深?其實(shí)它基本上就是指更改我們已存儲(chǔ)的數(shù)據(jù)。如果我們想將一個(gè)人名的值從 John 更改為 Mark,我們就是在“突變“這份數(shù)據(jù)。這就是 React 和 Vue 之間的關(guān)鍵區(qū)別所在。Vue 本質(zhì)上創(chuàng)建了一個(gè)數(shù)據(jù)對(duì)象,可以在其中自由更新數(shù)據(jù),而 React 通過(guò)所謂的狀態(tài) Hook 來(lái)處理數(shù)據(jù)突變。
從下面的圖片中可以看到兩者的設(shè)置,然后我們會(huì)具體說(shuō)明:
React 狀態(tài):
Vue 狀態(tài):
于是你看到我們將相同的數(shù)據(jù)傳遞給了兩者,但各自的結(jié)構(gòu)有所不同。
在 React 中,至少?gòu)?2019 年開(kāi)始,我們一般會(huì)通過(guò)一系列 Hooks 處理狀態(tài)。你可能以前沒(méi)接觸過(guò)這種概念,一開(kāi)始它看起來(lái)可能有點(diǎn)奇怪。它的工作機(jī)制基本上是這個(gè)樣子:
假設(shè)我們要?jiǎng)?chuàng)建一個(gè)待辦事項(xiàng)列表,我們可能需要?jiǎng)?chuàng)建一個(gè)名為 list 的變量,它可能需要接收一個(gè)由字符串或?qū)ο蠼M成的數(shù)組(比如說(shuō)給每個(gè) todo 字符串一個(gè) ID 或其他一些東西)。我們需要寫(xiě)的代碼是const [list, setList] = useState([])。這里我們用的就是 React 里面的 Hook,稱(chēng)為 useState。它本質(zhì)上是讓我們能夠在組件中保留局部狀態(tài)。
另外,你可能已經(jīng)注意到我們?cè)?useState() 內(nèi)部傳入了一個(gè)空數(shù)組 []。放在其中的是我們希望 list 最初設(shè)置的內(nèi)容,這里我們希望是一個(gè)空數(shù)組。但從上圖可以看到,我們?cè)跀?shù)組內(nèi)傳入了一些數(shù)據(jù),這些數(shù)據(jù)最后成了 list 的初始化數(shù)據(jù)。想知道 setList 是做什么的?稍后會(huì)進(jìn)一步說(shuō)明!
在 Vue 中,通常會(huì)將組件的所有突變數(shù)據(jù)放置在一個(gè) setup() 函數(shù)內(nèi),該函數(shù)返回一個(gè)對(duì)象,其中包含要公開(kāi)的數(shù)據(jù)和函數(shù)(就是那些你要在應(yīng)用中使用的東西)。你會(huì)注意到,應(yīng)用中的每個(gè)狀態(tài)數(shù)據(jù)(也就是我們希望能夠突變的數(shù)據(jù))都包裝在一個(gè) ref() 函數(shù)內(nèi)部。這個(gè) ref() 函數(shù)是我們從 Vue 導(dǎo)入的,可讓我們的應(yīng)用在這些數(shù)據(jù)更改 / 更新時(shí)完成更新。簡(jiǎn)而言之,如果你想在 Vue 中創(chuàng)建突變數(shù)據(jù),請(qǐng)為 ref() 函數(shù)分配一個(gè)變量,并在其中放入默認(rèn)數(shù)據(jù)。
如何在應(yīng)用中引用突變數(shù)據(jù)?
假設(shè)我們有一些數(shù)據(jù)名為 name,被分配了 Sunil 值。
在 React 中,由于我們使用 useState() 創(chuàng)建了較小的狀態(tài),因此很可能已經(jīng)用const [name, setName] = useState('Sunil')創(chuàng)建了一些東西。在應(yīng)用中,我們將簡(jiǎn)單地調(diào)用 name 來(lái)引用同一段數(shù)據(jù)。這里的主要區(qū)別在于我們不能簡(jiǎn)單地寫(xiě)上name = 'John',因?yàn)?React 有一些限制來(lái)預(yù)防這種簡(jiǎn)單且無(wú)所顧忌的突變。在 React 中,我們要寫(xiě)成setName('John')。這里用到了 setName。在const [name, setName] = useState('Sunil')中,它創(chuàng)建兩個(gè)變量,一個(gè)變量變?yōu)閏onst name = 'Sunil',而第二個(gè) const setName 被分配了一個(gè)函數(shù),該函數(shù)使 name 可以用新值重新創(chuàng)建。
在 Vue 中,它位于 setup() 函數(shù)內(nèi)部,并且被稱(chēng)為const name = ref('Sunil')。在應(yīng)用中,我們將調(diào)用 name.value 來(lái)引用它。如果要使用在 ref() 函數(shù)內(nèi)部創(chuàng)建的值,我們將在變量上尋找.value 而不是簡(jiǎn)單地調(diào)用該變量。換句話(huà)說(shuō),如果我們想要一個(gè)持有狀態(tài)的變量值,我們將尋找 name.value 而不是 name。如果要更新 name 的值,可以通過(guò)更新 name.value 來(lái)完成。例如,假設(shè)我想將我的名字從 Sunil 更改為 John, 可以寫(xiě)name.value = "John"來(lái)做到這一點(diǎn)。
實(shí)際上,React 和 Vue 在這里做的是同樣的事情,也就是創(chuàng)建可以更新的數(shù)據(jù)。Vue 本質(zhì)上會(huì)在每次更新一條包裝在 ref() 函數(shù)內(nèi)的數(shù)據(jù)是默認(rèn)結(jié)合它自己的 name 和 setName 版本。React 要求你使用內(nèi)部值調(diào)用 setName() 來(lái)更新?tīng)顟B(tài),而如果你曾嘗試更新數(shù)據(jù)對(duì)象內(nèi)部的值,Vue 就會(huì)假設(shè)你要這么做。那么為什么 React 會(huì)費(fèi)勁地將值與函數(shù)分開(kāi),還要使用 useState() 呢?這是因?yàn)楫?dāng)狀態(tài)改變時(shí),React 希望重新運(yùn)行某些生命周期 Hooks。在我們的例子中,當(dāng)你調(diào)用 setName() 時(shí),React 會(huì)知道有些狀態(tài)已更改,所以可以運(yùn)行它們的生命周期 Hooks。如果你直接改變狀態(tài),React 將不得不做更多的工作來(lái)跟蹤更改以及要運(yùn)行的生命周期 Hooks 等。
現(xiàn)在我們已經(jīng)搞明白了數(shù)據(jù)突變,接下來(lái)看看在兩個(gè) To Do 應(yīng)用中添加新項(xiàng)目的方法。
我們?nèi)绾蝿?chuàng)建新的待辦事項(xiàng)?
React:
- const createNewToDoItem = () => {
- const newId = generateId();
- const newToDo = { id: newId, text: toDo };
- setList([...list, newToDo]); setToDo("");
- };
在 React 你是怎么做的?
在 React 中,我們的輸入字段有一個(gè)名為 value 的屬性。每次通過(guò) onChange 事件偵聽(tīng)器更改它的值時(shí),都會(huì)自動(dòng)更新此值。JSX(基本上是 HTML 的變體)如下所示:
- type="text"
- placeholder="I need to..."
- value={toDo}
- onChange={handleInput}
- onKeyPress={handleKeyPress}
- />
每次更改值時(shí),它都會(huì)更新?tīng)顟B(tài)。handleInput 函數(shù)如下所示:
- const handleInput = (e) => {
- setToDo(e.target.value);};
現(xiàn)在,每當(dāng)用戶(hù)按下頁(yè)面上的 + 按鈕添加新項(xiàng)目時(shí),都會(huì)觸發(fā)createNewToDoItem 函數(shù)。我們?cè)賮?lái)看一下這個(gè)函數(shù),搞清楚具體發(fā)生了什么:
- const createNewToDoItem = () => {
- const newId = generateId();
- const newToDo = { id: newId, text: toDo };
- setList([...list, newToDo]); setToDo("");
- };
本質(zhì)上,newId 函數(shù)是在創(chuàng)建一個(gè)新 ID,該 ID 將提供給我們的新 toDo 項(xiàng)目。newToDo 變量是一個(gè)對(duì)象,有一個(gè) id 鍵,其值由 newID 確定。它還有一個(gè) text 鍵,其值由 toDo 確定。這個(gè) toDo 就是輸入值更改時(shí)要更新的那個(gè) toDo。
setList 函數(shù)到此為止,然后我們傳入一個(gè)包含整個(gè) list 以及新創(chuàng)建的 newToDo 的數(shù)組。
你可能覺(jué)得…list 看起來(lái)很奇怪:開(kāi)頭的三個(gè)點(diǎn)稱(chēng)為 spread 運(yùn)算符,負(fù)責(zé)將 list 中的所有值作為單獨(dú)的項(xiàng)目傳遞,而不是簡(jiǎn)單地把所有項(xiàng)目打包在一起作為數(shù)組傳遞。感覺(jué)有些糊涂嗎?那我強(qiáng)烈建議你仔細(xì)閱讀 spread 運(yùn)算符的相關(guān)介紹,因?yàn)樗苡杏茫?/p>
最后我們運(yùn)行 setToDo() 并傳入一個(gè)空字符串。這樣我們的輸入值為空,可以輸入新的 toDo 了。
Vue:
- function createNewToDoItem() {
- const newId = generateId();
- list.value.push({ id: newId, text: todo.value });
- todo.value = "";
- }
在 Vue 你是怎么做的?
在 Vue 中,我們的 input 字段有一個(gè)稱(chēng)為 v-model 的句柄。這使我們能夠執(zhí)行稱(chēng)為 雙向綁定 的操作。下面來(lái)看一下 input 字段,搞清楚到底發(fā)生了什么:
- type="text"
- placeholder="I need to..."
- v-model="todo"
- v-on:keyup.enter="createNewToDoItem"
- />
V-Model 將這個(gè)字段的輸入與我們?cè)?setup() 函數(shù)上創(chuàng)建的一個(gè)變量相關(guān)聯(lián),然后公開(kāi)為一個(gè)返回對(duì)象內(nèi)的鍵。到目前為止我們還沒(méi)有介紹對(duì)象返回的內(nèi)容,所以先說(shuō)一下,這是我們從 ToDo.vue 內(nèi)部的 setup() 函數(shù)返回的內(nèi)容:
- return {
- list,
- todo, showError, generateId, createNewToDoItem, onDeleteItem, displayError};
這里,list、todo 和 showError 是我們的有狀態(tài)值,而其他所有內(nèi)容都是我們希望能在應(yīng)用其他位置調(diào)用的函數(shù)。在頁(yè)面加載時(shí),我們必須將 todo 設(shè)置為一個(gè)空字符串,例如:const todo = ref("")。如果其中已經(jīng)有一些數(shù)據(jù),例如 const todo = ref("add some text here"):我們的輸入字段將在內(nèi)部已有 add some text here 的情況下加載。不管怎樣,回到空字符串的狀態(tài),無(wú)論我們?cè)谳斎胱侄沃墟I入什么文本都必須綁定到 todo.value。這實(shí)際上就是雙向綁定——輸入字段可以更新 ref() 值,反過(guò)來(lái)后者也可以更新輸入字段。
回顧一下前面的 createNewToDoItem () 代碼塊,可以看到,我們將 todo.value 的內(nèi)容推送到 list 數(shù)組中,然后將前者更新為一個(gè)空字符串。
我們還使用了與 React 示例中相同的 newId() 函數(shù)。
如何從列表中刪除項(xiàng)目?
React:
- const deleteItem = (id) => {
- setList(list.filter((item) => item.id !== id));
- };
在 React 你是怎么做的?
因?yàn)?deleteItem() 函數(shù)位于 ToDo.js 內(nèi),我可以很容易地在 ToDoItem.js 里引用它,首先將 deleteItem () 函數(shù)作為一個(gè) prop,如下所示:
這里首先將該函數(shù)傳遞下去,使其能被子級(jí)訪(fǎng)問(wèn)。然后在 ToDoItem 組件內(nèi)執(zhí)行以下操作:
- -
我要引用位于父組件內(nèi)的函數(shù),只需引用 props.deleteItem。你可能發(fā)現(xiàn)在代碼示例中,我們只寫(xiě)了 deleteItem,而不是 props.deleteItem。這是因?yàn)槲覀兪褂昧艘环N稱(chēng)為 解構(gòu) 的技術(shù),該技術(shù)允許我們獲取 props 對(duì)象的一部分并將其分配給變量。因此在我們的 ToDoItem.js 文件中有以下內(nèi)容:
- const ToDoItem = (props) => {
- const { item, deleteItem } = props;
- }
這為我們創(chuàng)建了兩個(gè)變量,其中一個(gè)稱(chēng)為 item,它被賦予與 props.item 相同的值,而 deleteItem 則根據(jù) props.deleteItem 賦值。我們也可以簡(jiǎn)單地使用 props.item 和 props.deleteItem 來(lái)避免解構(gòu)的操作,但我認(rèn)為這里值得單獨(dú)介紹一下!
Vue:
- function onDeleteItem(id) {
- list.value = list.value.filter(item => item.id !== id);
- }
在 Vue 你是怎么做的?
Vue 需要的方法稍微有一些不同。這里我們必須做三件事:
首先,在我們要調(diào)用函數(shù)元素上:
- -
然后我們必須在子組件(在本例中為 ToDoItem.vue)中創(chuàng)建一個(gè) emit 函數(shù)作為方法,如下所示:
- function deleteItem(id) {
- emit("delete", id);
- }
與此同時(shí)你會(huì)發(fā)現(xiàn),當(dāng)我們?cè)?nbsp;ToDo.vue 中添加 ToDoItem.vue 時(shí),我們實(shí)際上引用了一個(gè) 函數(shù):
這就是所謂的自定義事件偵聽(tīng)器 event-listener。它會(huì)偵聽(tīng)使用字符串“delete”觸發(fā) emit 的所有情況。如果聽(tīng)到此消息,它將觸發(fā)一個(gè)名為onDeleteItem 的函數(shù)。此函數(shù)位于 ToDo.vue 內(nèi)部,而不是在 ToDoItem.vue中。如前所述,此函數(shù)僅過(guò)濾來(lái)自 list.value 數(shù)組內(nèi)的 id。
在這里還需注意的是,在 Vue 示例中,我可以簡(jiǎn)單地將 $emit 部分寫(xiě)在 @click 偵聽(tīng)器中,如下所示:
- -
這樣就能把步驟從 3 步減少到 2 不,選哪個(gè)完全取決于個(gè)人喜好。簡(jiǎn)而言之,React 中的子組件可以通過(guò) props 來(lái)訪(fǎng)問(wèn)父函數(shù)(前提是你要向下傳遞 props,這是相當(dāng)標(biāo)準(zhǔn)的做法,其他 React 工作中也非常常見(jiàn));而在 Vue 中,你需要從子級(jí)發(fā)射事件,這些事件通常會(huì)在父組件內(nèi)部回收。
怎樣傳遞事件偵聽(tīng)器?
React:
針對(duì)簡(jiǎn)單事件(例如單擊事件)的事件偵聽(tīng)器很好做。下面是為創(chuàng)建新的 ToDo 項(xiàng)目的按鈕創(chuàng)建 click 事件的示例:
- +
這里非常簡(jiǎn)單,和在一般的 JS 里處理內(nèi)聯(lián) onClick 差不多。如 Vue 部分所述,設(shè)置一個(gè)事件偵聽(tīng)器來(lái)偵聽(tīng)一下 Enter 鍵的動(dòng)作有點(diǎn)復(fù)雜。這需要由 input 標(biāo)簽處理 onKeyPress 事件,如下:
- type="text"
- placeholder="I need to..."
- value={toDo}
- onChange={handleInput}
- onKeyPress={handleKeyPress}
- />
只要識(shí)別出已按下“enter”鍵,此函數(shù)就觸發(fā)了 createNewToDoItem 函數(shù),如下:
- const handleKeyPress = (e) => {
- if (e.key === "Enter") {
- createNewToDoItem(); }};
Vue:
在 Vue 中寫(xiě)起來(lái)非常直觀(guān)。我們只需使用 @符號(hào),后面是我們想要做的事件監(jiān)聽(tīng)器的類(lèi)型。例如要添加一個(gè) click 事件監(jiān)聽(tīng)器,我們可以編寫(xiě)以下代碼:
- +
注意:@click 實(shí)際上是 v-on:click 的簡(jiǎn)寫(xiě)。Vue 事件偵聽(tīng)器很好用的是你還可以綁定很多東西,例如.once,它可以防止事件偵聽(tīng)器被多次觸發(fā)。在編寫(xiě)處理按鍵的特定事件偵聽(tīng)器時(shí)還有許多捷徑。我發(fā)現(xiàn)在 React 中創(chuàng)建一個(gè)事件偵聽(tīng)器,做到每當(dāng)按下 enter 鍵就創(chuàng)建新的 ToDo 項(xiàng)目,寫(xiě)起來(lái)比較麻煩。在 Vue 中,我只需編寫(xiě):
如何將數(shù)據(jù)傳遞給子組件?
React:
在 React 中,我們將 props 傳遞到子組件的創(chuàng)建位置。如:
;
這里我們看到兩個(gè)傳遞給 ToDoItem 組件的 props。從這里開(kāi)始,我們就可以通過(guò) this.props 在子組件中引用它們。因此要訪(fǎng)問(wèn) item.todo prop 時(shí),我們只需調(diào)用 props.item。你可能已經(jīng)注意到還有一個(gè) key prop(因此從技術(shù)上講,我們實(shí)際上正在傳遞三個(gè) props)。這主要用于 React 的內(nèi)部,因?yàn)樗?jiǎn)化了同一組件的多個(gè)版本之間更新和跟蹤更改的工作(我們這里每個(gè) todo 是 ToDoItem 組件的一個(gè)副本)。確保你的組件具有唯一鍵也很重要,否則 React 會(huì)在控制臺(tái)中發(fā)出警告。
Vue:
在 Vue 中,我們將 props 傳遞到子組件的創(chuàng)建位置。如:
完成此操作后,我們將它們傳遞到子組件的 props 數(shù)組中,如下所示:props: [ "todo" ]。然后它們就可以在子組件中用名稱(chēng)引用——這里的名稱(chēng)就是 todo。如果你不知道在哪里放 prop 鍵,下面是我們的子組件中整個(gè) export default 對(duì)象的樣子:
- export default {
- name: "ToDoItem",
- props: ["item"],
- setup(props, { emit }) { function deleteItem(id) {
- emit("delete", id);
- } return {
- deleteItem, }; },};
你可能注意到 Vue 中遍歷數(shù)據(jù)時(shí),我們實(shí)際上遍歷的是 list 而非 list.value。遍歷后者這里是行不通的。
如何將數(shù)據(jù)發(fā)射回父組件?
React:
我們首先將函數(shù)向下傳遞給子組件,在調(diào)用子組件的位置將其作為 prop 引用。然后我們向子組件的函數(shù)添加調(diào)用,比如說(shuō) onClick 就引用props.whateverTheFunctionIsCalled——或者whateverTheFunctionIsCalled(如果用解構(gòu))。然后將觸發(fā)位于父組件中的函數(shù)。我們可以在“如何從列表中刪除項(xiàng)目”部分中查看全過(guò)程。
Vue:
在子組件中,我們只需要編寫(xiě)一個(gè)將值返回給父函數(shù)的函數(shù)即可。在父組件中我們編寫(xiě)一個(gè)函數(shù),該函數(shù)偵聽(tīng)何時(shí)發(fā)射出該值,然后可以觸發(fā)一個(gè)函數(shù)調(diào)用??梢栽凇叭绾螐牧斜碇袆h除項(xiàng)目”部分中查看全過(guò)程。
終于完成了!
我們已經(jīng)研究了如何添加、刪除和更改數(shù)據(jù),以 props 形式將數(shù)據(jù)從父級(jí)傳遞到子級(jí),以及以事件偵聽(tīng)器的形式將數(shù)據(jù)從子級(jí)發(fā)送到父級(jí)。當(dāng)然,React 和 Vue 之間還有其他許多小差異和癖好,但我希望本文的內(nèi)容有助于大家理解這兩個(gè)框架是如何處理事物的。
如果你有興趣 fork 本文中使用的樣式,并想制作自己的類(lèi)似作品,請(qǐng)自便!
兩個(gè)應(yīng)用的 Github 鏈接
Vue ToDo:
https://github.com/sunil-sandhu/vue-todo-2020
React ToDo:
https://github.com/sunil-sandhu/react-todo-2020
文章名稱(chēng):我用React和Vue構(gòu)建了同款應(yīng)用,對(duì)比看看
網(wǎng)站路徑:http://m.5511xx.com/article/dppscoc.html


咨詢(xún)
建站咨詢(xún)
