react基础学习 附加todo实现代码
简介
学习目的
我们在学习GUI开发的时候一般思考两个问题
- 如何来设计UI?
- 如何实现前后端数据交互?
问题一
React引入了tsx语法,让我们能够在ts中自由的嵌入html,让我们能只写tsx代码,来实现UI的控制,而不是把所有的UI都放进一个html文件中,让各个部分更好维护
所react中,我们使用组件来表示各个小的部分
那与之而来的问题,react是如何将tsx代码变成网页的呢?
要知道,我们使用react可以不写任何html实现网页效果
答案是使用虚拟dom,即由react管理的dom,我们的tsx语法都会先映射到这个虚拟的dom上,然后react来对比改变的部分,然后render到浏览器中的dom上,这样还能减少对浏览器dom的更改,节省了性能
问题二
我们的数据通过hook来修改,根据不同需要,可以封装不同的修改
React(React.js或ReactJS)是由Facebook开发并维护的一款用于构建用户界面的JavaScript库。React主要用于构建单页面应用程序(Single Page Applications)中的用户界面,通过组件化开发的方式,可以更轻松地构建和维护复杂的前端应用。
React的设计目标是提高前端开发的效率和可维护性,使得开发者能够更容易地构建现代、响应式的用户界面。它广泛应用于各种Web应用开发项目,并且与其他技术(如React Native)结合,使得开发者可以用相似的方式构建Web、移动端等不同平台上的应用。
运行环境
1. 创建 React 应用
在工作目录下打开终端,输入下面命令,设置名称为my-app后全部yes
npx create-next-app@latest
2. 进入 create-react-app 创建的文件夹并安装 typescript
cd my-app
3.启动 React
npm run dev
然后会自动在浏览器打开localhost:3000
网页,然后就能看到官方示例了
HOOK
格式如下,解释 : 用setTodos来设置todos,并初始值为<Todo[]>([])这个空列表
const [todos, setTodos] = useState<Todo[]>([])
然后我们可以利用这个来管理todos,如下
const addTodo = (text: string) => {
const newTodo = {
id: Date.now(),
text,
completed: false
}
setTodos([...todos, newTodo])//打开旧的,添加新的
}
const deleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id))
//将不需要删除的筛选出来称为新的
}
const toggleTodo = (id: number) => {
setTodos(todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
}))
}
TSX
TSX是TypeScript代码中嵌套XML标记的语法扩展。方便描述用户界面的结构。
所以跟其他ts类型的使用方法相同,可以类比字符串理解
常见用法
声明
const element = <h1>Hello, world!</h1>;
格式化
const name = "Josh Perez";
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(element, document.getElementById("root"));
返回值
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
属性
你可以通过使用引号,来将属性值指定为字符串字面量:
const element = <div tabIndex="0"></div>;
也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:
const element = <img src={user.avatarUrl}></img>;
习惯
在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。
因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用
camelCase
(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。例如,JSX 里的
class
变成了className
,而tabindex
则变为tabIndex
。
原理
babel会把 JSX 转译成一个名为 React.createElement()
函数调用。
以下两种示例代码完全等效:
const element = <h1 className="greeting">Hello, world!</h1>;
const element = React.createElement(
"h1",
{ className: "greeting" },
"Hello, world!"
);
React.createElement()
会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:
const element = {
type: "h1",
props: {
className: "greeting",
children: "Hello, world!",
},
};
这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。
render
渲染新元素
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById("root"));
更新元素
React 元素是不可变对象。更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()
。
考虑一个计时器的例子:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById("root"));
}
setInterval(tick, 1000);
这个例子会在 setInterval()
回调函数,每秒都调用 ReactDOM.render()
。也就是说,它每秒都渲染一个新的 React 元素,并将其传入ReactDOM.render()
中。
组件
组件
实际上是函数,props接收任意入参.不过一般参数还是都写好了,便于我们调用
在使用时我们可以像一般html一样使用,不过参数必须要传递
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
像用html一样使用,如下
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(element, document.getElementById("root"));
注意: 组件名称必须以大写字母开头。
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,
<div />
代表 HTML 的 div 标签而
<Welcome />
则代表一个组件,并且需在作用域内使用Welcome
。
使用
如下将接收的参数的类型可以通过接口AddTodoProps来解释,然后后面就是具体用法了
AddTodo.tsx
import { useState } from "react";
interface AddTodoProps {
addTodo: (text: string) => void
}
function AddTodo({ addTodo }: AddTodoProps) {
const [text, setText] = useState('')
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (text.trim().length === 0) {
return
}
addTodo(text)
setText('')
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
/>
<button>Add</button>
</form>
)
}
export default AddTodo;
page.tsx
像使用一般html标签一样使用,注意传递参数
<AddTodo addTodo={addTodo}></AddTodo>
todo所有代码
pages.tsx
"use client"
import AddTodo from "./components/AddTodo"
import TodoList from "./components/TodoList"
import TodoFilter from "./components/TodoFilter"
import { useState } from "react"
import { Todo } from "./types"
export default function Home() {
const [todos, setTodos] = useState<Todo[]>([])//在光标处按ctrl可以查看函数原型
const [filter, setFilter] = useState('all')
const addTodo = (text: string) => {
const newTodo = {
id: Date.now(),
text,
completed: false
}
setTodos([...todos, newTodo])//打开旧的,添加新的
}
const deleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id))
//将不需要删除的筛选出来称为新的
}
const toggleTodo = (id: number) => {
setTodos(todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
}))
}
const getFilteredTodos = () => {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed)
case 'completed':
return todos.filter(todo => todo.completed)
default:
return todos;
}
}
return (
<div>
<h1>Todo List</h1>
<AddTodo addTodo={addTodo}></AddTodo>
<TodoList todos={getFilteredTodos()} deleteTodo={deleteTodo} toggleTodo={toggleTodo} />
<TodoFilter setFilter={setFilter} ></TodoFilter>
</div>
)
}
types.ts
export interface Todo {
id: number;
text: string;
completed: boolean;
}
components
AddTodo.tsx
import { useState } from "react";
interface AddTodoProps {
addTodo: (text: string) => void
}
function AddTodo({ addTodo }: AddTodoProps) {
const [text, setText] = useState('')
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (text.trim().length === 0) {
return
}
addTodo(text)
setText('')
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
/>
<button>Add</button>
</form>
)
}
export default AddTodo;
TodoFilter.tsx
function TodoFilter({ setFilter }: any) {
return (
<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>
)
}
export default TodoFilter;
TodoItem.tsx
interface TodoListProps {
todo: any;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
}
function TodoItem({ todo, toggleTodo, deleteTodo }: TodoListProps) {
return (
<li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
<button onClick={() => toggleTodo(todo.id)}>切换</button>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
)
}
export default TodoItem;
TodoList.tsx
import { Todo } from "../types";
import TodoItem from "./TodoItem";
interface TodoListProps {
todos: Array<Todo>;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
}
function TodoList({ todos, toggleTodo, deleteTodo }: TodoListProps) {
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
))}
</ul>
)
}
export default TodoList;