iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 2
0
自我挑戰組

IT文章簡介(Medium)系列 第 2

(翻譯)I created the exact same app in React and Vue. Here are the differences.

原文:I created the exact same app in React and Vue. Here are the differences.

原作同意翻譯的連結

原文的程式碼:
react-todovue-todo
React版本:16.4.1
Vue版本:2.5.16
皆使用預設CLI建立APP

還有另外一篇Part 2,有另外一位作者加入Angular 6的做法來比較,原本要將兩片合一,嗯嗯嗯嗯嗯,後來放棄這個做法,兩片都翻譯。

Google翻譯 + Yahoo字典 + 自己順

作者在工作上是使用Vue,也對Vue有深刻的理解,但是他想知道React和Vue有什麼不同。
他看了React的文件和教學視頻,雖然文件和視頻很好也很全面,但是他想知道React和Vue的不同之處。不同之處並不是指是否有virtual DOMS或是如何渲染頁面,而是想知道程式碼的意思及如何運轉。作者爬過文,想要找可以解釋這些差異的文章,以方便新手可以理解React和Vue的差異。但是找不到,所以自己寫程式並記錄過程。

他建立非常標準的To-Dd App,允許新增、刪除項目,並且都使用CLI建立App(create-react-app for React,and vue-cli for Vue)。
React、Angular、Vue的畫面看起來如下
React、Angular、Vue的畫面

CSS Code除了放置的位置不同,內容實際上是一樣的。
文件結構:

你會發現兩者的文件結構很像,較大的差別是Vue將一個組件的內容全部放在一起,React將css放在單獨的檔案。css、html、script是否放在一起可以取決於各人偏好,這裡遵循CLI所產生的結構。

先看看組件長什麼樣子吧!

上圖:左邊是Vue(css包在一起),右邊是React

現在開始深入細節

我們如何改變資料(mutate data)

基本上只是指改變我們儲存的資料,而這是Vue和React不同的關鍵之處。Vue建立了data物件之後可以自由更新,而React建立一個state物件之後,需要一些而外工作完成更新。我們先看看Vue的data物件和React的state物件。

|||
上圖:左邊是Vue的data物件,右邊是React的state物件

我們將相同的資料傳遞到兩者,你可以看到這只是標記符號的差異而已。將初始資料傳遞到我們的組件是非常相似的,但是我們才提到,改變資料才是這兩個框架的不同之處。

假設我們有一個資料元素叫做 name: 'Sunil'

在Vue,我們通過 this.name 來引用,我們可以使用 this.name = 'John' 更新它,這樣name就會變成John。
在React,我們通過 this.state.name 來引用,關鍵的不同是不能使用 this.name = 'John' 更新它,而要使用 this.setState({name: 'John'}) 更新它。
雖然React的實現和Vue是相同的,實際上Vue在每次更新資料時會自己組合setState版本。也就是Vue會幫你做setState,而React要自己做。為什麼React需要自己setState?來看看Revanth Kumar的解釋:

“This is because React wants to re-run certain life cycle hooks, [such as] componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate, whenever state changes. It would know that the state has changed when you call the setState function. If you directly mutated state, React would have to do a lot more work to keep track of changes and what lifecycle hooks to run etc. So to make it simple React uses setState.”

這是因為當狀態改變時,React要重新運行某些生命週期的鉤子(life cycle hooks),例如componentWillReceiveProps、 shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate。當你調用setState時,React就會知道狀態改變了。假如你直接異動狀態,React必須花更多功夫去保持變更追蹤和運行生命週期的鉤子。所以就用setState吧。

我們如何建立新的組件?

React:

createNewToDoItem = () => {
    this.setState( ({ list, todo }) => ({
      list: [
          ...list,
        {
          todo
        }
      ],
      todo: ''
    })
  );
};

React如何做到的?
在React,我們的輸入欄位有一個名為 value 的屬性。這個值經由使用幾個function達成自動更新,這幾個function結合在一起產生類似雙向綁定(two-way binding)的東西。(Vue的部分會再解釋)。我們建立一個有額外 onChange 事件傾聽的輸入欄位來達成雙向綁定。讓我們快速看看輸入欄位,你可以看到發什麼事:

<input type="text" 
       value={this.state.todo} 
       onChange={this.handleInput}/>

只要輸入欄位的 value改變, handleInput function也會跟著執行。它更新 state 物件的todo值。
這個function看起來像:

handleInput = e => {
  this.setState({
    todo: e.target.value
  });
};

現在,只要使用者按下頁面上的+按鈕來增加新項目, createNewToDoItem function就會執行 this.setState 並傳遞一個function給它。這個function有兩個參數,第一個是來自 state 物件的整個 list ,第二個是 todo (被 handleInput function更新之後的)。 然後將返回一個新的物件,包含之前整個 list 並在其最後添加 todo。( ...是ES6的spread operator)。
最後,我們將todo設定為空字串,它會自動更新輸入欄位的值。

Vue:

createNewToDoItem() {
    this.list.push(
        {
            'todo': this.todo
        }
    );
    this.todo = '';
}

Vue如何做到的?
在Vue,我們的輸入欄位有一個叫 v-model 的句柄(handle)。這允許我們做雙向綁定。我們快速看看輸入欄位,我們會解釋發生什麼事:

<input type="text" v-model="todo"/>

V-Model綁定此欄位的輸入到 data 物件的toDoItem。當頁面載入時,toDoItem設定空字串如 todo: '',假如已經有一些資料,例如 todo: 'add some text here' ,輸入欄位就會載入add some text here。無論怎樣,不管我們在輸入欄位輸入什麼文字,都會更新至todo的值。這實際上是雙向綁定(輸入欄位可以更新data物件,data物件可以更新輸入欄位)。

