学习React中新的useState()钩子

56 阅读7分钟

React在状态、类和函数方面的工作方式已经发生了范式转变。React现在有一个叫做钩子的功能,它看起来是一个游戏规则的改变者。通过阅读react文档和观看React Conf,看起来我们这些网络开发者应该熟悉React中的钩子,因为它们可以使React代码更简洁。类组件仍然被支持,但看起来Hooks与功能组件的结合是未来最受欢迎的方法。


介绍useState()

useState()钩子是我们要学习的第一个钩子,为了使用它,我们把它导入到一个文件中,就像这样。

import React, { useState } from 'react';

useState()函数是一个钩子,可以让你在React中为函数组件添加状态。在这之前,只有类组件可以使用状态。


设置一个状态变量

我们可以使用这个钩子来分配一个状态变量。想象一下,我们要创建一个Todo应用程序,我们可以把useState()像这样工作。

import React, { useState } from "react";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  console.log(todos);
  console.log(setTodos);

  return <div />;
}

export default App;

当你调用useState()时,它会返回两样东西。当前的状态,和一个你可以用来更新该状态的 函数。在上面的代码中,一个包含三个对象的数组被分配给了 todos变量,而一个可以更新该状态的函数被存储在 setTodos.

useState()返回一个有状态的值,以及一个更新它的函数。

useState return values

我们可以在控制台中看到注销的结果 **todos**和 setTodos.前者保存了三个对象的数组,后者保存了一个函数,我们可以通过f字符看到。

因此,让我们继续渲染我们存储在变量中的这三个对象。 todos变量中的三个对象。我们了解到,当用 react 创建一个列表时,我们可以像这样使用 map 函数。

import React, { useState } from "react";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <div key={index} className="card mb-3">
          <div className="card-body">
            <h5 className="card-title">{todo.text}</h5>
          </div>
        </div>
      ))}
    </React.Fragment>
  );
}

export default App;

我们现在有三件事要做
learn react hooks


Todo功能组件

让我们把渲染Todo项目的标记移到一个专门的组件中。在 src/components 中创建一个名为todo.jsx的新文件,并添加这段代码。

import React from "react";

function Todo({ todo, index }) {
  return (
    <React.Fragment>
      <div key={index} className="card mb-3">
        <div className="card-body">
          <h5 className="card-title">{todo.text}</h5>
        </div>
      </div>
    </React.Fragment>
  );
}

export default Todo;

现在在主App.js文件中,进行突出的更新。

import React, { useState } from "react";
import Todo from "./components/todo";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <Todo todo={todo} key={index} />
      ))}
    </React.Fragment>
  );
}

export default App;

现在我们仍然在页面上渲染了三个todo项目,但使用的是Function Components来完成。


添加一个文本输入

我们需要更多的事情要做。为此,我们可以创建一个新的组件,它将容纳一个表单和文本输入字段来添加一个新的todo。很快我们将看到如何更新状态添加一个名为todoform.jsx的新文件并将其添加到src/components中。

import React, { useState } from "react";

function TodoForm() {
  const [value, setValue] = useState("");

  return (
    <form>
      <input
        type="text"
        className="form-control"
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Type then hit *Enter*"
      />
    </form>
  );
}

export default TodoForm;

现在我们可以在主App.js文件中导入并渲染它。

import React, { useState } from "react";
import Todo from "./components/todo";
import TodoForm from "./components/todoform";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <Todo todo={todo} key={index} />
      ))}
      <TodoForm />
    </React.Fragment>
  );
}

export default App;

调用setTodos()

我们已经准备好更新我们的 todos变量的状态。我们可以在组件中引发一个事件,然后在组件中处理该事件。我们可以在App.js中通过传递一个道具作为的属性,以及定义handleAddTodo()事件处理程序来开始。两者都在这里被强调。

import React, { useState } from "react";
import Todo from "./components/todo";
import TodoForm from "./components/todoform";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  const handleAddTodo = text => {
    const newToDos = [...todos, { text }];
    setTodos(newToDos);
  };

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <Todo todo={todo} key={index} />
      ))}
      <TodoForm onAddTodo={handleAddTodo} />
    </React.Fragment>
  );
}

export default App;

在这一点上,表单有一个onSubmit动作。这意味着当用户点击 "Enter "键时,表单的值将被传递给 handleSubmit函数。在该函数中,一个 onAddTodo事件被引发。我们可以访问这个事件,因为它是作为一个道具被传递到组件中的。当这个事件被引发时, handleAddTodo函数在App.js中运行。在这一点上。 setTodos()被调用,并且状态被更新


将一个Todo标记为完成

现在让我们在组件中添加一个按钮,以便能够将一个项目标记为完成。要做到这一点,有几个步骤。首先,我们可以在todo.jsx组件文件中添加这个按钮。

import React from "react";

function Todo({ todo, index, onComplete }) {
  return (
    <React.Fragment>
      <div key={index} className="card mb-3">
        <div className="card-body">
          <h5
            className="card-title"
            style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
          >
            {todo.text}
          </h5>
          <button onClick={() => onComplete(index)} className="btn btn-success">
            Mark Complete
          </button>
        </div>
      </div>
    </React.Fragment>
  );
}

export default Todo;

以上是重要的亮点。在第3行,我们需要接受 onComplete托词到组件中。在第10行,我们熟悉的三元操作符的使用,是为了决定是否对todo项目的文本进行穿行处理。最后,你可以看到一个新的按钮,当点击它时,会引发 onComplete事件,同时也接受索引作为一个参数。我们需要这个索引来知道哪个todo项目已经完成。现在我们需要对App.js进行以下更新。

