用TypeScript构建React应用程序
React是一种病毒式的,也是目前最好的前端库之一;这已经成为许多开发者创建前端应用程序的主要库。
另一方面,Typescript是一种严格的、静态类型的编程语言,使我们的JavaScript代码更具可预测性。作为开发者,我们的目标不仅仅是创造产品,而是创造可以随时预测其行为的产品;这是创建Typescript的主要原因之一。
简介
使用Typescript来构建我们的React应用程序将使我们的react应用程序更加可预测,因为我们将能够在运行时(编译期间)捕获大量的错误。
在这篇文章中,我将在构建一个简单的Todo应用程序时解释以下内容。
- 如何引导一个react typescript应用程序。
- 什么是Typescript中的类型和接口以及如何使用它们。
- 如何在功能组件中为状态和道具声明类型。
- 事件以及如何在我们的react应用中处理事件类型。
- 如何在类基础组件中声明状态和道具的类型。
这篇文章并不是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 应用程序,然后我们就可以像平时一样设置服务器了。我们的应用程序结构应该是这样的。

让我们测试一下一切是否正常,我们需要通过输入以下内容来运行该应用程序。
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 组件里面,AddTodoComponent 和TodosComponent 。这两个组件将分别负责创建一个新的todo和显示已经创建的todos列表。
-
AddTodoComponent:这个组件包含一个有一个输入字段的表单。当点击添加按钮时,新的todo数据被添加到状态中,然后TodosComponent。 -
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 是一个一般的类型,这意味着它接纳了另一个类型。这个类型表示这个函数期待的道具的种类。这是有意义的,因为我们要确保该组件能被正确使用;否则,就会出现编译错误。
这个组件期望有以下道具。
todos:这是前面声明的类型ITodos。todos数组是所有todos的数组。toggleTodos:这个函数接收一个整数(id),不返回任何东西(void);它所做的只是改变当前的完成字段的ITodo.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 和三个函数,它们作为道具被传递给子组件;元素中没有任何特别的东西。

如何在一个基于类的组件中为状态和道具声明类型
到目前为止,我们一直在讨论功能型组件,但如果你所使用的代码库是基于类的组件,那该怎么办呢? 那么我们需要想办法将我们的应用程序与之连接起来。
除了我们创建状态和道具的方式,一切都保持不变,这是因为它们的创建方式不同。我们需要把道具类型和状态类型作为一个通用类型传递给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应用中。