从头开始创建你的第一个 React Typescript 项目
本教程附带的其他资源
设置你的环境
使用 create-react-app 进行引导
探索引导应用程序
精简为“Hello World”
我们的待办事项列表应用程序的快速模拟
创建待办事项列表项
切换待办事项
创建 TodoList 组件
添加待办事项
结论
今天我们将学习如何编写一个 React Typescript 应用程序。正如任何新的前端语言或框架的传统,我们将学习如何编写一个待办事项列表应用程序!尽管待办事项列表应用程序的教程已经太多了,但我还是喜欢使用它,因为你可以将它与你曾经使用过的其他框架进行同类比较。
如果您喜欢本教程,请给予💓、🦄或🔖并考虑:
本教程附带的其他资源
本教程附带一个 GitHub 仓库!此外,如果您喜欢通过 YouTube 观看教程,我还录制了一个由三部分组成的 YouTube 教程系列。这两个视频都可以在下面找到:
设置你的环境
首先,需要满足几个先决条件。首先,如果你还没有安装 Node,则需要先安装。
通过在命令行中输入以下命令来确保已安装 Node node -v
。您应该会看到版本信息。我的当前版本是 10.15.2,但您的版本可能有所不同。
node -v
我们可以使用 npm 来管理 Node 包,但我更喜欢使用 yarn。因此,我打算使用 npm 全局安装 yarn:npm i -g yarn
npm i -g yarn
如果成功,你应该可以通过输入以下命令来查看你的 yarn 版本yarn -v
。同样,你的版本可能与我的不同:
yarn -v
现在我们准备出发了!
使用 create-react-app 进行引导
为了省去设置的麻烦,让我们更快地开始,我们可以使用 来引导我们的应用create-react-app
!我在生产环境中经常使用 React,但我通常还是会以它create-react-app
作为模板来开始。
让我们使用 yarn 创建一个 React 应用。我们需要确保指定要使用 Typescript,并且要将应用命名为todo-list
:
yarn create react-app todo-list --template typescript
你应该会看到一系列下载正在进行,最后会显示cd
进入新目录并开始编码的指示。开始吧!
探索引导应用程序
确保您位于新todo-list
目录中。您应该会看到以下文件夹和文件。虽然我们大部分工作都会在这个src
文件夹中完成,但了解其他文件夹的作用也很重要。以下是简要概述:
- node_modules - 包含您的应用程序使用的第三方库的代码。
- 公共- 包含有助于构建最终应用程序的资产,包括
index.html
应用程序的图标等。 - src——包含您最常使用的应用程序的源代码。
- .gitignore - 指定源代码控制中要忽略哪些文件。
- package.json - 包含您的应用程序的配置,包括依赖项和脚本等内容。
- README.md - 从有关 create-react-app 的信息开始,但在实际应用程序中,您应该描述应用程序本身。
- tsconfig.json - 包含 typescript 编译器的配置。
- yarn.lock - 包含所有项目依赖项的确切版本。应将其纳入版本控制。
启动应用程序
太好了,浏览得够多了。让我们通过yarn start
在命令提示符中运行来启动应用程序。
导航至http://localhost:3000
,您应该会看到我们的应用程序的全部初始功能:
注意:作为 create-react-app 的一部分,我们的应用会在每次修改时自动热加载!这意味着我们通常可以yarn start
在控制台中保持运行,而无需重启它。实际上,我们会发现,当 TypeScript 编译器崩溃或我们添加或删除文件时,我们的应用偶尔需要重启服务器。
精简为“Hello World”
一切看起来很酷,但我们希望从本教程开始,相对来说比较新。因此,我们将从文件夹中删除一些文件src
,并修改一些文件。
删除文件
cd src
rm App.css App.test.tsx index.css logo.svg serviceWorker.ts setupTests.ts
剩下的文件应该是App.tsx
、、index.tsx
和react-app-env.d.ts
。
编辑代码
首先,我们来index.tsx
删除对 Service Worker 的引用index.css
。你的文件最终应该如下所示:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
接下来,打开App.tsx
并删除对 logo 和 CSS 文件的引用。此外,删除App
函数中的所有内容,并将其替换为返回包含文本“Hello World”的 React 片段。
import React from 'react';
function App() {
return <>Hello World</>;
}
export default App;
现在查看我们的应用程序!
我们的待办事项列表应用程序的快速模拟
React 的一个优点是你的组件结构通常可以紧密遵循你的设计。在我们的待办事项列表应用示例中,我们可以假设给出了以下模拟:
重要的是,我们可以看到我们的应用程序有一个TodoListItem
、一个TodoList
和一个AddTodoForm
。最终,我们的应用程序结构将与此相对应。
创建待办事项列表项
让我们开始工作吧!TodoListItem.tsx
在您的src
文件夹中创建一个名为 的新文件。
让我们编写一个在列表项内包含占位符内容的基本 React 组件:
import React from 'react';
export const TodoListItem = () => {
return <li>content</li>;
};
酷!现在,让我们添加一些 props。从这里开始,我们就可以开始使用 TypeScript 了!我们的TodoListItem
组件至少会接受一个todo
item 作为 props。这个todo
item 会有text
一个属性,即string
,以及一个complete
属性,即boolean
。
一旦我们定义了我们的道具,我们就可以将其声明TodoListItem
为一个功能组件(React.FC
),然后将我们的Props
作为泛型传递。
import React from 'react';
interface Todo {
text: string;
complete: boolean;
}
interface Props {
todo: Todo;
}
export const TodoListItem: React.FC<Props> = props => {
return <li>content</li>;
};
接下来,让我们实际使用之前描述的 props。我们在每个列表项中放置一个复选框。当 为 时,复选框将被选中todo.complete
。true
标签将使用我们的 进行填充todo.text
。
另外,如果待办事项已完成,我们可以为其添加一个删除线。我们可以使用style
属性来实现这一点。
import React from 'react';
interface Todo {
text: string;
complete: boolean;
}
interface Props {
todo: Todo;
}
export const TodoListItem: React.FC<Props> = ({ todo }) => {
return (
<li>
<label
style={{ textDecoration: todo.complete ? 'line-through' : undefined }}
>
<input type="checkbox" checked={todo.complete} /> {todo.text}
</label>
</li>
);
};
创建类型声明文件
虽然我们可以Todo
在这个文件中保留声明,但它会在整个应用程序中使用。我们可以在这里导出它,然后在应用程序中任何需要的地方导入它,或者创建一个类型声明文件。我们称之为“文件” types.d.ts
,并将其放在我们的src
文件夹中。文件的好处在于*.d.ts
,我们的编译器会将其中的类型识别为项目的全局变量,我们无需显式地导入或导出它们。
类型.d.ts
interface Todo {
text: string;
complete: boolean;
}
现在我们可以删除Todo
声明的接口TodoListItem.tsx
,一切仍然可以正常工作。
在我们的应用程序中包括 TodoListItem
当然,到目前为止我们只编写了一个组件;我们仍然需要将其包含在我们的应用程序中。我们现在就开始吧。转到App.tsx
并导入组件。
import React from 'react';
import { TodoListItem } from './TodoListItem';
function App() {
return (
<>
<TodoListItem />
</>
);
}
export default App;
你可能会注意到,如果我们现在尝试运行我们的应用程序,它将编译失败——我们定义了我们的TodoListItem
参数todo
,但我们没有提供它!让我们改变这一点:我们将创建一个Todos
数组。
我们将创建两个项目并将它们放入无序列表中:
import React from 'react';
import { TodoListItem } from './TodoListItem';
const todos: Todo[] = [
{
text: 'Walk the dog',
complete: false,
},
{
text: 'Write app',
complete: true,
},
];
function App() {
return (
<ul>
<TodoListItem todo={todos[0]} />
<TodoListItem todo={todos[1]} />
</ul>
);
}
export default App;
现在让我们在浏览器中检查我们的应用程序:
切换待办事项
接下来我们想要实现切换待办事项的功能。我们不能再依赖todos
数组,而是需要一些状态来管理它们。为此,我们将useState
在App.tsx
文件中使用 React hook。我们可以将todos
数组重命名为,initialTodos
因为它实际上只是表示初始状态。
import React, { useState } from 'react';
import { TodoListItem, Todo } from './TodoListItem';
const initialTodos: Todo[] = [
{
text: 'Walk the dog',
complete: false,
},
{
text: 'Write app',
complete: true,
},
];
function App() {
const [todos, setTodos] = useState(initialTodos);
return (
<ul>
<TodoListItem todo={todos[0]} />
<TodoListItem todo={todos[1]} />
</ul>
);
}
export default App;
我们希望能够切换待办事项。我们可以toggleTodo
在App.tsx
文件中创建一个函数来实现。该toggleTodo
函数将接受选定的待办事项,并切换complete
该待办事项的 prop。
然后,我们可以传递toggleTodo
给 each TodoListItem
。
import React, { useState } from 'react';
import { TodoListItem } from './TodoListItem';
const initialTodos: Todo[] = [
{
text: 'Walk the dog',
complete: false,
},
{
text: 'Write app',
complete: true,
},
];
function App() {
const [todos, setTodos] = useState(initialTodos);
const toggleTodo = (selectedTodo: Todo) => {
const newTodos = todos.map(todo => {
if (todo === selectedTodo) {
return {
...todo,
complete: !todo.complete,
};
}
return todo;
});
setTodos(newTodos);
};
return (
<ul>
<TodoListItem todo={todos[0]} toggleTodo={toggleTodo} />
<TodoListItem todo={todos[1]} toggleTodo={toggleTodo} />
</ul>
);
}
export default App;
我们的 linter 现在很生气。这是因为toggleTodo
不是我们 的预期 prop TodoListItem
。让我们将其添加为预期 prop。趁此机会,我们在文件ToggleTodo
中声明一个类型types.d.ts
:
类型.d.ts
interface Todo {
text: string;
complete: boolean;
}
type ToggleTodo = (selectedTodo: Todo) => void;
现在,当我们将其添加toggleTodo
为的道具时TodoListItem
,让我们在元素onClick
的处理程序中执行它input
。
TodoListItem.tsx
import React from 'react';
interface Props {
todo: Todo;
toggleTodo: ToggleTodo;
}
export const TodoListItem: React.FC<Props> = ({ todo, toggleTodo }) => {
return (
<li>
<label
style={{ textDecoration: todo.complete ? 'line-through' : undefined }}
>
<input
type="checkbox"
checked={todo.complete}
onClick={() => {
toggleTodo(todo);
}}
/>{' '}
{todo.text}
</label>
</li>
);
};
让我们打开应用程序并开始切换待办事项。成功了!

