如何使用React Hooks創建一個待辦事項列表

原文地址:How to Build a Todo List with React Hooks css

clipboard.png

React v16.7.0-alpha 引入了鉤子(Hooks)。開心!html

什麼是鉤子(Hooks)?

鉤子是能讓你在沒有用es6類的狀況下使用React的狀態, 生命週期鉤子這些特性的功能。react

優點:git

  • 隔離狀態相關邏輯,使測試更加容易
  • 不須要使用渲染屬性或者高階組件就能夠共享狀態相關邏輯
  • 根據邏輯而不是生命週期鉤子來分離應用程序的關注點
  • 避免ES6類,由於它們很奇怪,不是真正的類,甚至會誤導有經驗的JavaScript開發人員

查看更多:React’s official Hooks introes6

不要在生產環境使用

寫這篇文章時,鉤子還處於內部測試(alpha)階段。它們的API隨時均可能改變。
我建議你在你的業餘項目中體驗鉤子,在它們成爲穩定版本以前,不要在線上代碼中使用。github

構建事項列表

clipboard.png

待辦事項清單是使用普遍的例子,理由很充分——它們是很棒的練習工具。不管你想嘗試任何語言或庫我都推薦使用它。
在這個例子中,咱們只實現其中的一小部分功能:npm

  • 使用Material Design展現事項列表
  • 經過input添加事項
  • 刪除事項

配置

這是githubCodeSandbox的地址數組

git clone https://github.com/yazeedb/react-hooks-todo
cd react-hooks-todo
npm install

master分支已經實現了這些功能,若是你想本身跟着實現,請切到start分支。dom

git checkout start

啓動工程函數

npm start

這個應用應該跑在localhost:3000上,這是初始UI:

clipboard.png

咱們已經設置了material-ui來給頁面一個專業的外觀,如今咱們加入更多功能!

ToDoForm 組件

添加一個新文件,src/TodoForm.js。這是初始代碼:

import React from 'react';
import TextField from '@material-ui/core/TextField';
const TodoForm = ({ saveTodo }) => {
  return (
    <form>
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
      />
    </form>
  );
};
export default TodoForm;

經過組件名字,咱們就知道它是用來添加事項的,它也就是咱們的第一個鉤子。

useState

看這段代碼:

import { useState } from 'react';
const [value, setValue] = useState('');

useState是一個接收初始狀態(state)返回一個數組的函數。console.log 它吧。
數組的第一個值是你的state如今的值,第二個值是state的更新方法。

因此咱們把它們叫作valuesetValue, 並使用es6解構賦值對它們進行賦值。

對錶單(Form)使用useState

咱們的表單應該跟蹤input的值並在保存提交時執行saveTodo方法。useState能幫咱們實現它。

更新updateForm.js, 這是更新以後的代碼:

import React, { useState } from 'react';
import TextField from '@material-ui/core/TextField';
const TodoForm = ({ saveTodo }) => {
  <b>const [value, setValue] = useState('');</b>
  return (
    <form
        <strong>onSubmit={event => {</strong>
        event.preventDefault();
        saveTodo(value);
      }}
      
    >
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
        onChange={event => {
          setValue(event.target.value);
        }}
        value={value}
      />
    </form>
  );
};
export default TodoForm;

回到index.js,引入而且使用這個組件。

...
import TodoForm from './TodoForm';
...
const App = () => {
  return (
    <div className="App">
    <Typography component="h1" variant="h2">
      Todos
    </Typography>
    <TodoForm saveTodo={console.warn} />
   </div>
  );
};

如今你在input輸入的值已經能夠被打印出來了。(記得敲enter哦)

clipboard.png

對事項列表(todos)使用useState

咱們的事項列表todos也須要state。在index.js中引入useState。初始state應該是空數組。

