譯者 jonjia 愛貝睿技術團隊
在工作中使用 Vue 一段時間後,對它的工作原理有了相當深入的瞭解。然而,我很想知道籬笆另一邊的草地是什麼樣 - React。
我已經閱讀了 React 文件,也觀看了一些教程視訊,雖然它們都很棒,但我真正想知道的是 React 與 Vue 到底有什麼不同。這裡的「不同」不是指它們是否具有虛擬 DOM 或者它們如何渲染頁面。我希望有人能直接解釋程式碼並告訴我會發生什麼!我想找到一篇能解釋這些差異的文章,那樣 Vue、React (或者 Web 開發)的新手就可以更好地理解兩者之間的差異了。
但我找不到任何解決這個問題的資源。所以我意識到必須靠自己來解決這個問題,發現它們之間的相似之處和不同之處。在這樣做時,我想記錄下整個過程,所以最終就有了這樣一篇文章。
你 pick 誰?
我決定構建一個標準的 Todo 列表應用,允許使用者新增、刪除列表中的專案。兩個應用都使用預設的 CLI (React 的 create-react-app,Vue 的 vue-cli) 來構建。順便說一下,CLI 表示命令列介面。?
因為這篇文章的篇幅已經超出了我的預期,所以讓我們首先快速瞭解下這兩個應用:
Vue vs React:勢均力敵
兩個應用的 CSS 程式碼完全相同,但這些程式碼的位置不同。為了說明這一點,我們先看看兩個應用的檔案結構,如下:
你 pick 誰?
可以發現,它們的結構幾乎相同。唯一不同是:React 應用有 3 個 CSS 檔案;Vue 應用一個也沒有。這樣做的原因是:在 create-react-app 中,每個 React 元件都會附帶一個樣式檔案來儲存其樣式;而 Vue CLI 採取單檔案元件,每個元件的樣式都會在元件內部宣告。
最終,它們都達到了同樣目的,你也可以在 React 或 Vue 中以不同方式構建自己的 CSS。這完全取決於個人偏好 - 你會在開發社群中聽到許多關於如何構建 CSS 的討論。現在,我們會遵循兩個 CLI 中列出的結構。
但在進一步討論之前,讓我們先看看典型的 Vue 和 React 元件是什麼樣的:
左邊是 Vue 元件,右邊是 React 元件。
現在開始,讓我們深入瞭解細節吧!
如何改變資料?
首先,「改變資料」是什麼意思?聽起來有些技術含量不是嗎?它基本上表示改變我們儲存的資料。因此,如果我們想將一個人的名字從 John 改為 Mark,我們就需要「改變資料」。這是 React 和 Vue 之間的關鍵區別所在。Vue 本質上建立了一個資料物件,其中的資料可以自由更新;而 React 建立了一個狀態物件,就需要更多的工作來完成更新。React 有充分的理由要求額外的工作,我們會稍微介紹一下。但首先,讓我們看一下 Vue 中的 data 物件和 React 中的 state 物件:
左邊是 Vue data 物件,右邊是 React state 物件。
你可以看到我們給兩個物件傳遞了相同的資料,只是識別符號不同。將初始資料傳入元件的方式非常相似。但正如上面提到的,如何改變這些資料在兩個框架之間會有所不同。
假設有一個名為 name 的資料元素,它的值是:'Sunil'。
在 Vue 中,我們通過 this.name
來引用它。也可以通過 this.name = 'John'
來更新它。這會把我的名字改為 John。
在 React 中,我們需要通過 this.state.name
來引用相同的資料。現在關鍵區別在於我們不能簡單地通過 this.state.name = 'John'
,因為 React 內部機制會防止這種簡單、輕易的改變。所以在 React 中,我們會通過 this.setState({ name: 'John' })
來更新資料。
雖然這和我們在 Vue 中的方法都能實現相同目的,但 React 內部為防止我們意外地覆蓋 this.state
有一些額外程式碼,this.state
和 this.setState
之間區別明顯。有些理由說明了為什麼 React 改變資料的方式與 Vue 不同,Revanth Kumar 的解釋如下:
這是因為 React 希望在狀態發生變化時重新執行某些生命週期方法,如 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。當你呼叫 setState 方法時,它會很快知道狀態發生了改變。如果你直接修改 state,React 需要做更多工作來跟蹤修改以及重新執行生命週期方法等等。所以為了簡單起見,React 使用 setState 方法。
肖恩·賓很有經驗(Sean Bean:《指環王:護戒使者》中博羅米爾扮演者,其中臺詞 “One does not simply walk into Mordor(魔多不是你想去就能去的),此處為:this.state
不是你想用就能用的)
現在我們已經知道如何改變資料,然後來看看如何在 Todo 列表應用中新增新專案。
如何新增一個新的 Todo 項?
React:
createNewToDoItem = () => {
this.setState( ({ list, todo }) => ({
list: [
...list,
{
todo
}
],
todo: ''
})
);
};
複製程式碼
使用 React 如何實現?
在 React 中,input 元素的 value
屬性被繫結到了 this.state.todo
這個值上。這個值可以通過呼叫一些函式實現自動更新,這些函式繫結到一起就建立了雙向資料繫結(如果你之前沒聽過,在後面的使用 Vue 如何實現部分有更詳細的解釋)。React 通過在 input 元素上繫結 onChange 方法來實現雙向繫結。讓我們來看看 input 元素是什麼樣的,然後再來解釋原理:
<input type="text"
value={this.state.todo}
onChange={this.handleInput}/>
複製程式碼
如果 input 元素的值發生變化,handleInput 方法就會被呼叫。它會使用 input 中的內容來更新 state 物件中 todo 的值。這個函式如下:
handleInput = e => {
this.setState({
todo: e.target.value
});
};
複製程式碼
現在,每當使用者按下頁面上的 + 按鈕新增新專案時,createNewToDoItem 函式就會執行 this.setState
方法並向其傳遞一個函式作為引數。這個函式有兩個引數,第一個是來自 state 物件的整個 list 陣列,第二個是新的 todo(由 handleInput 函式更新)項。然後該函式返回一個新物件,該物件包含之前的整個 list,然後在 list 末尾新增新的 todo 項。整個 list 是使用擴充套件運算子新增的(如果你以前沒有看過這個語法可以搜尋一下,這是 ES6 語法)。
最後,我們將 todo 設定為空字串,它會自動更新 input 元素的值。
Vue:
createNewToDoItem() {
this.list.push(
{
'todo': this.todo
}
);
this.todo = '';
}
複製程式碼
使用 Vue 如何實現?
在 Vue 中,input 元素有一個名為 v-model 的指令。可以幫助我們建立雙向資料繫結。先看看 input 元素是什麼樣的,然後再來解釋原理:
<input type="text" v-model="todo"/>
複製程式碼
V-Model 指令將 input 元素的值繫結到資料物件中的 toDoItem。當頁面載入時,我們通過 todo: ''
將 toDoItem 設定為空字串。如果這裡已經有一些資料,例如 todo: '原有 todo'
,input 元素會使用已有資料原有 todo 作為初始值。無論如何,假設使用空字串作為初始值,我們在 input 元素輸入的任何文字都繫結到 todo 上。這實際上就是雙向繫結(input 元素可以更新資料物件,資料物件也可以更改 input 元素的值)。
所以回顧前面 createNewToDoItem() 程式碼,我們看到它將 todo 的內容新增到 list 陣列,然後將 todo 更新為空字串。
如何刪除列表中的 Todo 項?
React:
deleteItem = indexToDelete => {
this.setState(({ list }) => ({
list: list.filter((toDo, index) => index !== indexToDelete)
}));
};
複製程式碼
使用 React 如何實現?
雖然 deleteItem 方法定義在 ToDo.js 檔案中,但先將 deleteItem() 方法作為 元件的 prop 傳遞進去,在 ToDoItem.js 內部引用它也就很容易了,寫法如下:
<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>
複製程式碼
首先將方法傳遞到子元件,使其可以訪問。同樣可以看到我們繫結了 this,並把 key 作為引數傳遞,key 用來區分點選刪除的是哪個 ToDoItem。然後,在 ToDoItem 內部,程式碼如下:
<div className="ToDoItem-Delete" onClick={this.props.deleteItem}>-</div>
複製程式碼
所有需要引用父元件中的一個方法只通過 this.props.deleteItem 就可以實現。
Vue:
onDeleteItem(todo){
this.list = this.list.filter(item => item !== todo);
}
複製程式碼
使用 Vue 如何實現?
Vue 應用需要稍微不同的方法。基本上分為三步:
首先,在元素上繫結點選事件處理方法:
<div class="ToDoItem-Delete" @click="deleteItem(todo)">-</div>
複製程式碼
然後,建立一個呼叫 emit 方法的函式作為子元件(這個例子中,就是 ToDoItem.vue 元件)內部方法,如下:
deleteItem(todo) {
this.$emit('delete', todo)
}
複製程式碼
除此之外,當我們在 ToDo.vue 中新增 ToDoItem.vue 時,我們實際引用了一個函式:
<ToDoItem v-for="todo in list"
:todo="todo"
@delete="onDeleteItem" // <-- 這裡 :)
:key="todo.id" />
複製程式碼
這就是所謂的自定義事件監聽器。它會監聽任何由 emit 觸發名為 delete 的事件發生的場合。如果監聽到,就會觸發執行名為 onDeleteItem 的方法。這個方法定義在 ToDoItem.vue 元件內部而不是 ToDoItem.vue 元件。這個方法,正如上面所示,會過濾 data 物件內的 todo 陣列並移除點選的專案。
這裡值得注意的是:在 Vue 應用中,也可以把 $emit
部分寫到**@click** 指令中,如下:
<div class="ToDoItem-Delete" @click="this.$emit('delete', todo)">-</div>
複製程式碼
這樣可以將步驟從 3 步減少到 2 步,這也僅僅取決於個人偏好。
簡而言之,React 中的子元件可以通過 this.props 訪問父元件(假設你向下傳遞 props,這是相當標準的做法,你會在其它 React 示例中多次看到)中的方法,而在 Vue 中,你必須從子元件內部發出通常在父元件內監聽的事件。
如何傳遞事件監聽器?
React:
簡單事件(如點選事件)的事件監聽器是直截了當的。以下是我們為新建 ToDo 項按鈕繫結 click 事件監聽的示例:
<div className="ToDo-Add" onClick={this.createNewToDoItem}>+</div>.
複製程式碼
這裡的實現非常簡單,看起來很像使用原生 JS 來處理行內的 onClick 事件。正如 Vue 部分提到的,如果是為按下回車按鈕設定事件監聽器就需要花費更長的時間了。input 標籤通常會處理 onKeyPress 事件,如下:
<input type="text" onKeyPress={this.handleKeyPress}/>.
複製程式碼
只要這個方法監聽到了Enter鍵按下,它就會呼叫 createNewToDoItem 函式,如下所示:
handleKeyPress = (e) => {
if (e.key === 'Enter') {
this.createNewToDoItem();
}
};
複製程式碼
Vue:
Vue 中的實現超級直接。只需使用 @ 符號,然後繫結相應的事件監聽器。例如,要新增 click 事件監聽器,只需如下編寫程式碼:
<div class="ToDo-Add" @click="createNewToDoItem()">+</div>
複製程式碼
注意:@click 實際上是 v-on:click 的簡寫。Vue 事件監聽器另一個很酷的事情是:有很多修飾符可以連結到後面,例如 .once,它可以防止事件監聽器被多次觸發。在編寫用於處理鍵盤事件偵聽器時,也有一些快捷方式。我發現在 React 中為建立新的 ToDo 項繫結一個事件監聽器需要花費更長的時間。而在 Vue 中,我能夠像下面這樣簡單實現:
<input type="text" v-on:keyup.enter="createNewToDoItem"/>
複製程式碼
如何將資料傳遞給子元件?
React:
在 React 中,我們在使用子元件的地方通過 prop 傳遞資料,如下:
<ToDoItem key={key} item={todo} />
複製程式碼
上面有兩個 props 傳遞給了 ToDoItem 元件。這樣傳遞之後,就可以在子元件內部通過 this.props
來引用它們了。因此,就可以通過 this.props.item
訪問 todo 變數了。
Vue:
在 Vue 中,也是在使用子元件的地方傳遞資料,如下:
<ToDoItem v-for="todo in list"
:todo="todo" :id="todo.id"
:key="todo.id"
@delete="onDeleteItem" />
複製程式碼
這樣傳遞之後,我們會把這些資料傳遞到子元件的 props 陣列中:props: [ 'id', 'todo' ]。然後就可以在子元件中通過它們的名字進行引用了,比如 id 和 todo。
如何將資料傳送回父元件?
React:
我們首先將函式傳遞給子元件,方法就是在使用子元件時將其作為 prop 傳入。然後在形如 onClick 方法中通過 this.props.whateverTheFunctionIsCalled 引用這個函式。這將觸發位於父元件中定義的函式。可以在如何從列表中刪除 Todo 項一節中看到整個過程的一個示例。
Vue:
在子元件中,我們只需編寫一個函式,將一個事件名傳送回父元件。在父元件中,我們編寫一個函式來監聽這個事件,它會觸發函式呼叫。可以在如何從列表中刪除 Todo 項一節中看到整個過程的一個示例。
到這裡就完成了 ?
我們研究瞭如何新增、刪除和更改資料,以 prop 形式將資料從父元件傳入到子元件,以及通過事件偵聽器形式將資料從子元件傳送到父元件。當然,在 React 和 Vue 之間還存在許多其它差異,但希望本文的內容對你理解兩個框架如何處理問題打下一個好的基礎 ?
兩個應用 Github 倉庫連結:
Vue ToDo:github.com/sunil-sandh…
React ToDo:github.com/sunil-sandh…
翻譯自 I created the exact same app in React and Vue. Here are the differences.,祝好。
關於我們:
公眾號:
部落格