import React, { useState } from "react";
import Todo from "./components/todo";
import TodoForm from "./components/todoform";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  const handleAddTodo = text => {
    const newToDos = [...todos, { text }];
    setTodos(newToDos);
  };

  const handleComplete = index => {
    const newToDos = [...todos];
    newToDos[index].isCompleted = true;
    setTodos(newToDos);
  };

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <Todo
          todo={todo}
          key={index}
          onComplete={handleComplete}
          index={index}
        />
      ))}
      <TodoForm onAddTodo={handleAddTodo} />
    </React.Fragment>
  );
}

export default App;

有一个新的事件处理程序,名为 handleComplete()的事件处理程序。这个事件处理程序在用户点击按钮时运行,并引发了 onComplete事件。再一次,我们看到setTodos()函数被调用在 handleComplete()的内部调用,以更新状态。最后,注意到嵌入的有两个新的属性,即 onCompleteindex.这就是我们可以在todo.jsx中访问它们的原因。这是React的一个有时很棘手的部分,那就是跟踪组件之间的数据共享。所以我们现在来看看这个动作。


将一个Todo标记为未完成

按照我们上面使用的惯例,现在应该很容易实现一个按钮,当点击该按钮时,将一个todo标记为未完成。
todo.jsx

import React from "react";

function Todo({ todo, index, onComplete, onUnfinished }) {
  return (
    <React.Fragment>
      <div key={index} className="card mb-3">
        <div className="card-body">
          <h5
            className="card-title"
            style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
          >
            {todo.text}
          </h5>
          <button onClick={() => onComplete(index)} className="btn btn-success">
            Mark Complete
          </button>{" "}
          <button
            onClick={() => onUnfinished(index)}
            className="btn btn-secondary"
          >
            Mark Unfinished
          </button>
        </div>
      </div>
    </React.Fragment>
  );
}

export default Todo;

App.js

import React, { useState } from "react";
import Todo from "./components/todo";
import TodoForm from "./components/todoform";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  const handleAddTodo = text => {
    const newToDos = [...todos, { text }];
    setTodos(newToDos);
  };

  const handleComplete = index => {
    const newToDos = [...todos];
    newToDos[index].isCompleted = true;
    setTodos(newToDos);
  };

  const handleUnfinished = index => {
    const newToDos = [...todos];
    newToDos[index].isCompleted = false;
    setTodos(newToDos);
  };

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <Todo
          todo={todo}
          key={index}
          onComplete={handleComplete}
          index={index}
          onUnfinished={handleUnfinished}
        />
      ))}
      <TodoForm onAddTodo={handleAddTodo} />
    </React.Fragment>
  );
}

export default App;

现在我们也可以把一个todo标记为未完成。


删除一个Todo

为了完善这个应用程序,我们可以再添加一个按钮来删除一个todo。再一次,这遵循了同样的模式。
App.js

import React, { useState } from "react";
import Todo from "./components/todo";
import TodoForm from "./components/todoform";

function App() {
  const [todos, setTodos] = useState([
    { text: "Learn React Hooks", isCompleted: false },
    { text: "Take a Drive", isCompleted: false },
    { text: "Play some games", isCompleted: false }
  ]);

  const handleAddTodo = text => {
    const newToDos = [...todos, { text }];
    setTodos(newToDos);
  };

  const handleComplete = index => {
    const newToDos = [...todos];
    newToDos[index].isCompleted = true;
    setTodos(newToDos);
  };

  const handleUnfinished = index => {
    const newToDos = [...todos];
    newToDos[index].isCompleted = false;
    setTodos(newToDos);
  };

  const handleDeleteTodo = index => {
    const newToDos = [...todos];
    newToDos.splice(index, 1);
    setTodos(newToDos);
  };

  return (
    <React.Fragment>
      {todos.map((todo, index) => (
        <Todo
          key={index}
          index={index}
          todo={todo}
          onComplete={handleComplete}
          onUnfinished={handleUnfinished}
          onDeleteTodo={handleDeleteTodo}
        />
      ))}
      <TodoForm onAddTodo={handleAddTodo} />
    </React.Fragment>
  );
}

export default App;

todo.jsx

import React from "react";

function Todo({ todo, index, onComplete, onUnfinished, onDeleteTodo }) {
  return (
    <React.Fragment>
      <div className="card mb-3">
        <div className="card-body">
          <h5
            className="card-title"
            style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
          >
            {todo.text}
          </h5>
          <button onClick={() => onComplete(index)} className="btn btn-success">
            Mark Complete
          </button>{" "}
          <button
            onClick={() => onUnfinished(index)}
            className="btn btn-secondary"
          >
            Mark Unfinished
          </button>{" "}
          <button
            onClick={() => onDeleteTodo(index)}
            className="btn btn-danger"
          >
            Delete Todo
          </button>
        </div>
      </div>
    </React.Fragment>
  );
}

export default Todo;

最后的结果允许你添加一个todo,将一个todo标记为完成,将一个todo标记为未完成,以及删除一个todo。


React useState钩子总结

在本教程中,我们看了一下React中新的useState()钩子。在这样做的时候,我们建立了一个小的todo应用程序。useState()函数现在允许Web开发者为Function Components添加状态处理能力,而在过去,只有Class Components有这样的能力。此外,这种新的方法消除了在JavaScript中使用这种方法的混乱性。