创建 TodoList 组件
如果您还记得的话,我们的应用程序模拟包含一个TodoList
包含所有待办事项的组件。
让我们创建这个组件。它需要接受以下 props:
todos
映射的列表toggleTodo
传递给每个待办事项的函数。
在这个组件中需要注意的是,我们映射了 ,todos
而不是逐个列出它们。这显然是个好主意,因为理论上我们可以有任意数量的todos
。需要注意的是,当我们迭代 时todos
,我们会给每个 传递TodoListItem
一个key
prop。这是 React 的 diffing 算法所需要的,它用于协调元素数组。
待办事项列表.tsx
import React from 'react';
import { TodoListItem } from './TodoListItem';
interface Props {
todos: Todo[];
toggleTodo: ToggleTodo;
}
export const TodoList: React.FC<Props> = ({ todos, toggleTodo }) => {
return (
<ul>
{todos.map(todo => (
<TodoListItem key={todo.text} todo={todo} toggleTodo={toggleTodo} />
))}
</ul>
);
};
App.tsx
现在,我们可以用 替换文件中的大部分代码了TodoList
。我们必须记住传递正确的 props 给它——虽然如果我们忘记了,TypeScript 编译器会警告我们,这很棒!
应用程序.tsx
import React, { useState } from 'react';
import { TodoList } from './TodoList';
const initialTodos: Todo[] = [
{
text: 'Walk the dog',
complete: false,
},
{
text: 'Write app',
complete: true,
},
];
function App() {
const [todos, setTodos] = useState(initialTodos);
const toggleTodo = (selectedTodo: Todo) => {
const newTodos = todos.map(todo => {
if (todo === selectedTodo) {
return {
...todo,
complete: !todo.complete,
};
}
return todo;
});
setTodos(newTodos);
};
return <TodoList todos={todos} toggleTodo={toggleTodo} />;
}
export default App;
如果我们在浏览器中打开我们的应用程序,我们应该能够确认一切正常。
添加待办事项
让我们创建一个名为 的新组件AddTodoForm
,以便添加待办事项。目前,我们只需创建一个不执行任何操作的表单并将其添加到App.tsx
文件中。
AddTodoForm.tsx
import React from 'react';
export const AddTodoForm: React.FC = () => {
return (
<form>
<input type="text" />
<button type="submit">Add Todo</button>
</form>
);
};
应用程序.tsx
import React, { useState } from 'react';
import { TodoList } from './TodoList';
import { AddTodoForm } from './AddTodoForm';
const initialTodos: Todo[] = [
{
text: 'Walk the dog',
complete: false,
},
{
text: 'Write app',
complete: true,
},
];
function App() {
const [todos, setTodos] = useState(initialTodos);
const toggleTodo = (selectedTodo: Todo) => {
const newTodos = todos.map(todo => {
if (todo === selectedTodo) {
return {
...todo,
complete: !todo.complete,
};
}
return todo;
});
setTodos(newTodos);
};
return (
<>
<TodoList todos={todos} toggleTodo={toggleTodo} />
<AddTodoForm />
</>
);
}
export default App;
现在我们可以在浏览器中看到表单出现了。当我们尝试添加待办事项并点击提交时,除了页面重新加载之外什么也没有发生。
现在,让我们让表单添加内容。首先,我们可以addTodo
在App.tsx
文件中创建一个函数,该函数最终将传递给表单。我们可以AddTodo
在types.d.ts
文件中声明其类型。
由于每个新事物todo
一开始都是不完整的,我们实际上只需要text
支撑来创建一个。
类型.d.ts
interface Todo {
text: string;
complete: boolean;
}
type ToggleTodo = (selectedTodo: Todo) => void;
type AddTodo = (text: string) => void;
应用程序.tsx
import React, { useState } from 'react';
import { TodoList } from './TodoList';
import { AddTodoForm } from './AddTodoForm';
const initialTodos: Todo[] = [
{
text: 'Walk the dog',
complete: false,
},
{
text: 'Write app',
complete: true,
},
];
function App() {
const [todos, setTodos] = useState(initialTodos);
const toggleTodo: ToggleTodo = (selectedTodo: Todo) => {
const newTodos = todos.map(todo => {
if (todo === selectedTodo) {
return {
...todo,
complete: !todo.complete,
};
}
return todo;
});
setTodos(newTodos);
};
const addTodo: AddTodo = (text: string) => {
const newTodo = { text, complete: false };
setTodos([...todos, newTodo]);
};
return (
<>
<TodoList todos={todos} toggleTodo={toggleTodo} />
<AddTodoForm addTodo={addTodo} />
</>
);
}
export default App;
再次,我们会在此时遇到一个熟悉的编译错误:AddTodoFrom
不期望addTodo
prop,所以编译器报错。好!让我们通过将 prop 添加到 来解决这个问题AddTodoForm
。
import React from 'react';
interface Props {
addTodo: AddTodo;
}
export const AddTodoForm: React.FC<Props> = ({ addTodo }) => {
return (
<form>
<input type="text" />
<button type="submit">Add Todo</button>
</form>
);
};
现在我们的编译器错误已经消失,但我们的表单仍然没有任何反应。为了让它正常工作,我们需要做以下几件事:
- 使用维护内部
text
状态useState
。这将允许我们维护新待办事项文本的状态。 - 绑定
text
到input
值。 setText
在输入的处理程序中使用设置文本onChange
。e.target.value
包含当前值。onClick
向提交按钮添加处理程序以提交输入的文本。- 确保取消实际提交表单的默认事件。
addTodo
使用并传递它来添加待办事项text
。text
通过设置为空字符串来清除我们的表单。
import React, { useState } from 'react';
interface Props {
addTodo: AddTodo;
}
export const AddTodoForm: React.FC<Props> = ({ addTodo }) => {
const [text, setText] = useState('');
return (
<form>
<input
type="text"
value={text}
onChange={e => {
setText(e.target.value);
}}
/>
<button
type="submit"
onClick={e => {
e.preventDefault();
addTodo(text);
setText('');
}}
>
Add Todo
</button>
</form>
);
};
就这样!回到应用程序,你现在应该能够添加新的待办事项并与它们进行交互了。

结论
感谢你的关注!希望这篇文章能帮助你开启使用 React 和 Typescript 打造精彩用户界面的旅程。
文章来源:https://dev.to/nas5w/creating-your-first-react-typescript-project-from-scratch-4f65