iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0

在正式撰寫react前,本篇探討一下原生與vue和react的簡單版的toDoList寫法

初衷

根據stateofjs的統計目前知名的前三大前端框架為React、Vue、Angular(如附圖)
,另外根據Google trend顯示台灣對於Vue及React更是比Angular的搜尋趨勢有更顯著的差異(如附圖)

為了感受其差別,這邊撰寫React、Vue和原生JS的to do list來體驗一下。

VanillaJS(原生JS)

HTML結構

首先我們先刻出簡單的html結構,這邊<li>的地方先放假資料,由於本次目標主要重點是在Javascript邏輯的部分,因此就不寫外部的css檔案,用inline-style當作行內樣式。

<p>
 <input type="text" id="input" /><!-- 這邊加入id屬性是為了之後使用js方便選取該元素-->
 <button id="add">add</button><!-- 這邊加入id屬性是為了之後使用js方便選取該元素-->
</p>
<ul id="ul"><!-- 這邊加入id屬性是為了之後使用js方便選取該元素-->
<!-- 這邊的li僅先繪製出來,等等在javascript邏輯部分要渲染的html結構與這裡會有關係 -->
 <li id="li" style="margin: 10px; text-decoration: line-through">
  <input type="checkbox" />測試<button style="margin: 10px">
   delete
  </button>
 </li>
</ul>

使用Js選取元素

接下來選取add、inputul元素,等等將會掛載事件監聽,順便宣告一個listArray來存放代辦事項的資料。

const getAddElement = document.getElementById("add");
const getInputElement = document.getElementById("input");
const getUlElement = document.getElementById("ul");

新增代辦事項(addFunction)

首先我們先在add按鈕上面監聽,當按下add按鈕的時候將資料寫入到listArray,然後我們命名在監聽事件要執行的函式叫做addFunction,在代辦事項當中使用Date.now()暫時作為UUID(通用唯一辨識碼)使用(備註)

let listArray = [];
getAddElement.addEventListener("click", addFunction);
  function addFunction() {
    let item = {
    date: Date.now(),//這邊暫時使用Date.now()來當作UUID
    content: getInputElement.value,
    done: false,
  };
listArray.push(item);
//render(listArray);等等將會有一個render function
}

這邊按鈕按下去後還不會有畫面的呈現,主要是為了做關注點分離(Separation of concerns),預計將資料處理和畫面渲染的動作分開成不同函式。

備註:更好的做法可以使用uuid套件有興趣可以自行查閱。

渲染函式(renderFunciton )

接下來撰寫render function,這邊使用innerHTML先將先前的內容清空,再根據接收到的資料(listArray)來繪製<li>的內容,其中在html的部分,將資料的date(也就是剛剛所說作為uuid)的部分設置在dataset作為識別不同的<li>

function render(listTodo) {
  getUlElement.innerHTML = "";
  var string = "";
  listTodo.forEach(function (item) {
    //這邊使用inline style在text-decoration當作是否已完成,若已完成的話將增加刪除線
    string += `
                <li style="margin: 10px; text-decoration: ${
                  item.done ? "lineThrough" : "none" 
                };" data-id=${item.date}><input type="checkbox" 
                ${item.done ? "checked" : ""}
                />
                  ${item.content}
                  <button style="margin: 10px">
                    delete
                  </button>
                </li>`;
  });
  getUlElement.innerHTML = string;
}

目前已經將render的funciton 寫完後,應當可以看到畫面的呈現。

刪除和勾選已完成的代辦事項(deleteFunction、completeFunction)

同理刪除該代辦事項和已完成也一樣綁定監聽事件其程式碼如下

/*刪除事件將會監聽刪除按鈕,
  之後得到刪除按鈕的id後,
  比對原本的listArray裡面的資料,
  將不是點擊刪除按鈕的資料留下來,
  (換句話說就是留下沒被點刪除的資料,
  因為你點刪除資料當然就不見了阿XD)
*/
getUlElement.addEventListener("click", deleteFunction);
function deleteFunction(e) {
  if (e.target.nodeName === "BUTTON") {
    let thisLi = e.target.parentNode;
    let date = thisLi.dataset.id;
    listArray = listArray.filter(function (el) {
      return el.date.toString() !== date;
    });
    render(listArray);//最後重新渲染listArray的資料
  }
}

/*監聽已完成checkbox,
 點擊後藉由得到dataset的id比對listArray當中的資料,
 將原本done改成true或false
*/
getUlElement.addEventListener("change", completeFunction);
function completeFunction(e) {
  if (e.target.nodeName === "INPUT") {
    console.log(e.target);
    let thisLi = e.target.parentNode;
    let date = thisLi.dataset.id;
    listArray.forEach(function (el) {
      if (el.date.toString() == date) {
        el.done = !el.done;
      }
    });
    console.log(listArray);
    render(listArray);//最後重新渲染listArray的資料
  }
}

