打开终端,运行命令。使用脚手架创建项目,项目名 my-react-app。
npx create-react-app my-react-app --template typescript
进入项目目录,启动项目,在浏览器查看效果
cd my-react-app
npm start
项目核心结构
my-react-app/
├── node_modules/ # 项目依赖的npm包
├── public/ # 静态资源文件夹
├── src/ # 源代码文件夹
│ ├── App.tsx # 组件
│ └── index.tsx # 项目入口文件
├── package.json # 项目依赖和配置文件
浏览器插件:https://legacy.reactjs.org/blog/2015/09/02/new-react-developer-tools.html#installation
可以安装通义灵码,或者 Fitten Code 等AI插件,辅助编程
index.tsx:项目的入口文件。以下代码解释由 Fitten Code 插件生成
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// ReactDOM.createRoot()方法创建一个新的 ReactDOM 根节点,并将其渲染到指定的 DOM 元素上。
// 该方法返回一个 ReactDOM.Root 实例,该实例可以用来渲染 React 组件。
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
// ReactDOM.render()方法渲染一个 React 组件到指定的 DOM 元素上。
// <React.StrictMode>组件可以帮助你在开发过程中发现潜在的错误,并提供有用的警告信息。(严格模式)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(); // 性能监控
打开 App.js,删除原有的代码,写一个 Hello World,运行。( index.tsx 中已经使用了该组件)
function App() {
return (
<h1>Hello World!</h1>
);
}
export default App;
注意点 1
关于return 后面的小括号,虽然renturn 返回单行代码可以不加括号,但建议无论返回单行还是多行,都加上括号
return (
)
注意点 2
JSX 只能返回一个根元素。所以需要返回多个根元素的时候,可以在外面加一层空标签 <></>,空标签起到占位作用,不会生成最终的html标签
function App() {
return (
<>
<div>
{/*代码*/}
</div>
<div>
{/*代码*/}
</div>
</>
);
}
基本概念
什么是组件?
Commponent,用于构建 HTML 页面,有 state (状态) 和 props (属性)。state 是组件自身管理的数据,props 是父组件传递给子组件的数据,通常不可改变。
React 组件有两种创建方式
-
函数式组件(这里就是函数式组件,官方主推)
-
类组件(复杂冗余)
JSX语法(JavaScript XML)
jsx 语法就是 js 和 html 代码混合着写。这种写法更加直观和易于理解,但它实际上是JavaScript表达式,最终会被转换成相应的JavaScript对象。
什么是虚拟DOM?
虚拟DOM是React为了提高性能而采用的一种技术。它是实际DOM的轻量级副本,React首先在虚拟DOM上进行变更,然后通过diff算法计算出最小的变更操作,最后将这些变更应用到实际的DOM上。这种方法避免了直接操作DOM的高成本,从而提高了应用的性能。
ReactDOM是React与真实DOM交互的库。虚拟DOM负责提高性能,而ReactDOM负责将React元素映射到真实DOM上。
插值
可以在标签的属性或者内容上使用插值。{值}
function App() {
const divContent = "标签内容";
const divTitle = "标签标题";
return (
<div title={divTitle}>{divContent}</div>
);
}
export default App;
事件操作
需求:点击按钮,控制台打印"点击了按钮"
function App() {
//点击按钮,触发 handleClick 函数,e是事件对象,包含事件信息
function handleClick (e){
console.log("点击了按钮",e);
}
return (
<button onClick={handleClick}>按钮</button>
);
}
export default App;
- 事件以小驼峰命名(例如 onClick 而不是 onclick)
列表渲染
需求:使用map函数遍历数组并创建元素列表,展示数据
import React, {Fragment} from 'react';
function App() {
// 假设这是从服务器获取的用户数据
const users = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' },
];
// 映射出用户列表
const userList = users.map((user, index) => (
<Fragment key={index}>
<li>{user.name} - {user.email}</li>
<li>-------------</li>
</Fragment>
));
return (
<div>
<h1>User List</h1>
<ul>
{userList}
</ul>
</div>
);
}
export default App;
- 在循环中,需要给每个元素都设置唯一的 key 属性,当列表数据更新时,React会使用 key 来确定哪些元素发生了变化,从而高效地更新DOM,而不是重新渲染整个列表。
- 在循环中,如果需要保持多个根元素的时候,在外层使用标签。这里不使用空标签是因为它无法添加key,在循环中不好用
条件渲染
可以使用 js 表达式,根据条件渲染不同的元素。
import React, {Fragment} from 'react';
function App() {
// 假设这是从服务器获取的用户数据
const users = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' },
];
// 映射出用户列表
const userList = users.map((user, index) => (
<Fragment key={index}>
<li>{user.name} - {user.email}</li>
<li>-------------</li>
</Fragment>
));
// 如果没有用户,渲染一条消息
if (userList.length === 0){
return (
<p>No users available.</p>
);
}
// 否则,渲染用户列表
return (
<div>
<h1>User List</h1>
<ul>
{userList}
</ul>
</div>
);
}
export default App;
Axios
官方文档:https://www.axios-http.cn/docs/intro
-
安装 Axios
npm install axios
-
启动一个准备好的后端,用来返回数据
-
使用 Axios 向后端发送请求。可以复制 Axios 官方文档的基本用例:https://www.axios-http.cn/docs/example
import axios from "axios";
import {BaseResponse} from "./model/BaseResponse";
import {UserVO} from "./model/UserVO";
export default function App({ id }: { id: bigint }) {
// 声明一个变量 user,用于存储请求成功后返回的UserVO对象
let user: UserVO | null = null;
// 定义函数,作用是发送请求。支持async/await用法
async function getUserVOById() {
try {
//BaseResponse就是response.data的类型
const response = await axios.get<BaseResponse<UserVO>>(`http://localhost:8101/api/user/get/vo?id=${id}`);
console.log(response.data.data);
user = response.data.data;
} catch (error) {
console.error(error);
}
}
// 调用函数
getUserVOById();
return (
<h1>{user.userName}</h1>
);
}
export interface BaseResponse<T>{
code: string,
data: T,
message?: string
}
export interface UserVO {
id: number,
userName: string,
userAvatar: string,
userProfile: string,
userRole: string,
createTime: string
}
在 index.tsx 中使用组件,传入id。注意:可以删除 index.tsx 中的 React.StrictMode 标签。因为严格模式会发两次请求,控制台会打印两次
root.render(
//处理大整数值的精度丢失问题,使用 js 的内置 BigInt 对象,数字末尾加n
<App id={1772186353223131138n}/>
);
可以在控制台查看响应体结构。文档:https://www.axios-http.cn/docs/res_schema
控制台成功打印,但页面上没有内容
分析执行流程
首次调用函数组件,函数从上到下依次执行,最后返回的 jsx 代码会被渲染成【虚拟 dom 节点】,根据【虚拟 dom 节点】会生成【真实 dom 节点】,由浏览器显示出来。
当函数组件的 props 或 state 发生变化时,才会重新调用函数组件,返回的 jsx与上次的【虚拟 dom 节点】对比,如果没变化,复用上次的节点;有变化,创建新的【虚拟 dom 节点】替换掉上次的节点(这是 react 自身的性能优化,react 是靠不断调用函数来更新页面的)
尝试用浏览器插件控制 props 发生改变,函数确实被重新调用了,但调用又让函数重置到了初始状态。响应的数据永远无法保存(函数无状态)
useState
useState 是React中的一个钩子(Hook),它允许你在函数组件中添加状态。就是把数据保存在了函数之外,随着函数的调用不断更新
-
参数:数据初始状态
-
返回值:[当前状态,更新函数]
调用这个更新函数,它能修改数据,并能触发组件函数重新调用,让React重新渲染组件
import axios from "axios";
import {BaseResponse} from "./model/BaseResponse";
import {UserVO} from "./model/UserVO";
import {useState} from "react";
export default function App({ id }: { id: bigint }) {
// 定义useState,用于存储请求到的用户信息
const [user, setUser] = useState({userName: ""});
// 声明一个变量 fetch,用于判断是否已经发送请求
const [fetch, setFetch] = useState(false);
// 定义函数,作用是发送请求。支持async/await用法
async function getUserVOById() {
try {
//BaseResponse就是response.data的类型
const response = await axios.get<BaseResponse<UserVO>>(`http://localhost:8101/api/user/get/vo?id=${id}`);
console.log(response.data.data);
setUser(response.data.data);
} catch (error) {
console.error(error);
}
}
// 判断是否已经发送请求,如果没有发送请求,则发送请求
if (!fetch) {
setFetch(true);
getUserVOById();
}
return (
<h1>{user.userName}</h1>
);
}
-
因为更新函数 setUser() 的执行会触发组件函数重新调用,导致 getUserVOById() 再次执行,这样会循环不断发请求。所以定义 fecth 来控制只发一次请求。下面使用 useEffect 会解决这个问题
-
对象结构不能在视图中直接呈现,会报错。所以这里不是{user},是{user.userName}
-
更新函数是直接使用新值完全替换掉旧值,而不是在旧值上做局部修改。
useEffect
Effect 称之为副作用(没有贬义),函数组件的主要目的,是为了渲染生成 html 元素,除了这个主要功能以外的功能,都可以称之为副作用。 useXXX 打头的一系列方法,都是为副作用而生的,在 react 中把它们称为 Hooks
-
参数1:箭头函数, 在真正渲染 html 之前会执行它
-
参数2:
- 情况1:没有,代表每次调用组件函数时,都会执行箭头函数
- 情况2:[],代表无论组件函数被调用几次,箭头函数只会执行一次
- 情况3:[依赖性],依赖项变化时,箭头函数会执行
import axios from "axios";
import {BaseResponse} from "./model/BaseResponse";
import {UserVO} from "./model/UserVO";
import {useEffect, useState} from "react";
export default function App({ id }: { id: bigint }) {
// 定义useState,用于存储请求到的用户信息
const [user, setUser] = useState({userName: ""});
// useEffect,用于发起请求
useEffect(() => {
async function getUserVOById() {
try {
//BaseResponse就是response.data的类型
const response = await axios.get<BaseResponse<UserVO>>(`http://localhost:8101/api/user/get/vo?id=${id}`);
console.log(response.data.data);
setUser(response.data.data);
} catch (error) {
console.error(error);
}
}
getUserVOById();
},[id]); // 只在 id 发生变化时发起请求
return (
<h1>{user.userName}</h1>
);
}