回顧 createNewToDoItem() 程式碼,看到我們將 todo的內容塞進 list 陣列,然後將 todo 設為空字串。

如何從list刪除資料?

React:

deleteItem = indexToDelete => {
    this.setState(({ list }) => ({
      list: list.filter((toDo, index) => index !== indexToDelete)
    }));
};

How did React do that?
雖然deleteItem function在 ToDo.js 中,我們可以很容易地在ToDoItem.js 引用它,首先將 deleteItem() 透過 的prop 傳入:

<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>

首首先將function傳給子組件,你可以看到我們綁定了 thiskey先參數, key是此function在按下刪除之後用來識別要刪除哪一個ToDoItem。 ToDoItem組件內部,我們執行以下操作:

<div className=”ToDoItem-Delete” onClick={this.props.deleteItem}>-</div> 

最後要做的就是用this.props.deleteItem 引用父組件的function。

Vue:

onDeleteItem(todo){
  this.list = this.list.filter(item => item !== todo);
}

How did Vue do that?
Vue的做法稍微不同,基本上有三件事:

首先,在元素上呼叫function:

<div class=”ToDoItem-Delete” @click=”deleteItem(todo)”>-</div>

然後在子組件創建一個 emit function(本範例是ToDoItem.vue),看起來像:

deleteItem(todo) {
    this.$emit('delete', todo)
}

你應該有注意到當我們在ToDo.vue增加ToDoItem.vue時實際上引用了一個function:

<ToDoItem v-for="todo in list" 
          :todo="todo" 
          @delete="onDeleteItem" // <-- this :)
          :key="todo.id" />

這叫做客製事件監聽器(customer event-listener),它監聽所有用delete字串觸發的發射(emit),如果監聽到就觸發onDeleteItem function。這個function在ToDo.vue ,而不是ToDoItem.vue。這個function是用來按下刪除時過濾data物件的todo 陣列,並刪除項目。

在Vue中可以簡單地在**@click使用$emit**達成監聽:

<div class=”ToDoItem-Delete” @click=”$emit(‘delete’, todo)”>-</div> 

這樣就只需要兩個步驟(三步變成兩步),這取決各人習慣。

簡而言之,React中的子組件可以通過this.props訪問父function(假設你將傳遞props,這是標準做法,你也能在其他React範例看到),而在Vue中則是由子組件發射事件,由父組件搜集這些事件。

如何傳遞事件監聽器(event listeners)?

React:
Event listeners(例如click 事件)是簡單直接的。下面是建立一個有 click事件按鈕來創建新ToDo項目的範例:

<div className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>

這裡很簡單。如同下方的Vue部分所描述,它花了更多時間去設定按下Enter 後的事件監聽處理。本質上是輸入標籤要求一個onKeyPress 事件。

<input type=”text” onKeyPress={this.handleKeyPress}/>

當辨識出Enter 被按下時,這個function就會觸發 createNewToDoItem,例如:

handleKeyPress = (e) => {
  if (e.key === ‘Enter’) {
    this.createNewToDoItem();
  }
};

Vue:
在Vue是更簡單直接的。使用 @ 符號,然後加上我們要去做的 event-listener類型。範例如下:

<div class=”ToDo-Add” @click=”createNewToDoItem()”>+</div> 

註:@clickv-on:click 的簡寫。Vue的事件監聽是很酷的東西,還有很多事件可以鏈結,例如 .once 可以預防事件監聽被觸發多次。處理鍵盤事件監聽還有許多簡潔方法。我發現在React光是按下 *Enter 按鍵來建立新的ToDo項目就需要花更長的時間。在Vue只需要這樣寫:

<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>

譯者:我認為是指React要自己寫程式判斷是不是按下Enter,而Vue有提供v-on:keyup.enter。

我們如何傳資料給子組件?

React:
在React中,在我們創建子組件時,我們就設定好傳遞資料了。例如:

<ToDoItem key={key} item={todo} />

這裡我們可以看到有兩個 props 傳給 ToDoItem 組件。從此我們可以經過 this.props 引用他們,要去取得 item.todo prop,我們可以呼叫 海伯利昂號 this.props.item

Vue:
在Vue,一樣是在創建子組件時設定。例如:

<ToDoItem v-for="todo in list" 
            :todo="todo"
            :key="todo.id"
            @delete="onDeleteItem" />

完成後,我們傳遞資料給子組件的 props 陣列,如: props: [ 'todo' ]。通過名稱我們可以在子組件引用,本案例是 'todo'

如何將資料傳回父組件?

React:
首先將 function 傳給子組件,讓子組件可以透過prop引用。我們在子組件增加一個call function(例如 onClick),透過這個function去調用 this.props.whateverTheFunctionIsCalled 。這就會觸發父組建中的function。範例可以看如何從list刪除資料?

Vue:
在我們的子組件只需要寫一個function將資料傳回父組件的function。在父組件寫一個function監聽該資料何時發出,以觸發function呼叫。範例一樣可以看如何從list刪除資料?

終於得到了!

我們研究了如何增、刪、修資料,父、子組件間的資料傳遞。當然React和Vue之間還存在一些小差異和怪僻,但是希望這篇文章的內容能夠幫助你理解這兩者是如何處理東西。


上一篇
(翻譯)Here’s how you can make better use of JavaScript arrays
下一篇
(翻譯)Internet of Things (IoT). What is IoT?
系列文
IT文章簡介(Medium)5

尚未有邦友留言

立即登入留言