ساخت یک برنامه ToDo ساده با استفاده از React

دسته بندی: آموزش
زمان مطالعه: 10 دقیقه
۰۴ دی ۱۳۹۷

در این مطلب قصد دارم بهتون آموزش بدم که چطور در React یک برنامه ToDo ساده رو به وجود بیارید و کارهاتون رو با اون مدیریت کنید.

این آموزش خیلی ساده و مقدماتی خواهد بود و انتظار استفاده از State management‌ها مثل Redux و Flux رو نداشته باشید. در این مقاله فقط بر روی React تمرکز میکنیم و سعی میکنیم که همه کارها رو با استفاده از اون انجام بدیم.

برای راه اندازی این پروژه از create-react-app استفاده میکنیم و میتونین این مقاله رو ببینید که چطور میتونین از این ابزار قدرتمند استفاده کنید.

در ابتدا در دایرکتوری public فایل index.html رو باز کرده و کدهای زیر رو درون اون قرار بدین:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <title>ToDo App</title>
  </head>
  <body>
    <div class="container">
      <div id="root" class="col-md-8"></div>
    </div>
  </body>
</html>

همونطور که میبینید ساختار ساده‌ای رو به عنوان ریشه سایت قرار دادیم و در اینجا از bootstrap 4.2.1 استفاده کردیم. همونطور که میبینید یکی از div‌ها id = root داره. برای شروع کار یک فایل بنام TodoApp.js در دایرکتوری src ساخته و کدهای زیر رو در اون قرار میدم:

import React from 'react';

const TodoApp = () => {
  return (
    <div>Hello World!</div>
  );
};

export default TodoApp;

همونطور که میبینید یک کد HTML ساده رو در اون return کردیم. حالا در دایرکتوری src، فایل index.js رو باز میکنیم و کدهای زیر رو درونش قرار میدم:

import React from 'react';
import ReactDOM from 'react-dom';
import TodoApp from './TodoApp';

ReactDOM.render(<TodoApp />, document.getElementById('root'));

همونطور که میبینید TodoApp رو وارد پروژه کرده و با استفاده از متد render از React-dom اون رو در المنت با id = root رندر کردیم. حالا اگر در command line دستور npm start رو بزنید، خروجی در مرورگر بهتون نمایش داده میشه و Hello World رو خواهید دید.

در این مطلب یک برنامه Todo ساده رو میخوایم بسازیم و قصد داریم با استفاده از LocalStorage اطلاعات اون رو مدیریت میکنیم. همونطور که میدونین component‌ها در React نقش خیلی مهمی رو ایفا میکنند و ما در این مطلب از presentation component استفاده میکنیم. همچنین از PureComponent نیز استفاده خواهیم کرد.

خروجی برنامه در انتها بصورت زیر خواهد بود:

همونطور که میبینید در حال حاضر 3 مورد در لیست وجود دارد.

  1. برای عنوان Todo یک component بنام Title به وجود میاریم و تعداد todo‌ها رو به اون پاس میدیم تا اون رو نشون بده.
  2. در این قسمت یک component بنام TodoForm به وجود میاریم که در اون یک input و یک button وجود داره و میتونین متن مورد نظرتون رو وارد کرده و بر روی + کلیک کنید تا یک آیتم به Todo‌ها اضافه بشه.
  3. در این قسمت یک component بنام TodoList به وجود میاریم که لیستی از component‌های Todo رو در خودش جا میده. میخوایم کاری کنیم که اگر بر روی هر کدام از Todo‌ها کلیک شد، اون Todo حذف بشه.

در نهایت باید همه component‌های بالا رو در TodoApp.js وارد کرده و از اونا در جاهای مورد نظر استفاده کنیم. در ابتدا با Title شروع میکنیم. در دایرکتوری components یک فایل بنام Title.js بسازید و کدهای زیر رو درون اون قرار بدین:

import React from 'react';

