如何用TypeScript构建一个React应用程序

269 阅读7分钟

用TypeScript构建React应用程序

React是一种病毒式的,也是目前最好的前端库之一;这已经成为许多开发者创建前端应用程序的主要库。

另一方面,Typescript是一种严格的、静态类型的编程语言,使我们的JavaScript代码更具可预测性。作为开发者,我们的目标不仅仅是创造产品,而是创造可以随时预测其行为的产品;这是创建Typescript的主要原因之一。

简介

使用Typescript来构建我们的React应用程序将使我们的react应用程序更加可预测,因为我们将能够在运行时(编译期间)捕获大量的错误。

在这篇文章中,我将在构建一个简单的Todo应用程序时解释以下内容。

  1. 如何引导一个react typescript应用程序。
  2. 什么是Typescript中的类型和接口以及如何使用它们。
  3. 如何在功能组件中为状态和道具声明类型。
  4. 事件以及如何在我们的react应用中处理事件类型。
  5. 如何在类基础组件中声明状态和道具的类型。

这篇文章并不是React或Typescript的初学者指南,相反,它将指导我们在React应用程序中集成Typescript。

设置一个React Typecript应用程序

我们使用Create React App来设置我们的react应用,这样我们就不需要担心所有的webpack和babel的压力。

你可能已经猜到了,要设置一个react typescript应用程序,我们仍然要使用create-react-app,只是多了一些标志。在我们的终端,我们会输入以下内容来启动我们的应用程序。

npx create-react-app todo-app --template typescript

这个命令将为我们创建一个 react typescript 应用程序,然后我们就可以像平时一样设置服务器了。我们的应用程序结构应该是这样的。

File structure

让我们测试一下一切是否正常,我们需要通过输入以下内容来运行该应用程序。

npm start #or yarn start

类型和接口

在我们开始讨论我们的应用程序之前,让我们先谈谈Typescript的一个基本特征,类型和接口。

这两样东西非常相似,我们甚至可能无法发现它们的区别,但还是有一些区别的。类型和接口主要是告诉我们更多关于一个对象或数据是什么,它的数据类型,它能包含什么,以及所有。

让我们来看看一些例子。

let num: number = 4
const name: string = "John Doe"

上面的例子是很直接的。在这种情况下,我们不必指定变量的类型;Typescript足够聪明,可以确定其类型。

type something = string
type IProps = {
  disabled: false,
  title: "Hello" | "Hi"
}

interface Props {
  disabled: boolean,
  title: string
}

const props_types: IProps = {
  disabled: false,
  title: "Hello"
}

const props_interface: Props = {
  disabled: false,
  title: "Hello"
}

const data: something = "Hey"

我们可以看到,我们能够为更复杂的数据定义一个类型,我们可以用这个想法来为更复杂的数据建模。从这个例子中,我们也许能够看到类型和接口之间的区别。

关于我们应用程序的更多细节

在我们的App.tsx 文件中,我们需要删除函数内部的所有内容,并返回一个简单的文本。

import React 'react'

function App() {
  return <h1>Hello World</h1>
}

export default App;

现在让我们来思考一下应用程序的逻辑。为了使事情简单化,我们将整个应用程序的代码放在这个文件里。

我们需要两个组件放在App 组件里面,AddTodoComponentTodosComponent 。这两个组件将分别负责创建一个新的todo和显示已经创建的todos列表。

  1. AddTodoComponent:这个组件包含一个有一个输入字段的表单。当点击添加按钮时,新的todo数据被添加到状态中,然后TodosComponent

  2. TodosComponent:这个组件包含一个所有创建的todos的列表。我们可以切换todo的完成字段并删除一个todo。

如何在react应用程序中声明类型

让我们为我们的Todo对象创建一个类型,在App.tsx 文件中,在导入后放置这个对象。

type ITodo = { 
  // Our todo should have the title and completed fields and the id field to 
  id: number;
  title: string;
  completed: boolean;
}

type ITodos = {
  todos: ITodo[], // Our Todos is an array of Todo
}

如何在一个功能组件中声明状态和道具

我们会看到在功能组件中为props ,我们也会看到如何在功能组件中为状态声明类型的不同方式。

要为一个功能组件声明props ,我们可以对函数使用正常的类型定义,或者我们可以将类型作为通用类型传递给React.FC 类型。

让我们看看如何通过创建我们的AddTodoComponent ,来演示初始化。这个组件有它的状态,它也接受props ,有一个字段,addTodos ,这个函数接收一个字符串,不返回任何东西。它只是更新ITodos 的状态。

要在一个功能组件中声明一个状态的类型,我们所要做的就是把这个状态的类型作为一个通用类型传递给useState

