iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
Modern Web

30天React練功坊-攻克常見實務/面試問題系列 第 22

30天React練功坊-攻克常見實務/面試問題 Day22: Simple todo-list(interview question)

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

昨天我們用了一個簡單的問題來測驗你對於react render邏輯的了解程度,相信透過昨天的經驗你應該對於useEffect的執行時機有著更深一層的印象了,今天換個方向,會需要你親自動手寫一部分的程式碼,雖然是老掉牙的todo-list,但卻是個很好看出測試者基礎的題目,我們馬上開始吧!

本日題目

首先請你先開啟這個codesandbox,這會是今天的starter file,請你根據以下的gif與說明完成題目的要求。

你的目標是完成這個基本的todo-list,其中需要包含以下的功能

  • 可以透過Input欄位新增todo item
  • 可以透過刪除按鈕刪除todo item
  • 可以透過點擊checkbox切換todo item的狀態
  • 已完成的事項會被畫上刪除線
  • 所有的程式碼請寫在提供的基礎檔案中

預期的完成結果如以下的gif

day22-demo-gif

請以以下的起始程式碼完成以上的需求

import React from "react";

const styles = {
  container: {
    maxWidth: "300px",
    margin: "auto"
  },
  ul: {
    listStyle: "none",
    padding: "0"
  },
  li: {
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "10px",
    borderBottom: "1px solid #ccc"
  },
  button: {
    background: "#f44336",
    color: "white",
    border: "none",
    cursor: "pointer"
  }
};

const TODO_ITEMS = [
  { id: 1, text: "Buy groceries", completed: false },
  { id: 2, text: "Clean room", completed: true }
];

function Item({ todo }) {
  return (
    <li style={styles.li}>
      <label>
        <input type="checkbox" />
        {todo.text}
      </label>
      <button style={styles.button}>Delete</button>
    </li>
  );
}

function TodoList() {
  return (
    <div style={styles.container}>
      <h2>Todo List</h2>
      <ul style={styles.ul}>
        {TODO_ITEMS.map((todo) => (
          <Item key={todo.id} todo={todo} />
        ))}
      </ul>
      <input type="text" placeholder="Add Todo" />
      <button>Add</button>
    </div>
  );
}

export default TodoList;

解答與基本解釋

今天的題目可以說是所有學習者都必定碰過的一個問題了,但坦白說實際上到面試時還是有不少人會出現一些很奇妙的錯誤或是用了遠超預期的時間才完成,是個很適合做基礎測驗的題目。起始程式碼我已經替你處理掉絕大多數的部分,你需要完成的僅有幾個邏輯,也就是題目要求的增加/編輯狀態以及刪除。

增加的部分肯定是最為容易的

  1. 你會需要一個state來做控制這樣你才能去修改渲染出來的item
  2. 同時你需要另一個state去控制input內的字串值
  3. 最後,觀察一下提供的初始值,你會需要另一個state來記錄目前產生的id

因此首先你會需要增加以下的程式碼

const [todos, setTodos] = useState(TODO_ITEMS); // 將原本給的TODO_ITEMS作為初始值
const [input, setInput] = useState(""); // 用來處理新增項目的text值
const [idCounter, setIdCounter] = useState(TODO_ITEMS.length + 1); // 紀錄目前item的對應id

const handleAdd = () => { // 處理新增邏輯
  if (input) {
    setTodos([...todos, { id: idCounter, text: input, completed: false }]);
    setInput("");
    setIdCounter(idCounter + 1);
  }
};


return (
  <div style={styles.container}>
    <h2>Todo List</h2>
    <ul style={styles.ul}>
      {todos.map((todo) => ( // 將渲染的部分用todos state渲染
        <Item
          key={todo.id}
          todo={todo}
        />
      ))}
    </ul>
    <input
      type="text"
      placeholder="Add Todo"
      value={input} // 利用input state控制值
      onChange={(e) => setInput(e.target.value)}
    />
    <button onClick={handleAdd}>Add</button> // 掛載監聽函數
  </div>
);

到這邊基本上就完成一半了,剩下編輯與刪除的部分其實邏輯相當的類似,但這也是最容易有人卡住的地方,首先你要理解渲染的部分都會在這個TodoList組件中完成,因此所有關於todo的state都會在這組件控制,但問題是按鈕都是在子組件TodoItem中,也就是你需要將對應的函數作為props傳遞給子組件,同時傳入的函數還要透過id來決定你是要操作原本todos陣列中哪一個值,理解這一點之後一切就容易多了,我們先修改一下TodoItem的程式碼,我們知道我們要新增兩個props,一個用來處理delele、另一個則用來處理toggle狀態,另外題目有要求刪除線的部分,我們可以利用todo.completed來判斷是否要加上刪除線。

// TodoItem
function Item({ todo, onDelete, onToggle }) {
  return (
    <li style={styles.li}>
      <label>
        <input type="checkbox" checked={todo.completed} onChange={onToggle} />
        {todo.completed ? <s>{todo.text}</s> : todo.text}
      </label>
      <button style={styles.button} onClick={onDelete}>
        Delete
      </button>
    </li>
  );
}

同時我們需要修改一下上方TodoList的程式碼

// TodoList
const handleDelete = (id) => { // 新增刪除的函數
  setTodos(todos.filter((todo) => todo.id !== id));
};

const handleToggle = (id) => { // 新增編輯狀態的函數
  setTodos(
    todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  );
};

return (
  <div style={styles.container}>
    <h2>Todo List</h2>
    <ul style={styles.ul}>
      {todos.map((todo) => ( 
        <Item
          key={todo.id}
          todo={todo}
          onDelete={() => handleDelete(todo.id)} // 掛載對應的函數
          onToggle={() => handleToggle(todo.id)} // 掛載對應的函數 
        />
      ))}
    </ul>
    <input
      type="text"
      placeholder="Add Todo"
      value={input} 
      onChange={(e) => setInput(e.target.value)}
    />
    <button onClick={handleAdd}>Add</button> 
  </div>
);

好了!這麼一來就大功告成了,雖然是非常簡陋的版本,但我們確實已經達到題目的要求了!

總結

Todo-list一直以來都是一個熱門的教學/測驗的題目,而他確實有它的道理存在,小小一個題目卻包含了最常使用的state & props的控制與傳遞,同時也牽扯到一些條件與陣列的渲染,是個相當簡單卻實務的題目,當然即便這個題目已經有所限制,但解法仍遠遠不止這麼一種,你在面試時的答案往往也跟面試官心中所想的會有些許差異,不過只要能順利達成要求就是個好的開始,通常面試官會在完成基本要求後開始提一些新的需求,那就是另外要討論的情況了!如果你今天並沒有辦法完成這個題目,那麼我必須說你還沒有準備好要做這樣的技術面試,強烈建議你多做一點練習,這對你會是更好的選擇!我們明天的題目會稍稍複雜一些,敬請期待!

本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!


上一篇
30天React練功坊-攻克常見實務/面試問題 Day21: React render logic interview question
下一篇
30天React練功坊-攻克常見實務/面試問題 Day23: Simple tab component(interview question)
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言