1.创建项目
// xxxxx 项目名称
pnpm create vite xxxxx --template=react-ts
2.函数式与Hooks已成为现代react开发的绝对标准 (用类开发代码臃肿)
import { useState } from 'react';
import './index.css';
//函数式开发
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1 onClick={() => setCount(count + 1)}>Hello React! {count}</h1>
</div>
);
}
export default App;
3.组件开发核心
1.组件输入的叫属性(props),组件内部的叫状态(State)
prop的使用(父子组件参数传递)
//子组件
import { useState } from "react";
//定义类型
interface HelloWorldProps {
title: string; //传字符串
age?: number; //传数字
render?: (count:number) => React.ReactNode; //传函数 类似vue插槽
onChange?: (count:number) => void; //父元素给子元素传递方法
}
export const HelloWorld = (props: HelloWorldProps) => {
const { title, age, render, onChange } = props;
const [count, setCount] = useState(0);
const handleAdd = ()=>{
setCount(count+1);
onChange?.(count+1);
}
return (
<div>
<h1>{title}===== {count}</h1>
<p>Age: {age}</p>
{/* 把子组件属性通过render函数传递给父组件 */}
{render?.(count)}
<button onClick={handleAdd}>增加</button>
</div>
);
};
//父组件
import './index.css';
import { HelloWorld } from './components/index.tsx';
function App() {
return (
// 传递子组件count参数给render函数
<HelloWorld
title="Hello React!"
age={18}
render={(count)=><div style={{color:'red'}}>你好吗?{count}</div>}
// 传递函数给子组件
onChange={(count)=>console.log(count)}
/>
);
}
export default App;
4.状态管理 Hooks详解
1.useState
//count 状态变量 setCount更新函数 第一次渲染传入初始值 0
//为异步更新
const [count, setCount] = useState(0);
import { useState } from "react";
export const HelloWorld = () => {
const [info, setInfo] = useState({
age:0
});
const handleAdd = ()=>{
//错误写法
// info.age++
// //!因为是异步 这里info.age++ 但下面仍然传的是旧对象,数据不是响应式所以数据不会改变
// setInfo(info);
//正确写法 解构的方式传的是新对象
//对象写法
// setInfo({
// ...info,
// age: info.age + 1
// });
//函数写法 (推荐写法,后续可以联合useCallback使用)
setInfo((prevInfo)=>({
...prevInfo,
age: prevInfo.age + 1
}));
}
return (
<div>
<h1>你好 ===== {info.age}</h1>
<button onClick={handleAdd}>增加</button>
</div>
);
};
条件渲染,列表渲染
import {useState} from "react";
export const List = () => {
const [list, setList] = useState([]);
return (
<div>
{
// 只添加奇数 key不要用index作为索引
list.map(item=>item%2===0?<div key={item}>{item}</div>:null)
}
{/* [...list, list.length]解构成新对象进行添加 */}
<button onClick={()=>setList([...list, list.length])}>添加</button>
</div>
);
};
2.useEffect
import { useState,useEffect } from "react";
export const Hooks = () => {
const [count, setCount] = useState(0);
//表示监听数据count变化时,执行useEffect函数中的代码
//1.参数给 count 只有count变化时才执行
useEffect(()=>{
console.log(count);
document.title = `当前计数: ${count}`;
},[count])
//2.参数给 空数组 [] 只第一次加载渲染
useEffect(()=>{
console.log('组件挂载完成'); //类比于组件的onComponentDidMount
//直接return一个函数组件销毁时调用
return ()=>{
console.log('组件卸载完成');
}
},[])
//3.不给参数 每次更新后都执行
useEffect(()=>{
console.log('组件更新完成后');//类比于组件的onComponentWillUnmount
})
return (
<div>
<h1>当前计数: {count}</h1>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
};
3.useRef主要有两个作用
1.通过ref用于获取Dom元素, 2.存储值
import { useState,useEffect,useRef } from "react";
export const Hooks = () => {
const [count, setCount] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);//1.通过ref用于获取Dom元素
const isMounted = useRef<boolean>(false);//2.存储值只能赋值boolean类型,改变时不会改变视图
useEffect(()=>{
//1.获取Dom元素
console.log(inputRef.current);
inputRef.current.focus();//操作Dom元素,获取焦点
console.log(isMounted.current);//第一次为false,后续为true
},[count])
useEffect(()=>{
isMounted.current = true;//组件挂载完成后,将isMounted的值设置为true,后续更新时不会执行
},[])
return (
<div>
<h1>当前计数: {count}</h1>
<button onClick={() => setCount(count + 1)}>增加</button>
<input ref={inputRef} type="text" />
</div>
);
};
5.react19 新特性
1.form表单原来的提交方式
import React,{useState} from 'react'
export const Form = () => {
//要声明状态进性管理
const [formData, setFormData] = useState({
username: '',
password: ''
})
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()//阻止默认行为
console.log(formData)
}
return (
<form onSubmit={handleSubmit}>
{/* 帮我写出 用户名 密码,提交按钮 用label包裹 */}
<label>
用户名:
<input type="text" name="username" onChange={e => setFormData({...formData, username: e.target.value})} />
</label>
<label>
密码:
<input type="password" name="password" onChange={e => setFormData({...formData, password: e.target.value})} />
</label>
<button type="submit">提交</button>
</form>
);
};
2.通过新特性Action的方式
export const FormAction = () => {
const handleAction = (formData: FormData) => {
//获取表单的key和值
console.log([...formData.keys()]);
console.log([...formData.values()]);
};
return (
<form action={handleAction}>
{/* 帮我写出 用户名 密码,提交按钮 用label包裹 */}
<label>
用户名:
<input type="text" name="username" />
</label>
<label>
密码:
<input type="password" name="password" />
</label>
<button type="submit">提交</button>
</form>
);
};
3.useActionState使用
import { useActionState } from "react";
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export const FormAction = () => {
const handleAction = async (previousState: any,formData: FormData) => {
//获取表单的key和值
console.log([...formData.keys()]);
console.log([...formData.values()]);
//模拟异步操作
await delay(1000);
return {
success: true,
message: "提交成功",
};
};
const [state,submitAction,isPending] = useActionState(handleAction,null);
console.log(state,isPending);//获取提交状态和是否正在提交中
return (
<form action={submitAction}>
{/* 帮我写出 用户名 密码,提交按钮 用label包裹 */}
<label>
用户名:
<input type="text" name="username" />
</label>
<label>
密码:
<input type="password" name="password" />
</label>
<button type="submit">{isPending ? "提交中..." : "提交"}</button>
</form>
);
};
4.useFormStatus的使用
提交按钮存在子组件中时使用
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
//声明子组件
const SubmitButton = () => {
//在子组件中时使用useFormStatus来获取状态处理
const { pending, data, method } = useFormStatus();
console.log(pending, data, method);
return <button type="submit">{pending ? "提交中..." : "提交"}</button>;
};
export const FormAction = () => {
const handleAction = async (previousState: any, formData: FormData) => {
//获取表单的key和值
console.log([...formData.keys()]);
console.log([...formData.values()]);
//模拟异步操作
await delay(1000);
return {
success: true,
message: "提交成功",
};
};
const [state, submitAction, isPending] = useActionState(handleAction, null);
console.log(state, isPending); //获取提交状态和是否正在提交中
return (
<form action={submitAction}>
{/* 帮我写出 用户名 密码,提交按钮 用label包裹 */}
<label>
用户名:
<input type="text" name="username" />
</label>
<label>
密码:
<input type="password" name="password" />
</label>
<SubmitButton />
</form>
);
};
6.并发与use Hook
1.suspense 异步加载组件 会先有一个loading的效果再渲染组件
//子组件
const Child = function Child() {
return (
<div>
<h1>Child</h1>
</div>
);
}
export default Child
//父组件
import { lazy, Suspense } from "react";
//通过异步的方式加载组件
const Child = lazy(() => import("./Child"));
export default function SuspenseDemo() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Child />
</Suspense>
</div>
);
}
打包后文件对比
不用异步加载组件时只有一个js文件
用异步组件后会多一个child的js文件
2.使用use和suspense模式(Render-ad-you-fetch) 实现不需要在useEffect中获取数据,也无需手动管理loading
import { Suspense, use } from "react";
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
//!不用use 需要在useEffect管理数据和处理loading
// const fetchMessage = async () => {
// await delay(1000);
// //等待1秒后返回消息
// return "Hello, React!";
// }
// const Message = () => {
// //状态
// const [loading, setLoading] = useState(true);
// const [message, setMessage] = useState("");
// useEffect(() => {
// setLoading(true);//初始加载loading设置为true
// //过两秒返回消息后赋值给message
// fetchMessage()
// .then(msg => setMessage(msg))
// .finally(() => setLoading(false));//整个数据处理完后的结果 再把loading设置为false
// }, []);
// return (
// <div>
// <h1>{loading ? "Loading..." : message}</h1>
// </div>
// )
// }
//!使用use 实现数据处理和laoding
const fetchMessage = () => {
//模拟接口请求
return new Promise((resolve) => {
//使用Promise等待1秒后返回消息
delay(1000).then(() => resolve("Hello, React!"));
})
}
const Message = ({messagePromise}:{messagePromise:Promise<string>})=>{
const message = use(messagePromise);
console.log(message,"========");
return (
<div>
<h1>{message}</h1>
</div>
)
}
export const SuspenseNew = () => {
const messagePromise = fetchMessage();
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Message messagePromise={messagePromise as Promise<string>} />
</Suspense>
</div>
)
}
7.高级Hooks
1.useState 和 UseReducer的用法
使用useState时的写法
import { useState } from "react";
export const UseReducer = () => {
const [info, setInfo] = useState({
name: "张三",
age: 18,
});
return (
<div>
<p>Name: {info.name}</p>
<p>Age: {info.age}</p>
<input
type="text"
value={info.name}
onChange={(e) => setInfo({ ...info, name: e.target.value })}
/>
<input
type="number"
value={info.age}
onChange={(e) => setInfo({ ...info, age: Number(e.target.value) })}
/>
</div>
);
};
使用UseReducer的写法
//redux的设计思想,useReducer是同一个作者设计
//对于状态操作,我们需要提取出来,叫做action
//然后action 需要有专门的方法来完成状态值的更新,reducer
//结果state,驱动试图更新'
import { useReducer } from "react";
const initialState = {
name: "张三",
age: 18,
}
//如果有新的状态和属性只需要在reducerNew和initialState里面做文章 耦合度底
const reducerNew = (
state: typeof initialState,
action: {type: string, payload: string | number}
) => {
switch (action.type) {
case "changeName":
return {...state, name: action.payload};
case "changeAge":
return {...state, age: Number(action.payload)};
default:
return state;
}
}
export const UseReducer = () => {
//默认写dispatch
const [info, dispatch] = useReducer(reducerNew, initialState);
return (
<div>
<p>Name: {info.name}</p>
<p>Age: {info.age}</p>
<input
type="text"
value={info.name}
onChange={(e) => dispatch({type: "changeName", payload: e.target.value })}
/>
<input
type="number"
value={info.age}
onChange={(e) => dispatch({type: "changeAge", payload: Number(e.target.value) })}
/>
</div>
);
};
2.UseContext(深层状态传递)的用法 实现父级组件传值给孙子辈或更远的组件
//创建文件 ThemeContext.ts
import { createContext } from "react";
export const ThemeContext = createContext({
theme: "light",
toggleTheme: () => {},
})
//父组件
//创建文件parent.tsx
import {Child} from './Child'
import {ThemeContext} from './ThemeContext'
import { useState } from "react";
export const Parent = () => {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
}
return (
<div>
<ThemeContext.Provider value={{theme,toggleTheme}}>
<Child />
<button onClick={toggleTheme}>切换主题</button>
</ThemeContext.Provider>
</div>
)
}
//创建child.tsx子组件
import {GrandChild} from './GrandChild'
export const Child = () => {
return (
<div>
<GrandChild />
</div>
)
}
在孙子辈组件GrandChild.tsx中有三种用法 第三种用的最多最优雅
//创建孙子辈组件GrandChild.tsx
//第一种直接引入使用
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export const GrandChild = () => {
//!第一种写法
const {theme} = useContext(ThemeContext);
return ( <div> GrandChild {theme} </div>)
}
//第二种用法 使用ThemeContext.Consumer
import { ThemeContext } from "./ThemeContext";
export const GrandChild = () => {
return (
<ThemeContext.Consumer>
{({theme}) => ( <div> GrandChild {theme} </div>)}
</ThemeContext.Consumer>
)
}
//第三中方式通过自定义hooks 用的最多最优雅
//1.先新建useTheme.ts文件 导出theme
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export const useTheme = () => {
const {theme} = useContext(ThemeContext);
return theme;
}
//然后在GrandChild.tsx引入使用
import { useTheme } from "./useTheme";
export const GrandChild = () => {
const theme = useTheme();
return ( <div> GrandChild {theme} </div>)
}
3.memo的用法
import {GrandChild} from './GrandChild'
import {memo} from 'react'
//memo 用法
export const Child = memo(() => {
return (
<div>
<GrandChild />
</div>
)
},()=>true) //因为值一直为true所以该组件不会渲染 使用memo则该组件会监听后面的值是否改变而重新渲染此组件
4.useMemo用法用于缓存值
import {useMemo,useState} from 'react'
export const Memo = ()=>{
const [count,setCount] = useState(0);
//1.count改变才会更新数据刷新组件
//2.此组件的其他状态改变 而 count没有改变 则doubleInfo.info此数据也不会更新
const doubleInfo:{info:number} = useMemo(()=>{return {info:count*2}},[count]);
return (
<div>
<h1>{doubleInfo.info}</h1>
</div>
)
}
5.useCallback用于缓存函数
import {Memo} from './Memo'
import {useCallback,useState} from 'react'
export const Callback = ()=>{
const [count,setCount] = useState(0);
const handleClick = useCallback(()=>{
setCount(count+1)
},[count]); //!只要count不变 传入子组件的函数就永恒不变
return (
<div>
<Memo onClick={handleClick} />
</div>
)
}
6.自定义HOOKs
//建一个useLocalstorage.ts文件
import {useState,useEffect} from 'react'
//自定义hook 使用tyoescript 泛型 <T>
export const useLocalstorage = <T>(key:string,defaultValue:T): [T, React.Dispatch<React.SetStateAction<T>>] =>{
const [state,setState] = useState<T>(()=>{
const storeValue = localStorage.getItem(key)
return storeValue ? JSON.parse(storeValue) : defaultValue
})
useEffect(()=>{
localStorage.setItem(key,JSON.stringify(state))
},[key,state])
return [state,setState]
}
//在tsx文件中引入使用
import {useLocalstorage} from './useLocalstorage'
export const CustomHooks = ()=>{
const [count,setCount] = useLocalstorage('count',0)
return (
<div>
<h1>自定义hooks</h1>
<p>当前计数:{count}</p>
<button onClick={()=>setCount(count+1)}>增加</button>
<button onClick={()=>setCount(count-1)}>减少</button>
<button onClick={()=>setCount(0)}>重置</button>
</div>
)
}
7.zod的使用
//安装 "zod": "4.1.5" pnpm i zod@4.1.5
///以下为zod的3种用法
import { z } from "zod";
//1.通过zod定义好 Response 的 schema
const ResponseSchema = z.object({
id: z.number(),
name: z.string(),
success: z.boolean(),
});
//2.校验数据是否符合格式 对运行时数据的检查
try {
const data = ResponseSchema.parse({
id: "1", //如果这里不符合上面定的number格式 则直接在浏览器打印区报错
name: "张三",
success: true,
});
console.log(data);
} catch (error) {
console.log(error);
}
//3.通过 zod 转为TS类型
type User = z.infer<typeof ResponseSchema>;
export const ZodDemo = () => {
//定义为上面格式的数据
const user: User = {
id: 1,
name: "张三",
success: true,
};
return (
<div>
<h1>{user.name}</h1>
</div>
);
};
8.项目开发
9.第三方hooks的使用
ahooks 国内第三方hooks 有很多第三方可用的hooks,不用自定义
//依赖ahooks
pnpm i ahooks --save
10 css moudle
每个css文件都当坐单独的模块,命名规则 xxx.moudle.css 为每个className增假后缀名,不让他们重复
//QuestionCard.module.css
.list-item {
margin-bottom: 16px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
//QuestionCard.tsx使用
import styles from './QuestionCard.module.css';
<div className={styles['list-item']}></div>
效果:自动加上后缀 防止重复
11.将空格替换成<br`>在页面上换行
//使用dangerouslySetInnerHTML
import { type FC, useState, type ChangeEvent } from 'react';
const FormElemsDemo: FC = () => {
const [text, setText] = useState('');
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setText(e.target.value);
};
const getHtml = () => {
return {
__html: text.replaceAll('\n', '<br>'),
};
};
return (
<>
<h1>Form Elems Demo</h1>
<div>
<textarea value={text} onChange={handleChange}></textarea>
<p dangerouslySetInnerHTML={getHtml()}></p>
</div>
</>
);
};
export default FormElemsDemo;