用react hooks構建一個todoList

本文翻譯來自於upmostly.com/tutorials/b…css

pc版本預覽 html

image.png

構建一個簡單的todoList用react和react hooks.這是一個很好的教程對於初學者和中級開發人員.react

我將帶你走進如何用react構建簡單的todo list,僅僅使用functional組件和新的useState react hooknpm

tips: useState hook 將使咱們可以存儲狀態的內部功能組件。👋Goodbye過於混亂 類組件,你好hook!🎣數組

建立一個新react項目

咱們將跳過全部手動構建配置過程,這樣咱們可以快速進入正題瀏覽器

npx create-react-app todo-app
複製代碼

而後咱們用ide開發todo-app文件夾bash

寫html,css樣式

image.png

當我建立一個新的react 組件,我喜歡首先編寫的HTML和CSS。 咱們一般不會在內部類組件中添加render方法,相反,咱們直接返回HTML功能組件 替換app.js成下面的代碼app

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="app">
      <div className="header">
        <img src={logo} className="logo" alt="logo" />
      </div>
      <form className="todo-list">
        <ul>
          <div className="todo">
            <div className="checkbox" />
            <input type="text" value="Todo one" />
          </div>
        </ul>
      </form>
    </div>
  );
}

export default App;
複製代碼

粘貼下面的CSS到 App.css裏面,而後你也能夠修改它,修改爲你喜歡的樣子。ide

App.css
body {
  background-color: #282c34;
  min-height: 100vh;
}

