本文翻譯來自於upmostly.com/tutorials/b…css
pc版本預覽 html
構建一個簡單的todoList用react和react hooks.這是一個很好的教程對於初學者和中級開發人員.react
我將帶你走進如何用react構建簡單的todo list,僅僅使用functional組件和新的useState react hooknpm
tips: useState hook 將使咱們可以存儲狀態的內部功能組件。👋Goodbye過於混亂 類組件,你好hook!🎣數組
咱們將跳過全部手動構建配置過程,這樣咱們可以快速進入正題瀏覽器
npx create-react-app todo-app
複製代碼
而後咱們用ide開發todo-app文件夾bash
當我建立一個新的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);
}
}
複製代碼
如今,咱們有一個漂亮的todo應用,讓咱們開始hook。svg
State容許咱們來跟蹤變化在React組件中。一個todo列表常常變化。例如:
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爲數組中的每一項。 而後保存一下代碼,你將看到。。。
如今咱們可以顯示待辦事項列表,讓咱們添加新建一個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函數看起來很複雜,但實際上很是簡單。讓我將其分解:
您可能已經注意到,代碼行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 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);
}
複製代碼
保存組件並返回到您的瀏覽器您就能夠本身試一下具體的功能
咱們討論了建立、更新和刪除一個todo。如今讓咱們來完善它。 如今,我給圓圈添加了hover效果,不過點擊並無任何效果。 讓咱們改變它 添加一個onClick綁定事件以及todo-is-completed樣式名
...
<div className={`todo ${todo.isCompleted && 'todo-is-completed'}`}>
<div className={'checkbox'} onClick={() => toggleTodoCompleteAtIndex(i)}>
{todo.isCompleted && (
<span>✔</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>✔</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;
複製代碼