import React, { useState } from 'react';
...
const App = () => {
  const [todos, setTodos] = useState([]);
  return ...

TodoList組件

創建一個新文件:src/TodoList.js

大部分代碼是來自Material-UI庫的高級組件, 這是更新以後的代碼:

import React from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import ListItemText from '@material-ui/core/ListItemText';
import Checkbox from '@material-ui/core/Checkbox';
import IconButton from '@material-ui/core/IconButton';
import DeleteIcon from '@material-ui/icons/Delete';

const TodoList = ({ todos, deleteTodo }) => (
  <List>
    {todos.map((todo, index) => (
      <ListItem key={index.toString()} dense button>
        <Checkbox tabIndex={-1} disableRipple />
        <ListItemText primary={todo} />
        <ListItemSecondaryAction>
          <IconButton
            aria-label="Delete"
            onClick={() => {
              deleteTodo(index);
            }}
          >
            <DeleteIcon />
          </IconButton>
        </ListItemSecondaryAction>
      </ListItem>
    ))}
  </List>
);

export default TodoList;

它接收兩個屬性:

  • todos: 事項數組,咱們遍歷數組,創建每個事項
  • deleteTodo: 點擊一個事項的刪除按鈕觸發這個方法,它接收一個參數: 索引,這個索引惟一標識每個事項。

index.js中引入這個組件。

...
import TodoList from './TodoList';
import './styles.css';
const App = () => { ...

並在App方法中使用它:

...
<TodoForm saveTodo={console.warn} />
<TodoList todos={todos} />

添加事項

仍是在index.js中,編輯TodoForm的屬性, saveTodo

<TodoForm
  saveTodo={todoText => {
    const trimmedText = todoText.trim();
    if (trimmedText.length > 0) {
      setTodos([...todos, trimmedText]);
    }
  }}
/>

這裏咱們只是把空格去掉,把新的值添加到todos中。

咱們如今能夠添加事項了!

clipboard.png

清除input框

如今添加新的事項後,咱們沒有把input清空,這是很差的用戶體驗!
咱們只須要在TodoForm.js中作一點小改動,就能夠修復它。

<form
  onSubmit={event => {
    event.preventDefault();
    saveTodo(value);
    setValue('');
  }}
>

當事項被保存後,咱們就把form的state變成空字符串。
如今看起來很好了!

clipboard.png

刪除事項

TodoList爲每一條事項都提供了索引,根據索引咱們能找到咱們想刪除的事項。

// TodoList.js
<IconButton
  aria-label="Delete"
  onClick={() => {
    deleteTodo(index);
  }}
>
  <DeleteIcon />
</IconButton>

index.js中傳遞這個函數

<TodoList
  todos={todos}
  deleteTodo={todoIndex => {
    const newTodos = todos
      .filter((_, index) => index !== todoIndex);
  
    setTodos(newTodos);
  }}
/>

咱們使用setTodos方法把全部不符合index的事項保存下來。
刪除功能完成!

clipboard.png

抽取事項列表(todos)的useState

文章開頭我提到 鉤子便於分離狀態和組件的邏輯。因此咱們在這個應用中能夠這樣作。

新建一個文件叫src/useTodoState.js

import { useState } from 'react';

export default initialValue => {
  const [todos, setTodos] = useState(initialValue);

  return {
    todos,
    addTodo: todoText => {
      setTodos([...todos, todoText]);
    },
    deleteTodo: todoIndex => {
      const newTodos = todos
        .filter((_, index) => index !== todoIndex);

      setTodos(newTodos);
    }
  };
};

這就是index.js中原來的代碼,咱們只是把它分離出來了!咱們的狀態處理邏輯再也不和組件混在一塊兒了!

如今咱們只須要引入它,這是更新以後的代碼:

import React from 'react';
import ReactDOM from 'react-dom';
import Typography from '@material-ui/core/Typography';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
import useTodoState from './useTodoState';
import './styles.css';

const App = () => {
  const { todos, addTodo, deleteTodo } = useTodoState([]);

  return (
    <div className="App">
      <Typography component="h1" variant="h2">
        Todos
      </Typography>

      <TodoForm
        saveTodo={todoText => {
          const trimmedText = todoText.trim();

          if (trimmedText.length > 0) {
            addTodo(trimmedText);
          }
        }}
      />

      <TodoList todos={todos} deleteTodo={deleteTodo} />
    </div>
  );
};

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

如今一切工做正常!

clipboard.png

抽取表單input中的useState

咱們能夠對錶單作一樣的處理!
新建一個文件:src/useInputState.js

import { useState } from 'react';

export default initialValue => {
  const [value, setValue] = useState(initialValue);

  return {
    value,
    onChange: event => {
      setValue(event.target.value);
    },
    reset: () => setValue('')
  };
};

如今todoForm.js應該變成這樣:

import React from 'react';
import TextField from '@material-ui/core/TextField';
import useInputState from './useInputState';

const TodoForm = ({ saveTodo }) => {
  const { value, reset, onChange } = useInputState('');

  return (
    <form
      onSubmit={event => {
        event.preventDefault();

        saveTodo(value);
        reset();
      }}
    >
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
        onChange={onChange}
        value={value}
      />
    </form>
  );
};

export default TodoForm;

如今咱們所有完成了!
但願你喜歡!!
謝謝!
做者:Yazeed Bzadough

相關文章
相關標籤/搜索