.app {
  padding-top: 10rem;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.logo {
  animation: App-logo-spin infinite 20s linear;
  height: 20vmin;
  pointer-events: none;
}

.todo-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

.todo {
  display: flex;
  align-items: center;
  margin: 1rem 0;
}

.todo-is-completed .checkbox {
  color: #000;
  background: #fff;
}

.todo-is-completed input {
  text-decoration: line-through;
}

.checkbox {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  margin-right: 10px;
  cursor: pointer;
  font-size: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: background-color .2s ease-in-out;
  border: 1px solid #fff;
}

.checkbox:hover {
  background: rgba(255, 255, 255, 0.25);
  border: 1px solid rgba(255, 255, 255, 0);
}

.checkbox-checked {
  color: #000;
  background: #fff;
}

ul {
  list-style: none;
  padding: 0;
  line-height: 2rem;
  width: 500px;
}

input {
  border: none;
  background: transparent;
  color: white;
  font-size: 1.4rem;
  outline: none;
  width: 100%;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
複製代碼

用useState Hook添加State

如今,咱們有一個漂亮的todo應用,讓咱們開始hook。svg

爲何咱們須要State?

State容許咱們來跟蹤變化在React組件中。一個todo列表常常變化。例如:

  • 添加新的待辦事項   
  • 改變現有待辦事項   
  • 刪除待辦事項   
  • 完成待辦事項   
  • 沒有完成待辦事項 接下來在App.js文件的頂部 import useState hook
import React, { useState } from 'react';
複製代碼

終於咱們能夠初始化咱們組件的state屬性,像下面這個樣子

App.js
...

function App() {
  const [todos, setTodos] = useState([
    {
      content: 'Pickup dry cleaning',
      isCompleted: true,
    },
    {
      content: 'Get haircut',
      isCompleted: false,
    },
    {
      content: 'Build a todo app in React',
      isCompleted: false,
    }
  ]);
  
...
複製代碼

tips: 記住,hook 必須初始化react 組件的內部。你不能在函數外部初始化它。

當你使用useState hook ,將會添加兩個值:getter和setter。在上面的代碼中,todos是state自己,setTodos 是更新state 值的方法。 咱們初始化todos設置一個默認對象,這個對象用一個數組填充。爲何咱們使用對象而不是簡單的字符串?由於咱們須要存儲爲每一個作兩條信息:

  • 待辦事項的內容
  • 待辦事項是否已經完成

顯示待辦事項

保存項目而後跳轉到你的應用程序在瀏覽器中運行的狀況(執行npm start),你將看到一條todo item..🤔 這麼奇怪。咱們有三個待辦事件在咱們的State,爲何咱們只看到一條。 咱們設置狀態初始值,但尚未返回語句todo狀態值 顯示待辦事項,咱們須要循環todos數組和呈現一個todo項數組裏面的每一項行動計劃。爲此,咱們將使用map函數

...
<form className="todo-list">
  <ul>
    {todos.map((todo, i) => (
      <div className="todo">
        <div className="checkbox" />
        <input type="text" value={todo.content} />
      </div>
    ))}
  </ul>
</form>
...
複製代碼

map是一個很常見的函數。你可能會遇到不少次,因此重要的是你須要瞭解它是如何工做的。 上面的代碼是遍歷todos數組而後渲染括號內HTML爲數組中的每一項。 而後保存一下代碼,你將看到。。。

image.png

新建一個新的待辦事件

如今咱們可以顯示待辦事項列表,讓咱們添加新建一個todo項的能力。 首先添加一個onKeyDown事件處理程序的輸入字段:

...

<div className="todo">
  <div className="checkbox" />
  <input
    type="text"
    value={todo.content}
    onKeyDown={e => handleKeyDown(e, i)}
  />
</div>
...
複製代碼

onKeyDown調用一個名爲handleKeyDown的函數。經過輸入的名稱以及待辦事項的索引。handleKeyDown裏面,咱們發現若是返回鍵被按下。若是是,咱們稱之爲createTodoAtIndex。讓咱們添加這兩個函數在返回語句,以下所示:

...

function handleKeyDown(e, i) {
  if (e.key === 'Enter') {
    createTodoAtIndex(e, i);
  }
}

function createTodoAtIndex(e, i) {
  const newTodos = [...todos];
  newTodos.splice(i + 1, 0, {
    content: '',
    isCompleted: false,
  });
  setTodos(newTodos);
  setTimeout(() => {
    document.forms[0].elements[i + 1].focus();
  }, 0);
}
...
複製代碼

createTodoAtIndex函數看起來很複雜,但實際上很是簡單。讓我將其分解:

  • 咱們經過監聽返回鍵被按下而後檢查event.key的值
  • 接下來,咱們建立一個副本todos狀態數組。咱們這樣作是由於state不該該直接修改
  • 使用複製出來的待辦事件,咱們插入一個新的空待辦事項後當前選中待辦事項。這就是爲何咱們須要在當前todo 的index傳遞給這個函數
  • 插入新的todos副本後,咱們更新原始數組的副本
  • 最後,咱們將focus到Input中

您可能已經注意到,代碼行focus到Input中超時觸發後0毫秒 更新內部狀態的組件不發生瞬間反應。有時候須要時間,尤爲是咱們更新包含大量的數據。 所以咱們添加一個延時去等待state完成更新以後focusing on新渲染的input。 [站外圖片上傳中...(image-7255ee-1561032624160)]

更新一個待辦事項

如今咱們能夠建立一個新的任務,讓咱們添加一個實際上填入的值。 文本框有一個onChange事件該字段的值改變時觸發。 當心,雖然input values自己並不提供更新綁定函數,相反,一個event對象容許您經過event.target.value找到更新後的values 在createTodoAtIndex方法以後 添加如下方法

...

function updateTodoAtIndex(e, i) {
  const newTodos = [...todos];
  newTodos[i].content = e.target.value;
  setTodos(newTodos);
}

...
複製代碼

很像createTodoAtIndex方法,updateTodoAtIndex兩個參數:輸入event和待辦事項index,一樣的,咱們複製todos數組來避免直接state. 在這個複製出來的數組中,咱們給todos添加content key,內容是event 中的values值。最後,咱們用複製出來的數組更新todos的state [站外圖片上傳中...(image-2065b1-1561032624160)]

刪除todos

下面咱們來實現,todos values內容爲空以後輸入退格鍵,刪除這個todos的功能。 咱們稱之爲一個叫作removeTodoAtIndex新功能

function handleKeyDown(e, i) {
  if (e.key === 'Enter') {
    createTodoAtIndex(e, i);
  }
  if (e.key === 'Backspace' && todos[i].content === '') {
    e.preventDefault();
    return removeTodoAtIndex(i);
  }
}

function removeTodoAtIndex(i) {
  if (i === 0 && todos.length === 1) return;
  setTodos(todos => todos.slice(0, i).concat(todos.slice(i + 1, todos.length)));
  setTimeout(() => {
    document.forms[0].elements[i - 1].focus();
  }, 0);
}
複製代碼

保存組件並返回到您的瀏覽器您就能夠本身試一下具體的功能

完善todos

咱們討論了建立、更新和刪除一個todo。如今讓咱們來完善它。 如今,我給圓圈添加了hover效果,不過點擊並無任何效果。 讓咱們改變它 添加一個onClick綁定事件以及todo-is-completed樣式名

...

<div className={`todo ${todo.isCompleted && 'todo-is-completed'}`}>
  <div className={'checkbox'} onClick={() => toggleTodoCompleteAtIndex(i)}>
    {todo.isCompleted && (
      <span>&#x2714;</span>
    )}
  </div>
  <input
    type="text"
    value={todo.content}
    onKeyDown={e => handleKeyDown(e, i)}
    onChange={e => updateTodoAtIndex(e, i)}
  />
</div>

...
複製代碼

最後,添加toggleTodoCompletedAtIndex函數

function toggleTodoCompleteAtIndex(index) {
  const temporaryTodos = [...todos];
  temporaryTodos[index].isCompleted = !temporaryTodos[index].isCompleted;
  setTodos(temporaryTodos);
}
複製代碼

保存以後,讓咱們看下最後的成果吧! [站外圖片上傳中...(image-93efad-1561032624160)]

完成的源碼

下面我提供了完整的源代碼,這樣你就能夠看到咱們的反應todo組件在其全部的使用方式。

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  const [todos, setTodos] = useState([
    {
      content: 'Pickup dry cleaning',
      isCompleted: true,
    },
    {
      content: 'Get haircut',
      isCompleted: false,
    },
    {
      content: 'Build a todo app in React',
      isCompleted: false,
    }
  ]);

  function handleKeyDown(e, i) {
    if (e.key === 'Enter') {
      createTodoAtIndex(e, i);
    }
    if (e.key === 'Backspace' && todos[i].content === '') {
      e.preventDefault();
      return removeTodoAtIndex(i);
    }
  }

  function createTodoAtIndex(e, i) {
    const newTodos = [...todos];
    newTodos.splice(i + 1, 0, {
      content: '',
      isCompleted: false,
    });
    setTodos(newTodos);
    setTimeout(() => {
      document.forms[0].elements[i + 1].focus();
    }, 0);
  }

  function updateTodoAtIndex(e, i) {
    const newTodos = [...todos];
    newTodos[i].content = e.target.value;
    setTodos(newTodos);
  }

  function removeTodoAtIndex(i) {
    if (i === 0 && todos.length === 1) return;
    setTodos(todos => todos.slice(0, i).concat(todos.slice(i + 1, todos.length)));
    setTimeout(() => {
      document.forms[0].elements[i - 1].focus();
    }, 0);
  }

  function toggleTodoCompleteAtIndex(index) {
    const temporaryTodos = [...todos];
    temporaryTodos[index].isCompleted = !temporaryTodos[index].isCompleted;
    setTodos(temporaryTodos);
  }

  return (
    <div className="app">
      <div className="header">
        <img src={logo} className="logo" alt="logo" />
      </div>
      <form className="todo-list">
        <ul>
          {todos.map((todo, i) => (
            <div className={`todo ${todo.isCompleted && 'todo-is-completed'}`}>
              <div className={'checkbox'} onClick={() => toggleTodoCompleteAtIndex(i)}>
                {todo.isCompleted && (
                  <span>&#x2714;</span>
                )}
              </div>
              <input
                type="text"
                value={todo.content}
                onKeyDown={e => handleKeyDown(e, i)}
                onChange={e => updateTodoAtIndex(e, i)}
              />
            </div>
          ))}
        </ul>
      </form>
    </div>
  );
}

export default App;
複製代碼
相關文章
相關標籤/搜索