Vue.js

首先使用vue-cli,先全域安裝其指令如下npm install -g @vue/cli,接下來就可以使用指令vue create [專案名稱]來建置一個專案了(備註)

備註:搭建鷹架(英文為scaffolding亦翻譯腳手架)作為快速搭建專案的方式更多請參考vue-cli-overview

單文件組件(Single-File Components)

這邊使用單文件組件的方式建置代辦事項,並且將整個to do list做為一個component(其實要拆分component還是可以拆得更細,但不在這次主要討論範疇因此先暫且不拆),因此在src的資料夾底下結構大致如下

│  App.vue
│  main.js
│
│
└─components
        toDo.vue

vue-單文件組件

Template

在toDo的vue檔案裡面寫了模板語法,該語法是基於HTML模板所構成的東西,可以被一般瀏覽器和HTML解析器解析,Vue再將其編譯成Javascript並結合響應式系統(Reactivity),如果想要寫得像react的渲染函式或式JSX的話,vue也可以達到,可以參見官方的渲染函數&JSX,不過官方也提到這樣做將不能得到與模板相同的編譯優化。

另外官方使用vue-cli鷹架所建置出來的內容也是默認使用模板系統(備註)

這邊會在template裡面除了常見的html的標籤以外,也會嵌入一些vue語法,像是v-model、v-on、v-for、v-bind等等,在程式碼旁邊將會撰寫簡單說明如下

<template>
  <p>
    <input type="text" v-model="input" />//透過v-model綁定使用者輸入的文字與接下來的data函式input變數的值
    <button v-on:click="addHandler">add</button>//使用v-on撰寫按鈕按下時要做的事件處理函式
  </p> 
  <ul>
    <li
      v-for="item in listArray" //透過v-for遍歷data裡面的listArray
      v-bind:key="item.date"
      v-bind:style="{
        textDecoration: item.done ? 'line-through' : 'none'
      }"//對屬性動態綁定,換句話說可以撰寫data函式的變數,也能使用函式表達式的方式動態改寫屬性。
    >
      <input type="checkbox" v-on:change="changeHandler(item)" />{{
        item.content
      }}<button v-on:click="deleteHandler(item)" style="margin: 10px">
        delete
      </button>
    </li>
  </ul>
</template>

官方也是推薦使用模板系統撰寫,更多資訊可以參考官方文件模板vs渲染函數

Reactivity Fundamentals(響應式)

透過響應式的方式來宣告元件的狀態,換句話說結合template撰寫的內容在元件創建實體的時候會使用這邊的函式。更多可以參考響應式基礎

  • data()函數
    • 原先的template也有listArrayinput變數,這些變數就是參照我們在data函式裡面定義的內容
  • methods物件
    • 這邊可以對元件添加方法,method可以說是包含很多方法的物件,已剛剛的template為例就是在<span class="red">v-on</span>的地方要觸發的事件函式,值得一提的地方式透過this.變數名稱來指向data裡面的變數。

以下addHandler、deleteHandler、changeHandler除了需要透過this指向正確的變數外,函式內的方法與原生JS寫法大同小異,則不再闡述。

<script>
export default {
  name: "toDo",
  data() {
    return {
      listArray: [],
      input: "",
    };
  },
  methods: {
    addHandler() {
      let object = {};
      object.content = this.input;
      object.date = Date.now();
      object.done = false;
      this.listArray.push(object);
    },
    deleteHandler(item) {
      this.listArray = this.listArray.filter(function (el) {
        return el.date != item.date;
      });//透過this.xxxx用來指向data()函式裡面的變數值
    },
    changeHandler(item) {
      this.listArray.forEach(function (el) {
        if (el.date == item.date) {
          el.done = !el.done;
        }
      });
    },
  },
};
</script>
<!-- 添加scoped的屬性是表示此css僅能在此元件使用-->
<style scoped></style>

更多細節可參考vue-聲明方法

React

在React方面在命令提式字元使用npx create-react-app XXX的方式可以快速建立一個專案捆包,其src資料夾結構大致如下

│  App.js
│  index.js
│
└─components
        Todo.js

Function component

目前根據16.8版本後所撰寫的方式官方推薦使用function的方式結合hook來建置react,想了解更多資訊可以參考官方hook的動機說明。

JSX