const Title = ({ todoCount }) => {
  return (
    <div>
      <div>
        <h1>to-do ({todoCount})</h1>
      </div>
    </div>
  );
};

export default Title;

همونطور که میبینید این component یک props بنام todoCount رو دریافت میکنه و اون رو در یک h1 نمایش میده. حالا از این Component در TodoApp.js استفاده میکنیم. بصورت زیر:

import React, { PureComponent } from 'react';

import Title from './components/Title';

class TodoApp extends PureComponent {
  constructor(props) {
    // Pass props to parent class
    super(props);

    // Set initial state
    this.state = {
      todos: [],
    };
  }

  render() {
    // Render JSX
    return (
      <div>
        <Title todoCount={this.state.todos.length} />
      </div>
    );
  }
}

export default TodoApp;

میبینید که در state مقدار اولیه todos رو یک آرایه خالی قرار دادیم و برای کامپوننت Title هم تعداد اعضای این آرایه رو ارسال کردیم. با این کار خروجی در مرورگر بصورت زیر خواهد شد:

همونطور که میبینید در حال حاضر عدد 0 نشون داده میشه چون task ای در state وجود ندارد. در componentDidMount کدهای زیر رو قرار میدیم:

componentDidMount(){
  const todos = JSON.parse(window.localStorage.getItem('todos'));

  if (todos) {
    this.setState({
      todos,
    });
  }
}

همونطور که میبینید در ابتدا چک شده که todos در localStorage وجود داره یا خیر. اگر وجود داشته باشه اعضای اون در state قرار میگیرن و ما میتونیم اونا رو ببینیم.

حالا میخوایم کامپوننت TodoForm رو که وظیفه اون ساختن todo جدید هست رو بسازیم. برای اینکار کدهای زیر رو در فایل TodoForm.js در دایرکتوری component قرار میدیم:

import React from 'react';

const TodoForm = ({addTodo}) => {
  // Input tracker
  let input;

  return (
    <div className="input-group mb-3">
      <input
        type="text"
        className="form-control"
        ref={node => input = node}
        placeholder="Todo title"
        aria-label="Todo title"
      />

      <div className="input-group-append">
        <button
          className="btn btn-outline-secondary"
          type="button"
          onClick={() => {
            addTodo(input.value);
            input.value = '';
          }}
        >
          +
        </button>
      </div>
    </div>
  );
};

export default TodoForm;

همونطور که میبینید این component یک props بنام addTodo داره که از پدرش به اون پاس داده میشه که بعدا ساختار اون رو بهتون توضیح میدم. این کامپوننت ساده فقط یک input و یک button رو در خودش جا داده و زمانی که بر روی دکمه + کلیک میشه، متد addTodo فراخوانی میشه و مقدار فعلی input به اون پاس داده میشه. در این component از ref نیز استفاده شده است.

برای نمایش لیست وظایف به کامپوننت TodoList نیاز داریم. برای اینکار کدهای زیر رو در این کامپوننت قرار میدیم:

import React from 'react';

import Todo from './Todo';

const TodoList = ({todos, remove}) => {
  // Map through the todos
  const todoNode = todos.map((todo) => {
    return (
      <Todo todo={todo} key={todo.id} remove={remove}/>
    );
  });

  return (
    <ul
      className="list-group"
      style={{marginTop:'30px'}}
    >
      {todoNode}
    </ul>
  );
};

export default TodoList;

همونطور که میبینید در این کامپوننت از Todo استفاده شده که در ادامه اون رو به وجود خواهیم آورد. ساختار کامپوننت Todo نیز بصورت زیر است:

import React from 'react';

const Todo = ({todo, remove}) => {
  // Each Todo
  return (
    <li
      className="list-group-item"
      onClick={remove(todo.id)}
    >
      {todo.text}
    </li>
  );
};

export default Todo;

میبینید که برای هر Todo یک onClick تعریف شده که با کلیک کردن بر روی اون متد remove که از پدرش به اون پاس داده شده است، مورد استفاده قرار میگیره. متد addTodo بصورت زیر میباشد:

