react19 + ts

76 阅读8分钟

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文件 1776046923042.png

用异步组件后会多一个child的js文件 1776047049779.png

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.项目开发

1776073687463.png

1776073637044.png

1776073760627.png

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>

效果:自动加上后缀 防止重复 1776846710531.png

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;