接下來我們在component function的return的地方撰寫畫面,可以發現return的東西長得很像HTML,實際上這些code稱之為JSX,而原本像是html就有的onclick之類的撰寫方式,在JSX當中變成駝峰式寫法,使得我們可以對觸發後的事件執行相對應的事情,另外在{}當中可以直接使用javascript語法撰寫函式表達式來指定要渲染的內容。例如可以參考程式碼當中list.map(以下略...)那一段,比較值得一提的地方是onChangeonClick要帶入參數的話,得用callbackFunction的方式。(備註)

備註:若帶入函式加小括弧(也就是只有寫completeHandler(listItem.date))的話,畫面在載入的時候就會被執行,但這種情況並非我們想要的結果,我們希望是在按下click後或者chage事件發生的時候再觸發,因此再用一個callback函式包裹住。

import React, { useState } from "react";
const Todo = () => {
  return (
    <>
      <p>
        <input type="text" value={input} onChange={inputHandler}></input>
        <button onClick={addHanndler}>add</button>
      </p>
      <ul>
        {list.map((listItem) => { //渲染list的資料的內容
          return (
            <li
              style={listItem.done ? { ...margin, ...deleteLine } : margin}
              key={listItem.date}
            >
              <input
                type="checkbox"
                checked={listItem.done}
                //要帶數值進去所以要多一個callback
                onChange={() => completeHandler(listItem.date)}
              />
              {listItem.content}
              <button
                style={margin}
                onClick={() => deleteHandler(listItem.date)}
              >
                Delete
              </button>
            </li>
          );
        })}
      </ul>
    </>
  );
};
export default Todo;

hook與事件處理

剛剛在JSX的事件處理要觸發的函式將會寫在component函式當中,可以注意到我們也宣告了list和input透過解構的方式引用了useState這個hook,這邊可以理解成跟畫面渲染有關係的資料得透過hook的方式進行連動,要更改資料的部分也是透過setInput和setList這個函式來進行,值得一提的地方是setList([...list, inputObject])的寫法是透過...list展開運算子得到變動前的值然後加入新的物件到陣列當中作為要變動後的值。更多可以參考官方網站hook的解說。

const Todo = () => {
  const margin = { margin: 10 };
  const deleteLine = { textDecoration: "line-through" };
  const [input, setInput] = useState("");
  const [list, setList] = useState([]);
  const inputHandler = (e) => {
    setInput(e.target.value);
  };
  const addHanndler = (e) => {
    const inputObject = {
      content: input,
      date: Date.now(),
      done: false,
    };
    setList([...list, inputObject]);//透過展開運算子來展開list然後再加入物件組成新陣列最後透過setList函式更改list變數。
  };
  const completeHandler = (date) => {
    const newList = list.map((el) => {
      if (el.date === date) {
        el.done = !el.done;
      }
      return el;
    });
    setList(newList);
  };
  const deleteHandler = (date) => {
    console.log(date);
    const newList = list.filter((el) => {
      return el.date !== date;
    });
    setList(newList);
  };
}
return //(以下省略)


總結

在寫完Vue和React和原生js的to do list後,可以理解為什麼有些人說React寫起來比較像是Javascript、Vue比較像在寫原本的HTML以下簡單比較一下寫法。

寫法差異

Vue的寫法

用template的方式撰寫合法的html,對於原本不用框架的人來說相對上手比較容易,另外在template有一些增強的語法可以簡單的使用來控制畫面處理的邏輯,例如v-for的方式就可遍歷出<li>另外把資料和方法建立在Reactivity Fundamentals(響應式基礎)系統當中,比喻就好像填空題,將資料放入data函式,將要操作的方法放入method物件當中。

React的寫法

JSX的方式作為基礎來撰寫,如果初次使用的的人突然看到return(<div>Hello World</div>)的話,將會難以理解,另外今天的範例使用list.map()的表達式來將<li>給遍歷後渲染,而且你可以寫事件處理和宣告變數在return JSX之前,換句話說就像Javascript在撰寫函式的時候將要執行的內容執行完再return出去,諸如此類的作法更像是在寫Javascript


上一篇
在github page部署react—簡述為何重新整理出現404 feat.解決方案
下一篇
React—jsx基本介紹、snippet、各種css style解決方案
系列文
從Create到React—用來實作使用者介面的JavaScript函式庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
heiyuyu
iT邦新手 5 級 ‧ 2023-01-20 17:00:51

都簡單列出來比較好容易理解不同之處,感謝整理/images/emoticon/emoticon32.gif

DannyChen iT邦研究生 5 級 ‧ 2023-01-21 08:49:19 檢舉

您的鼓勵是我寫文的動力XD/images/emoticon/emoticon37.gif

我要留言

立即登入留言