今天我们将学习如何编写一个React Typescript应用程序。按照任何新的前端语言或框架的传统,我们将学习如何编写一个待办事项列表应用程序尽管待办事项列表的教程已经过气了,但我还是喜欢用它,因为你可以和其他框架做一个苹果对苹果的比较,你也可以用它来编写同样的应用程序。
伴随本教程的其他资源
本教程有一个配套的github资源库!此外,我还录制了一个由三部分组成的YouTube教程系列,如果这是你喜欢的教程消费方式。两者都可以在下面找到。
- [Github资源库]
- [YouTube教程系列]
获取你的环境设置
要开始使用,有几个先决条件。首先,如果你还没有Node,你需要安装它。
- [安装nodejs]
在你的命令行中输入node -v ,以确保你已经安装了node。你应该看到一个版本显示。我的版本目前是10.15.2,但你的版本可能不同。
node -v
我们可以使用npm来管理我们的node包,但我更喜欢yarn。因此,我将使用npm来全局安装yarn。npm i -g yarn
npm i -g yarn
如果这样做了,你应该可以通过输入yarn -v 来查看你的yarn版本。同样,你的版本可能与我的不同。
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 到你的新目录并开始编码。开始吧!
探索Bootstrapped应用程序
确保你在新的todo-list 目录中。你应该看到以下文件夹和文件。虽然我们将在src 文件夹中进行大部分工作,但重要的是要了解其他文件的作用。下面是一个简单的概述。
- node_modules- 包含你的应用程序使用的第三方库的代码。
- public- 包含有助于建立你的最终应用程序的资产,包括像
index.html和你的应用程序的favicon。 - src- 包含你最常使用的应用程序的源代码。
- .gitignore- 指定哪些文件在源码控制中被忽略。
- package.json- 包含你的应用程序的配置,包括依赖关系和脚本等内容。
- README.md- 以关于 create-react-app 的信息开始,但在真正的应用中你应该描述应用本身。
- tsconfig.json- 包含typecript编译器的配置。
- 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 ,删除对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 ,删除对标志和CSS文件的引用。同时,删除App 函数中的所有内容,取而代之的是返回一个带有 "Hello World "文字的React片段。
import React from 'react';
function App() {
return <>Hello World</>;
}
export default App;
现在看看我们的应用程序吧
我们的Todo List应用程序的快速模拟
React有一个很好的方面,就是你的组件结构往往可以紧跟你的设计。在我们的待办事项列表应用程序的例子中,我们可以假装我们得到了下面的模拟。
重要的是,我们可以看到我们的应用程序有一个TodoListItem ,一个TodoList ,和一个AddTodoForm 。最终,我们的应用程序结构将最终反映在这里。
创建一个Todo列表项目
让我们开始工作吧!在你的src 文件夹中创建一个名为TodoListItem.tsx 的新文件。
让我们写一个基本的React组件,在一个列表项中有占位符内容。
import React from 'react';
export const TodoListItem = () => {
return <li>content</li>;
};
酷。现在,让我们添加一些道具。这就是我们将开始进行类型化处理的地方!我们的。我们的TodoListItem 组件将至少接受一个todo 项目作为道具。这个todo 项目将有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 。
此外,如果我们的todo项目已经完成,让我们把它打上一个删除标记。我们可以用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 文件的好处是,我们的编译器会将其中的类型识别为项目的全局,我们不需要明确地导入或导出它们。
types.d.ts
interface Todo {
text: string;
complete: boolean;
}
现在我们可以删除在TodoListItem.tsx 中声明的Todo 接口,一切都应该可以正常工作。
在我们的应用程序中包括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
我们要做的下一件事是真正能够切换todo项目。我们不能再依赖我们的todos 数组,而是需要一些状态来管理事情。为此,我们将在我们的App.tsx 文件中使用useState React钩。我们可以将我们的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;
我们将希望能够切换todos。我们可以通过在我们的App.tsx 文件中创建一个toggleTodo 函数来做到这一点。toggleTodo 函数将接收一个选定的待办事项,并切换该待办事项的complete 道具。
然后,我们可以将toggleTodo 传递给每个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;
我们的林特现在很疯狂。这是因为toggleTodo 并不是我们的TodoListItem 的一个预期道具。让我们把它添加为一个预期道具。既然如此,让我们在我们的types.d.ts 文件中声明一个ToggleTodo 类型。
types.d.ts
interface Todo {
text: string;
complete: boolean;
}
type ToggleTodo = (selectedTodo: Todo) => void;
现在,当我们为TodoListItem 添加toggleTodo 作为一个道具时,让我们在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>
);
};
让我们打开我们的应用程序,开始切换我们的todo项目。它起作用了!
创建一个TodoList组件
如果你还记得,我们的应用程序模拟包括一个TodoList 组件来包含我们所有的todos。
让我们来创建这个组件。它必须使用以下道具。
- 要映射的
todos的列表 - 传递给每个todo项目的
toggleTodo函数。
值得注意的是,在这个组件中,我们正在映射我们的todos ,而不是单独列出它们。这显然是个好主意,因为理论上我们可以有任何数量的todos 。注意,当我们遍历todos ,我们传递给每个TodoListItem 一个key 。这是React的diffing算法所需要的,以协调元素的数组。
TodoList.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>
);
};
现在,我们可以用我们的TodoList 替换我们在App.tsx 文件中的大部分代码。我们必须记住给它传递正确的道具--尽管如果我们忘记了,typescript编译器会对我们大喊大叫,这很好!
App.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;
如果我们在浏览器中打开我们的应用程序,我们应该能够确认一切都在工作。
添加Todo项目
让我们做一个新的组件,叫做AddTodoForm ,这样我们就可以添加Todo项目。现在,我们只是做一个不做任何事情的表单,并把它添加到我们的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>
);
};
App.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;
现在我们可以看到在我们的浏览器中出现了这个表单。当我们尝试添加一个todo并点击提交时,除了页面重新加载外,并没有什么真正发生。
现在,让我们让我们的表单添加东西。首先,我们可以在我们的App.tsx 文件中创建一个addTodo 函数,这个函数最终将被传递给我们的表单。我们可以在我们的types.d.ts 文件中声明类型AddTodo 。
由于每个新的todo 开始时都是不完整的,我们实际上只需要text 道具来创建一个。
types.d.ts
interface Todo {
text: string;
complete: boolean;
}
type ToggleTodo = (selectedTodo: Todo) => void;
type AddTodo = (text: string) => void;
App.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 的道具,所以编译器会出错。很好!让我们通过添加道具到我们的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>
);
};
现在我们的编译器错误消失了,但我们的表单仍然没有任何作用。为了让它正常工作,我们要做一些事情。
- 使用
useState维护内部text状态。这将使我们能够维护新的todo项目的文本状态。 - 将
text与input的值绑定。 - 在输入的
onChange处理程序中使用setText设置文本。e.target.value包含当前值。 - 在提交按钮上添加一个
onClick处理程序来提交输入的文本。 - 请确保取消实际提交表单的默认事件。
- 使用
addTodo添加todo,并传递给它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>
);
};
就这样,你拥有了它!如果你回到应用程序,你现在应该能够添加新的todo项目并与之交互。
总结
谢谢你的关注!希望这能给你在使用React和Typescript制作令人难以置信的用户界面的旅程中提供一个良好的开端。