iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 10
3
Modern Web

React 30天系列 第 10

Day 10-todos小練習

  • 分享至 

  • xImage
  •  

前情提要:
昨天介紹了在Parcel中如何使用CSS、PostCSS(Autoprefixer)和SCSS。
今天我們就過去前幾天的經驗做個todos練習吧!

昨天找了很多大家的作品,也曾經想過自己找個api抓抓資料render過濾,最後想著果然還是應該要有畫面才能傳達練習的內容,所以我們簡單就好XD,畢竟今天是重陽節...
欸?什麼跟什麼?有什麼關聯嗎?重陽節很忙,當然不是登高望遠,反正就忙碌重陽節QAQ

操作上參照的雛形是這個:todos
https://i.imgur.com/ymtvAnV.gif

預計功能會有:

  1. 新增待辦事項,驗證條件value不為空
  2. 顯示紀錄的代辦事項,checkbox標記完成/未完成

那麼就來動工吧!
首先,我們先處理input的部分

  1. 在src/components下新增new_todo.js作為我們新增事項的component
  2. 初始化state,設定todoVal為空字串 (todoVal對應的就是input text)
  3. 設定兩個event handler,一個處理onchange,一個處理onsubmit。onchange處理輸入的value,onsubmit處理資料送出的部分(value不為空才送)
  4. 在onsubmit接了一個來自父層component的props,這個傳遞過來的props是一個function,我們把輸入的value作為引數送去function處理。
  5. 判斷todoVal是否有值。
  6. 送出資料後把input element的value清空。
class NewTodo extends Component {  //  1.
  constructor(props) {  // 2.
    super(props);
    this.state = {
      todoVal: ""
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  
  
  handleChange(e) {  // 3.
    this.setState({
      todoVal: e.target.value
    });
  }

  handleSubmit(e) {  // 4. props的function來自父層<App/>
    e.preventDefault();
    if(this.state.todoVal) {  // 5. 判斷todoVal是否有值
      this.props.addNewTodo(this.state.todoVal);
      this.setState({ todoVal: "" });  // 6. 重置input
    }
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>  { /* 4. onsubmit event */ }
        <input
          type="text"
          value={this.state.todoVal}
          onChange={this.handleChange}  { /* 3. onchange event */ }
        />
      </form>
    );
  }
}

export default NewTodo;

而在index.js這邊:

  1. 匯入NewTodo這個component
  2. 初始化state,並將todoList設為空array
  3. 新增addNewTodo method,處理來自子層component的value
  4. 訂好我們要儲存的資料內容 (key, text, completed)
  5. 用解構的方式將舊資料和新資料合併成一個新array
import NewTodo from './src/components/new_todo'; // 1. 匯入NewTodo

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { // 2. 初始化state,todoList會集合我們在input輸入的value
      todoList: []
    }
    this.addNewTodo = this.addNewTodo.bind(this);
  }

  addNewTodo(text) {  // 3. text的value來自NewTodo
    const newItem = {    // 4. todoList array的每個element內容
      text,
      key: uuid(),
      completed: false
    }
    this.setState({  // 5. 更新todoList
      todoList: [...this.state.todoList, newItem]
    })
  }

  render() {
    return (
      <div>
        <NewTodo addNewTodo={this.addNewTodo}/>
      </div>
    );
  }
}

接下來處理顯示部份,新增一個component命名為todo_list.js。
在todo_list裡,我只需要做三件事

  1. render todoList的內容
  2. checkbox標記待辦事項是否完成,把更新後的item送出處理
  3. 點擊button移除該項待辦事項,把array item的key值送出處理
    這三件事我都需要使用props傳遞,todoList資料從props傳入,更新跟移除也是透過props執行父層component的function做todoList的更新。所以我這邊使用function compoonent處理。
import React from 'react';

const TodoList = ({todo, completeTodo, removeTodo}) => ( // 解構props
  todo.map((item, index) => (    { /* 1. render todoList的內容 */ }
    <li key={item.key}>
      <input         { /* 2. checkbox標記待辦事項是否完成 */ }
        type="checkbox" 
        checked={item.completed} 
        onChange={() => completeTodo(index, { ...item, completed: !item.completed})}
      />
      <span>{item.text}</span>
      { /* 3. 點擊button移除該項待辦事項▼ */ }
      <button onClick={() => removeTodo(index)}>移除</button>  
    </li>
  ))
)

export default TodoList;

