در قسمت قبل کارهای مربوط به State را تمام کردیم اما هنوز یکی از کارکردهای اصلی برنامه ما پیاده سازی نشده است. برای پیاده سازی آن باید فایل TodoList.tsx را باز کرده و به جای اینکه todo.text را مستقیما درون <li> داشته باشیم، آن را درون یک Span قرار دهیم. همچنین دکمه ای به نام Delete نیز داشته باشیم (در کنار همان آیتم) تا اگر روی آن کلیک شد، آیتم ما حذف شود:
const TodoList: React.FC<TodoListProps> = props => { return ( <ul> {props.items.map(todo => ( <li key={todo.id}> <span>{todo.text}</span> <button>Delete</button> </li> ))} </ul> ); };
بنابراین می توان برای button یک رویداد onClick تعریف کرد اما از آنجایی که todo ها را درون App.tsx تنظیم می کنیم باید ابتدا به همان فایل برویم و تابعی برای مدیریت آن ایجاد کنیم. من درون App.tsx تابعی به نام todoDeleteHandler تعریف می کنم:
const App: React.FC = () => { const [todos, setTodos] = useState<Todo[]>([]); const todoAddHandler = (text: string) => { setTodos(prevTodos => [ ...prevTodos, { id: Math.random().toString(), text: text } ]); }; const todoDeleteHandler = (todoId: string) => { setTodos(prevTodos => { return prevTodos.filter(todo => todo.id !== todoId); }); }; // بقیه کدها //
همانطور که در جلسه قبل توضیح داده بودم برای تغییر state از setTodos استفاده می کنیم و باز هم همان تابع همیشگی را داریم. این بار از یک تابع به نام filter استفاده می کنم. این تابع شرطی را برای فیلتر کردن یک آرایه می گیرد و سپس تک تک اعضای آرایه را بر اساس آن شرط اعتبارسنجی می کند. اگر آیتم برای آن شرط برابر با true بود آن را نگه داشته و در غیر این صورت آن را دور می اندازد. نهایتا یک آرایه جدید به ما برگردانده خواهد شد. Todo که به آن پاس داده ام به صورت خودکار برابر تک تک اعضای آرایه ما خواهد بود. بقیه آن که شرط اصلی می باشد منطق ساده ای دارد: اگر todo.id (آیدی هر کدام از todo ها در State) برابر با todoId (آیدی پاس داده شده به تابع) نبود، آن را حذف کن.
حالا می توانیم از prop ها استفاده کرده و این تابع را به todoList پاس بدهیم تا بتواند آن را اجرا کند بنابراین در همان فایل (App.tsx) می گوییم:
// بقیه کدها // const todoDeleteHandler = (todoId: string) => { setTodos(prevTodos => { return prevTodos.filter(todo => todo.id !== todoId); }); }; return ( <div className="App"> <NewTodo onAddTodo={todoAddHandler} /> <TodoList items={todos} onDeleteTodo={todoDeleteHandler} /> </div> ); };
من نام این prop را onDeleteTodo گذاشته ام. حالا تایپ اسکریپت به ما خطا می دهد چرا که در مورد onDeleteTodo چیزی به todoList (همان interface تعریف شده) نگفته ایم، بنابراین به فایل TodoList.tsx رفته و در interface آن می گوییم:
interface TodoListProps { items: { id: string; text: string }[]; onDeleteTodo: (id: string) => void; }
یعنی onDeleteTodo تابعی است که به عنوان اولین پارامتر خود یک رشته دریافت می کند و void برمی گرداند (چیزی برنمی گرداند یا هر چه برگرداند برای ما مهم نیست). حالا در فایل TodoList.tsx برای دکمه خودمان از onClick استفاده می کنیم:
const TodoList: React.FC<TodoListProps> = props => { return ( <ul> {props.items.map(todo => ( <li key={todo.id}> <span>{todo.text}</span> <button onClick={props.onDeleteTodo}> DELETE </button> </li> ))} </ul> ); };
مشکل اینجاست که من باید id مربوط به todo را به این تابع پاس بدهم اما نمی توانم جلوی آن پرانتز بگذارم (اگر چنین کاری بکنید این متد در همان ابتدای برنامه درجا اجرا خواهد شد و همه چیز بهم می ریزد). برای حل این مشکل می توانیم از bind استفاده کنیم!
<button onClick={props.onDeleteTodo.bind(null, todo.id)}> DELETE </button>
همانطور که می دانید bind حداقل دو آرگومان می گیرد: اولین آرگومان تعریف this است (یعنی کلیدواژه this به چه چیزی اشاره کند). از آنجایی که من در این قسمت از کدها از this استفاده نمی کنم و برایم مهم نیست، null را به عنوان پارامتر اول پاس می دهم. سپس آرگومان های بعدی bind همیشه آرگومان هایی هستند که تابع ما به آن ها نیاز دارد (هر چند تا که باشند). من فقط یک آرگومان دارم که آن هم todo.id است بنابراین آن را پاس داده ام.
با انجام این کار باید به فکر استایل دهی این دکمه نیز باشیم تا لیست ما را بهم نریزد. در ابتدا در پوشه components دو فایل جدید به نام های NewTodo.css و TodoList.css ایجاد کنید. من کدهای هر دو فایل را برایتان قرار می دهم.
کدهای فایل NewTodo.css:
form { width: 90%; max-width: 40rem; margin: 2rem auto; } .form-control { margin-bottom: 1rem; } label, input { display: block; width: 100%; } label { font-weight: bold; } input { font: inherit; border: 1px solid #ccc; padding: 0.25rem; } input:focus { outline: none; border-color: #50005a; } button { background: #50005a; border: 1px solid #50005a; color: white; padding: 0.5rem 1.5rem; cursor: pointer; } button:focus { outline: none; } button:hover, button:active { background: #6a0a77; border-color: #6a0a77; }
کدهای فایل TodoList.css:
ul { list-style: none; width: 90%; max-width: 40rem; margin: 2rem auto; padding: 0; } li { margin: 1rem 0; padding: 1rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26); border-radius: 6px; display: flex; justify-content: space-between; align-items: center; }
حالا به فایل NewTodo.tsx بروید و فایل استایل مربوط به آن را در آن وارد کنید:
import React, { useRef } from 'react'; import './NewTodo.css'; // بقیه کدها //
حالا در همان فایل و در قسمت return برای div اصلی یک کلاس به نام form-Control انتخاب می کنم:
return ( <form onSubmit={todoSubmitHandler}> <div className="form-control"> <label htmlFor="todo-text">Todo Text</label> <input type="text" id="todo-text" ref={textInputRef} /> </div> <button type="submit">ADD TODO</button> </form> );
حالا به فایل TodoList.tsx رفته و فایل Css مربوط به آن را نیز وارد کنید:
import React from 'react'; import './TodoList.css'; // بقیه کدها //
اکنون اگر برنامه را در مرورگر خود باز کنید با چنین استایل هایی روبرو می شوید:
بنابراین برنامه ساده خودمان را با تایپ اسکریپت نوشته ایم. همچنین در صورتی که بخواهید از redux استفاده کنید، باید به documentation کامل آن مراجعه کنید که نحوه انجام کار را قدم به قدم برایتان توضیح داده است:
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.