const AddTodoComponent = ({addTodos} : {addTodos: (text: string) => void}) => {
  const [todo, setTodo] = React.useState<string>("");
  const submit = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    e.preventDefault();
    if (!todo) {
      alert("Please enter a todo");
    } else {
      addTodos(todo);
      setTodo("");
    }
  };
  return (
    <div className="AddTodo">
      <form>
        <input
          value={todo}
          onChange={e => {setTodo(e.target.value)}} />
        <button onClick={submit}>Add</button>
      </form>
    </div>
  );
};

组件中的内容是非常直接的,但这个组件中定义的submit 函数值得一谈。这个函数接收一个event 对象并返回void

Reaction中的事件类型

现在的问题是,我们如何知道传递给事件的类型?

好吧,React有很多事件,但我们当然不能知道分配给每个事件的类型,所以我通常做的是把事件对象放在它正在使用的处理程序中,并在它上面悬停,然后复制类型。

<button onClick={e => {console.log(e) }}>Add</button>
(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>

然后我们就可以把这个类型传给函数;这似乎很酷,对吧!?

使用'React.FC'声明道具类型

让我们在根App 组件定义下面创建我们的TodosComponent

const TodosComponent: React.FC<{
  todos: ITodos, 
  toggleTodos: (id: number) => void,
  deleteTodos: (id: number) => void
}> = ({todos, toggleTodos, deleteTodos}) => {
  const deleteTodo = (id: number) => {
    if (window.confirm(`Are you sure you want to delete todo?`)) {
      deleteTodos(id);
    }
  }
  return (
    <div className="section__todos">
    <h2>Todos</h2>
    {todos.todos.length ? <ul className="todos">
      {todos.todos.map(todo => (
        <li key={todo.id}>
          <span style={{textDecoration: todo.completed? 'line-through': 'none'}}>{todo.title}</span>
          <input 
            type="checkbox" 
            checked={todo.completed} 
            onChange={() => toggleTodos(todo.id)} />
          <button onClick={() => {deleteTodo(todo.id)}}>X</button>
        </li>
      ))}
    </ul>: <div>No Todo has been created</div>}
  </div>
  );
};

React给我们提供了一些可以使用的内置类型;其中一个是React.FC ,我们用它来告诉Typescript这个函数返回JSX,是一个功能组件。

React.FC 是一个一般的类型,这意味着它接纳了另一个类型。这个类型表示这个函数期待的道具的种类。这是有意义的,因为我们要确保该组件能被正确使用;否则,就会出现编译错误。

这个组件期望有以下道具。

  1. todos:这是前面声明的类型ITodos 。todos数组是所有todos的数组。
  2. toggleTodos:这个函数接收一个整数(id),不返回任何东西(void);它所做的只是改变当前的完成字段的ITodo.
  3. deleteTodos:这与toggleTodos 相似,只是它不是切换它,而是将它从状态中移除(ITodos 数组)。

该函数内部发生的事情是非常简单明了的。

到目前为止,我们已经创建了我们应用程序中需要的组件,但我们的应用程序是空的,因为我们没有把它连接到根App 组件。

让我们把我们的根App 组件更新为以下内容。

function App() {
  const [todos, setTodos] = React.useState<ITodos>({todos: []});
  const addTodos = (title: string) => { 
    setTodos({
      todos: [
        {title, completed: false, id: todos.todos.length+1}, 
        ...todos.todos
      ]
    });
  };
  const deleteTodos = (id: number) => {
    setTodos({
      todos: todos.todos.filter(t => t.id !== id)
    });
  };
  const toggleTodos = (id: number) => {
    setTodos({
      todos: todos.todos.map(todo => todo.id === id ? {...todo, completed: !todo.completed} : todo)
    });
  }

  return (
    <div className="App">
      <AddTodoComponent addTodos={addTodos} />
      <hr />
      <TodosComponent 
        todos={todos} 
        toggleTodos={toggleTodos}
        deleteTodos={deleteTodos} />
    </div>
  );
}

这个组件有一个状态ITodos 和三个函数,它们作为道具被传递给子组件;元素中没有任何特别的东西。

application demo

如何在一个基于类的组件中为状态和道具声明类型

到目前为止,我们一直在讨论功能型组件,但如果你所使用的代码库是基于类的组件,那该怎么办呢? 那么我们需要想办法将我们的应用程序与之连接起来。

除了我们创建状态和道具的方式,一切都保持不变,这是因为它们的创建方式不同。我们需要把道具类型和状态类型作为一个通用类型传递给React.Component

class App extends React.Component<IProps, Istate> {
  constructor(props: IProps) {
        super(props)
        this.state = {
            // your state should be in here
        }
  }

  // Your helper functions and component life cycle

  render () {
    // return your JSX here
  }
}

总结

这篇文章解释了如何通过建立一个简单的Todo应用将Typescript集成到你的React应用中。