剛剛可以看到todo_list做的事都很簡單,因為已經把燙手山芋交給爸爸了(父層component),接下來我們來處理燙手山芋吧!

  1. 匯入TodoList
  2. 在render method內使用TodoList,並設定props。props包含
    2-1 要render的todoList
    2-2 處理待辦事項是否完成的completeTodo method
    2-3 刪除待辦事項的removeTodo method
    ※ 因為todoList有可能是空字串,為了避免渲染不需要的element,所以加了todoList.length > 0的判斷
import TodoList from './src/components/todo_list';  // 1. 匯入TodoList

constructor(props) {
  super(props);
  // ...
  this.completeTodo = this.completeTodo.bind(this);
  this.removeTodo = this.removeTodo.bind(this);
}
// ...
completeTodo(index, item) {   // 2-2
const todoList = update(this.state.todoList, {
  [index]: {$set: item}
});
this.setState({ todoList })
}

removeTodo(index) {          // 2-3
const todoList = update(this.state.todoList, {
  $splice: [[index, 1]]
});
this.setState({ todoList });
}

render() {
  const todoList = this.state.todoList;
  const hasTodoList = todoList.length > 0;
  return (
    ...
    { hasTodoList && (   { /* 2. */ }
      <ul className="list">
        <TodoList            
          todo={todoList}                   { /* 2-1 */ }
          completeTodo={this.completeTodo}  { /* 2-2 */ }
          removeTodo={this.removeTodo}      { /* 2-3 */ }
        />
      </ul>
    )}
    ...
  )
}

額外處理的項目(immutability-helper & uuid)

React官方建議把state當作是不可變的data(相關內容可參考The Power Of Not Mutating Data,為了保持state的不可變性,在這邊想了很多。我們之前在處理state的時候幾乎只用到string或boolean,這兩個型別很單純,一但新值更新就直接覆蓋,但如果state存的是一個Object,很容易讓state資料產生變異。
React提出幾個解套方案Using Immutable Data Structures,我在這邊用了immutability-helper
使用記得先安裝yarn add immutability-helper,然後匯入module

import update from 'immutability-helper';

另外,在這個階段發現另一個問題,本來打算將key值設為array的index。(又在偷懶!)
但發現之後新增/移除時變動資料的話,array的index會因為增減受影響,所以這裡安裝node-uuid使用uuid(Universally Unique IDentifier,通用唯一識別碼),避免造成未來的煩惱。使用前一樣先安裝yarn add uuid,然後更新index.js內的addNewTodo function

import uuid from 'uuid/v4';

// addNewTodo 調整成
addNewTodo(text) {
  const newItem = {
    text,
    key: uuid(),
    completed: false
  }
  this.setState({
    todoList: [...this.state.todoList, newItem]
  })
}

React Developer Tools

在自己寫react的時候,需要常常查看props和state的值是否正確,React Developer Tools可以讓我們在開發的時候隨時監控state和props的變化。
安裝完後會在chrome的devtools看到react的tab,就可以開始使用囉。
https://ithelp.ithome.com.tw/upload/images/20181017/201115952rPgNnQ2g2.png

在設定那邊可以勾選Highlight Update
https://ithelp.ithome.com.tw/upload/images/20181017/20111595xA5RcMBaGE.png
測試結果如下:
https://i.imgur.com/aaSdWN4.gif
可以看出,新增項目的時候在更新,勾選項目的時候有更新,刪除項目的時候也更新。
等等!好像不太對,新增項目為什麼時時刻刻跟著其他人一起更新,明明勾選和刪除沒有他的事,原來是因為父層component的資料更新了,所以作為子層的NewTodo也一起收到變動而re-render。但這邊他只需要維持現狀就好,所以回想過去幾天在lifecycle那邊提到的shouldComponentUpdate不應該使用它防止渲染(render),所以我們使用PureComponent處理吧!操作結果如下:
https://i.imgur.com/mHABBDD.gif
調整後的NewTodo就不會受影響re-render了!

加上css調整後,最後操作如下:
https://i.imgur.com/OKBbNxW.gif
github傳送門


今日總結:
實作之後可以得到不同的收穫,就算題目多麼的小XD

本日到這告一段論,大家重陽節快樂~


上一篇
Day 09-[番外]繽紛世界(CSS / Autoprefixer / SCSS with Parcel)
下一篇
Day 11-初探Redux(State)
系列文
React 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
royal801991
iT邦新手 5 級 ‧ 2018-10-17 22:20:16

怎麼知道今天是重陽節的啊   哈哈

Summer iT邦新手 3 級 ‧ 2018-10-17 22:54:20 檢舉

日曆上有寫

Yvonne iT邦新手 5 級 ‧ 2018-10-18 00:25:37 檢舉

有人會提醒 ˊ_>ˋ

我要留言

立即登入留言