addTodo = (val) => {
  // Assemble data
  const todo = { text: val, id: window.id++ };

  const todos = window.localStorage.getItem('todos');

  if (todos === null) {
    const todos = [];
    todos.push(todo);

    window.localStorage.setItem('todos', JSON.stringify(todos));
  } else {
    const oldTodos = JSON.parse(todos);
    oldTodos.push(todo);

    // then reset the localStorage
    window.localStorage.setItem('todos', JSON.stringify(oldTodos));
  }

  const newTodos = [
    ...this.state.todos,
    todo,
  ];

  this.setState({todos: newTodos});
}

همونطور که میبینید در اینجا در ابتدا چک شده که چیزی در localStorage وجود داره یا خیر. اگر وجود داشت یک یک آیتم دیگه به اون پاس داده میشه و به روز رسانی میشه. اگر اصلا وجود نداشته باشه هم ساخته میشه.

متد handleRemove نیز بصورت زیر هست:

handleRemove = (id) => () => {
  // Filter all todos except the one to be removed
  const remainder = this.state.todos.filter((todo) => {
    if(todo.id !== id) return todo;
  });

  window.localStorage.setItem('todos', JSON.stringify(remainder));

  this.setState({
    todos: remainder,
  });
}

همونطور که میبینید در ابتدا todo مورد نظر در State پیدا شده و فیلتر میشه و state و localStorage به روز رسانی میشن. در نهایت اگر بخوایم از همه component‌ها در کنار هم در فایل TodoApp.js استفاده کنیم، بصورت زیر خواهد شد:

import React, { PureComponent } from 'react';

import TodoForm from './components/TodoForm';
import Title from './components/Title';
import TodoList from './components/TodoList';

window.id = 0;
class TodoApp extends PureComponent {
  constructor(props) {
    // Pass props to parent class
    super(props);

    // Set initial state
    this.state = {
      todos: [],
    };
  }

  // Lifecycle method
  componentDidMount(){
    const todos = JSON.parse(window.localStorage.getItem('todos'));

    if (todos) {
      this.setState({
        todos,
      });
    }
  }

  // Add todo handler
  addTodo = (val) => {
    // Assemble data
    const todo = { text: val, id: window.id++ };

    const todos = window.localStorage.getItem('todos');

    if (todos === null) {
      const todos = [];
      todos.push(todo);

      window.localStorage.setItem('todos', JSON.stringify(todos));
    } else {
      const oldTodos = JSON.parse(todos);
      oldTodos.push(todo);

      // then reset the localStorage
      window.localStorage.setItem('todos', JSON.stringify(oldTodos));
    }

    const newTodos = [
      ...this.state.todos,
      todo,
    ];

    this.setState({todos: newTodos});
  }

  // Handle remove
  handleRemove = (id) => () => {
    // Filter all todos except the one to be removed
    const remainder = this.state.todos.filter((todo) => {
      if(todo.id !== id) return todo;
    });

    window.localStorage.setItem('todos', JSON.stringify(remainder));

    this.setState({
      todos: remainder,
    });
  }

  render() {
    // Render JSX
    return (
      <div>
        <Title todoCount={this.state.todos.length} />
        <TodoForm addTodo={this.addTodo}/>
        <TodoList
          todos={this.state.todos}
          remove={this.handleRemove}
        />
      </div>
    );
  }
}

export default TodoApp;

همونطور که دیدید میتونیم با استفاده از React به راحتی هر چیزی که بخوایم رو به Component‌های کوچکتری تقسیم‌بندی کنیم و به راحتی و خوانایی بالا کدهامون رو به وجود بیاریم.

چه امتیازی به این مقاله می دید؟
نویسنده محمد اسفندیاری
بسیار به طراحی وب علاقمندم و به سرعت در حال یادگیری تمام مباحث پیشرفته هستم و دوست دارم که به دیگران هم یاد بدهم.

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :