用React创建一个待做事项组件

用React创建一个待做事项组件

2566 Words 8.5 Minutes

alt

第一步:先创建一个组件,显示单个待办事项

组件的结构 TodoLists.jsx

// TodoLists.jsx

import './TodoLists.css';

// 这里添加props作为行参,将来要接收Parent传递过来的参数
const TodoLists = (props) => {
  return (
    <div className="todo-container">
      <div className="todo-list">
        <div>Date //这里是添加日期</div>
        <div>Thing //这里是待办事项的名字</div>

        <button className="btn">删除</button>
      </div>
    </div>
  );
};

export default TodoLists;

组件的样式 TodoLists.css

/* TodoLists.css */

.todo-container {
  font-size: 2rem;
  width: 100%;
  /* height: 10rem; */
  color: white;
  padding: 2rem;
}
.todo-list {
  font-weight: 400;
  background-color: rgb(46, 125, 67);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 2rem;
}

显示组件,一般默认是 App.jsx,它为所有组件的 Top Parent App.jsx

import TodoLists from './components/TodoLists/TodoLists';

function App() {
  return (
    <div className="App">
      <TodoLists />
    </div>
  );
}

export default App;

下面是渲染后的样子 alt

第二步:添加动态数据

数据应该储存在最上层组件里,因为需要渲染这些数据,而最上层组件 App.jsx 负责全部组件,所以在 App.jsx 里添加数据

App.jsx

// App.jsx

function App() {
  const todoListItems = [
    { id: 0, title: '睡觉', date: new Date(2022, 2, 2) },
    { id: 1, title: '上学', date: new Date(2022, 1, 2) },
    { id: 2, title: '睡午觉', date: new Date(2021, 2, 2) },
    { id: 3, title: '吃饭', date: new Date(2022, 1, 5) },
  ];

  return <div className="App">{mapTodoListItems(todoListItems)}</div>;
}

const mapTodoListItems = (items) => {
  // 用map函数返回多个小组件,避免代码重复
  return items.map((item) => {
    return <TodoLists key={item.id} title={item.title} date={item.date} />;
  });
};

TodoLists.jsx

// TodoLists.jsx

const TodoLists = (props) => {
  // 将Date的实例转为字符串
  const date = props.date.toLocaleString('zh-cn', {
    month: 'long',
    day: '2-digit',
    hour12: false,
  });
  return (
    <div className="todo-container">
      <div className="todo-list">
        <div>{date}</div>
        <div>{props.title}</div>

        <button className="btn">删除</button>
      </div>
    </div>
  );
};

每个组件应该负责它们单独的任务,可能很多地方都需要这个功能。所以对日期各式转换的功能我想单独放在一个组件里。

创建一个文件 TodoListDate.jsx

alt

现在把处理日期的代码移到 TodoListDate 组件里

//TodoListDate.jsx

const TodoListDate = (props) => {
  const date = formatDate(props.date);
  return <div>{date}</div>;
};

const formatDate = (date) => {
  return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
};

export { TodoListDate, formatDate };
// TodoLists.jsx

import { TodoListDate } from './TodoListDate';

const TodoLists = (props) => {
  return (
    <div className="todo-container">
      <div className="todo-list">
        // 将date实参传给TodoListDate(子组件)
        <TodoListDate date={props.date} />
        <div>{props.title}</div>
        <button className="btn">删除</button>
      </div>
    </div>
  );
};

第三步:增加添加待做事项的功能

创建新的组件 NewTodoList 文件夹,里面包含功能的小组件

alt

// NewTodoList.jsx

import { formatDate } from '../TodoLists/TodoListDate';
import './NewTodoList.css';

const NewTodoList = (props) => {
  return (
    <div className="new-todo-container">
      <form onSubmit="" className="new-todo">
        <div className="new-todo__inputs">
          <div className="new-todo__input">
            <label htmlFor="title">Title:</label>
            <input type="text" name="title" value="" />
          </div>
          <div className="new-todo__input">
            <label htmlFor="date">Date:</label>
            <input type="date" name="date" min={formatDate(new Date())} max="2022-12-31" />
          </div>
        </div>
        <button className="btn" type="submit">
          添加
        </button>
      </form>
    </div>
  );
};

export default NewTodoList;
/* NewTodoList.css */

.new-todo-container {
  width: 100%;
}
.new-todo {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 3rem;
  font-size: 1.2rem;
  color: rgb(255, 254, 254);
}
.new-todo__inputs {
  display: flex;
}
.new-todo__input {
  margin: 0 1rem;
}

label {
  margin-right: 0.5rem;
  font-weight: 800;
}
.btn {
  background-color: white;
  padding: 1rem 2rem;
}
.btn:hover {
  background-color: white;
  color: black;
}

添加到 App.jsx 里

// App.jsx

import NewTodoList from './components/NewTodoList/NewTodoList';

return (
  <div className="App">
    {mapTodoListItems(todoListItems)}
    <NewTodoList />
  </div>
);

alt

React input 元素里有 onChange 的属性,可以实时监听输入的内容,我们需要获取输入的内容,然后储存起来,再传入到 App 父组件里进行渲染

// NewTodoList.jsx

const NewTodoList = (props) => {
  const titleEnteredHandler = (event) => {
    // onChange属性返回一个事件,先log一下事件
    console.log(event);
  };
  return (
    <div>
      <input type="text" name="title" value="" onChange={titleEnteredHandler} />
    </div>
  );
};

alt

alt

这个事件是一个 object,它的 target 属性就是我们需要的,因为其中有用户输入的内容的数据。

// 可以通过下面的表达式获取用户输入的内容
const data = event.target.value;

如果需要 Title 和 Date,则需要对 2 个 input 元素同时用 onChange 的属性。 现在来定义监听事件的函数

首先我需要把这些数据储存起来。form 元素的 onSubmit 的属性也接受一个传入函数,它用来处理提交表单。我希望的是提交表单后 input 元素里的值为空,并且用来储存这些数据的参数传递给 App.js 后,也为空。

先单个处理 Title

// NewTodoList.jsx
const NewTodoList = (props) => {
  const title = '';
  const titleEnteredHandler = (event) => {
    title = event.target.value;
    console.log(title);
  };

  return (
    <div>
      {' '}
      <input type="text" name="title" onChange={titleEnteredHandler} />{' '}
    </div>
  );
};

上面代码有问题,因为函数里的 title 的作用域和我们在组件函数里定义的 title 的作用域不一样,所以无法改变定义在外面的 title,一般解决办法就是将组件定义为一个类,然后用构造函数,使得 title 为类的全局属性,但是这样太累赘了。React 给我们提供了一个特别方便的函数useState() 。它接受一个参数:字符串,我们想传入的初始值;返回一个数组。数组中第一个是字符串,也就是更新后的状态值,第二个是函数,用来设置状态值。

现在来定义输入框()的 value 属性,这个 value 属性也就是event.target.value

// NewTodoList.jsx

const [enteredTitle, setEnteredTitle] = useState(''); // 将初始值设为空的字符串,因为输入框初始状态就是没有输入任何内容
const [enteredDate, setEnteredDate] = useState(''); // 同理设为空

//或者直接把title和date设置为一个初始状态,它们是一个object的属性。

const [enteredInput, setEnteredInput] = useState({ title: '', date: '' });

  const titleEnteredHandler = (event) => {
	// 这个函数可以接受一个函数参数,prevState是这个函数传入在的参数函数里的值,它表示之前的状态。通过用箭头函数可以实时更新状态,而不必等某一个输入框完成更新。
    setEnteredInput((prevState) => {
      return { ...prevState, title: event.target.value };
    });

	// log一下最新的状态
    console.log(enteredInput);
  };

  const dateEnteredHandler = (event) => {
    setEnteredInput((prevState) => {
      return { ...prevState, date: event.target.value };
    });
    // log一下最新的状态
    console.log(enteredInput);
  };

// 在返回的 div元素里, 修改input里onChange接受的函数
<input type="text" name="title" onChange={titleEnteredHandler} />
<input
    type="date"
    name="date"
    min={formatDate(new Date())}
    max="2022-12-31"
    onChange={dateEnteredHandler}
/>

当我输入标题或者修改日期后,控制台出现了最新的 state alt

现在有了数据了,那怎么才能传给 App 组件呢 child 组件可以通过 props 传递给 parent 组件。

先处理提交表单的函数

// NewTodoList.jsx

const formSubmitHandler = (event) => {
  // 防止跳转
  event.preventDefault();

  const newTodo = {
    title: enteredInput.title,
    date: new Date(enteredInput.date),
  };

  props.onAddTodo(newTodo);

  setEnteredInput((prevState) => {
    return { title: '', date: '' };
  });
};

return (
  <div className="new-todo-container">
    <form onSubmit={formSubmitHandler} className="new-todo">
      <div className="new-todo__inputs">
        <div className="new-todo__input">
          <label htmlFor="title">Title:</label>
          <input
            type="text"
            name="title"
            onChange={titleEnteredHandler}
            value={enteredInput.title}
          />
        </div>
        <div className="new-todo__input">
          <label htmlFor="date">Date:</label>
          <input
            type="date"
            name="date"
            min={formatDate(new Date())}
            max="2022-12-31"
            onChange={dateEnteredHandler}
            value={enteredInput.date}
          />
        </div>
      </div>
      <button className="btn" type="submit">
        添加
      </button>
    </form>
  </div>
);

然后在 App 组件里更新储存待做事项的数据

// App.jsx

function App() {
  const DEFAULT_TODO = [
    { id: 0, title: '睡觉', date: new Date(2022, 2, 2) },
    { id: 1, title: '上学', date: new Date(2022, 1, 2) },
    { id: 2, title: '睡午觉', date: new Date(2021, 2, 2) },
    { id: 3, title: '吃饭', date: new Date(2022, 1, 5) },
  ];

  const [todoListItems, setTodoListItems] = useState(DEFAULT_TODO);

  const addTodoHandler = (newTodo) => {
    setTodoListItems((prevState) => {
      newTodo = { id: prevState[prevState.length - 1].id + 1, ...newTodo };

      return [newTodo, ...prevState];
    });
  };
  return (
    <div className="App">
      {mapTodoListItems(todoListItems)}
      <NewTodoList onAddTodo={addTodoHandler} />
    </div>
  );
}

alt alt

第四步:增加移除待做事项的功能

我们需要监听删除按钮,当它被点击的时候,需要更新 App.js 里的数据。

// App,jsx

const deleteTodoHandler = (id) => {
  // 我们需要获取按钮所处的位置,因为每个待做事项都有自己id,所以可以通过id来删除某个待做事项。
  const newTodoListItems = todoListItems.filter((item) => item.id !== id);
  setTodoListItems(newTodoListItems);
};

const mapTodoListItems = (items) => {
  return items.map((item) => {
    return (
      <TodoLists
        key={item.id}
        id={item.id}
        title={item.title}
        date={item.date}
        // 增加一个prop 它用来处理button被点击的事件
        onDeleteTodo={deleteTodoHandler}
      />
    );
  });
};
//TodoList.jsx

const TodoLists = (props) => {
  const deleteTodoHandler = (event) => {
    props.onDeleteTodo(props.id);
  };

  return (
    <div className="todo-container">
      <div className="todo-list">
        <TodoListDate date={props.date} />
        <div>{props.title}</div>
        <button className="btn" onClick={deleteTodoHandler}>
          删除
        </button>
      </div>
    </div>
  );
};

测试一下,先删除所有的列表 alt

但是全部删除后再添加,浏览器报错 alt

因为现在没有数据在数组里,长度为 0,减去 1 为负数,Python 里的话应该是 Index out of range。所以报错了。

// App.jsx

const addTodoHandler = (newTodo) => {
  setTodoListItems((prevState) => {
    // 这里出错了,因为现在没有数据在数组里,长度为0,减去1为负数,Python里的话应该是Index out of range。所以报错了。
    // 解决办法就是加一个判断语句
    newTodo = { id: prevState[prevState.length - 1].id + 1, ...newTodo };
    return [newTodo, ...prevState];
  });
};

修改后

const addTodoHandler = (newTodo) => {
  setTodoListItems((prevState) => {
    if (prevState.length > 0) {
      newTodo = { id: prevState[prevState.length - 1].id + 1, ...newTodo };
    } else {
      newTodo = { id: 0, ...newTodo };
    }
    return [newTodo, ...prevState];
  });
};

完成! alt

分享自制vscode主题 Hexo博客部署到Linux