react-typescript学习笔记

react-typescript官网学习资源:react.dev/learn/types...

这个学习笔记我使用bootstrap处理了样式:

text 复制代码
pnpm install bootstrap

1.创建vite项目

用vite创建vite项目非常简单

2.vite.env.d.ts

创建项目之后我们会看到一个文件叫vite.env.d.ts

ts 复制代码
/// <reference types="vite/client" />

vite.env.d.ts 是一个 TypeScript 声明文件(declaration file),它的作用是为你的 Vite 项目提供类型定义,增强 TypeScript 的类型检查和代码提示功能。

  • /// <reference types="..." /> 是 TypeScript 的三斜杠指令,用于引用外部类型定义。

  • "vite/client" 是 Vite 提供的类型定义模块,它包含了 Vite 在客户端运行时的一些类型支持,比如对静态资源(如 .css、.png 等文件)的模块导入支持。

如果把这个文件删了,我们引入静态资源的时候报错:

3.条件渲染

最原始的写法:

最原始的条件渲染就是使用if-else

ts 复制代码
export default function App() {

    const cities = ['北京', '上海', '南京', '重庆']

    // 最原始的条件渲染
    if (cities.length === 0) {
        return null
    } else {
        return (
            <ul className='list-group'>
                {cities.map((item, index) => (
                    <li key={index} className='list-group-item'>{item}</li>
                ))}
            </ul>
        )
    }
}

三元表达式:

ts 复制代码
export default function App() {

    const cities = ['北京', '上海', '南京', '重庆']

    return (
        <>
            { cities.length === 0 ? <p>No city found!</p> : (
                <ul className='list-group'>
                    { cities.map((item, index) => (
                        <li key={ index } className='list-group-item'>{ item }</li>
                    )) }
                </ul>
            )
            }
        </>
    )
}

可以直接使用&&运算:

ts 复制代码
export default function App() {

    let cities = ['北京', '上海', '南京', '重庆']
    cities = []

    return (
        <>
            {cities.length ===0 && <p>No city found!</p>}
        </>
    )
}

4.Event

ts 复制代码
import { MouseEvent } from "react"

export default function App() {

    let cities = ['北京', '上海', '南京', '重庆']

    const handleClick = (event:MouseEvent<HTMLLIElement>) => {
        console.log(event.target)
    }

    return (
        <>
            <ul className='list-group'>
                { cities.map((item, index) => (
                    <li
                        key={ index }
                        className='list-group-item'
                        onClick={ handleClick }
                    >
                        { item }
                    </li>
                )) }
            </ul>
        </>
    )
}

定义事件处理函数的时候,事件对象event需要指定类型。

首先从react中导入MouseEvent:

ts 复制代码
import { MouseEvent } from "react"

然后指定类型:

ts 复制代码
const handleClick = (event:MouseEvent<HTMLLIElement>) => {
    console.log(event.target)
}

这里的event:MouseEvent<HTMLLIElement>可以不写泛型<HTMLLIElement>,但是推荐精确指定泛型。

5.useState状态管理

ts 复制代码
import { useState } from "react"

export default function App() {

    let cities = ['北京', '上海', '南京', '重庆']

    // 推断类型为number,不用显示的指定:useState<number>(0)
    const [selectedIndex, setSelectedIndex] = useState(0)

    return (
        <>
            <ul className='list-group'>
                { cities.map((item, index) => (
                    <li
                        key={ index }
                        className={`list-group-item ${selectedIndex === index ? 'active' : ''}`}
                        onClick={ () => {
                            setSelectedIndex(index)
                            console.log(selectedIndex)
                        } }
                    >
                        { item }
                    </li>
                )) }
            </ul>
        </>
    )
}

注意,使用useState定义状态变量的时候也可以定义其类型,也可以自动推断,比如:

ts 复制代码
// 自动推断为number
const [selectedIndex, setSelectedIndex] = useState(0)
ts 复制代码
// 显示的指定类型
const [selectedIndex, setSelectedIndex] = useState<number>(0)

使用useState注意事项

  1. state是异步更新的
  2. state是不可变的(不能修改state原来的值,如果想修改,就需要重新赋值)

普通变量:

ts 复制代码
import { useState } from "react";

export default function App() {

    const [count, setCount] = useState(0)

    const handleClick = () => {
        // 不能count++,而需要重新赋值
        setCount(count + 1)
    }

    return (
        <div>
            <button onClick={handleClick}>{count}+</button>
        </div>
    )
}

复杂类型怎么修改:

  • 修改对象类型
ts 复制代码
import { useState } from "react";

export default function App() {

    const [userInfo, setUserInfo] = useState({id: 12, name: 'akbar', age: 15})

    const handleChange = () => {
        setUserInfo({
            ...userInfo,
            age: 23   // 这里的age:23会覆盖掉原来的age:15
        })
    }

    return (
        <div>
            <h3>state 是不可变数据</h3>
            <div>{JSON.stringify(userInfo)}</div>
            <button onClick={handleChange}>修改userInfo</button>
        </div>
    )
}

或者重新一个一个赋值:

ts 复制代码
import { useState } from "react";

export default function App() {

    const [userInfo, setUserInfo] = useState({id: 12, name: 'akbar', age: 15})

    const handleChange = () => {
        setUserInfo({
            id: 23,
            name: 'saipulla',
            age: 35
        })
    }

    return (
        <div>
            <h3>state 是不可变数据</h3>
            <div>{JSON.stringify(userInfo)}</div>
            <button onClick={handleChange}>修改userInfo</button>
        </div>
    )
}
  • 修改数组类型:

我们可以使用数组的concat操作(两个或多个数组合并成一个新的数组)

ts 复制代码
import { useState } from "react";


export default function App() {

    const [list, setList] = useState(['I', 'He', 'She', 'It'])

    const addItem = () => {
        setList(list.concat('Them'))
    }

    return (
        <div>
            {JSON.stringify(list)}
            <button onClick={addItem}>添加数据</button>
        </div>
    )
}

或者用解构的方式赋值:

ts 复制代码
import { useState } from "react";

export default function App() {

    const [list, setList] = useState(['I', 'He', 'She', 'It'])

    const addItem = () => {
        setList([...list, 'Them'])
    }

    return (
        <div>
            {JSON.stringify(list)}
            <button onClick={addItem}>添加数据</button>
        </div>
    )
}

或者重新赋值:

ts 复制代码
import { useState } from "react";

export default function App() {

    const [list, setList] = useState(['I', 'He', 'She', 'It'])

    const addItem = () => {
        setList(['hello', 'world'])
    }

    return (
        <div>
            {JSON.stringify(list)}
            <button onClick={addItem}>添加数据</button>
        </div>
    )
}

6.Props数据传递(父组件向子组件传递数据)

子组件ListGroup.tsx:

ts 复制代码
import { useState } from 'react'

// 当要接受的Props很多的时候,最好定义一个interface
interface Props {
    items: string[]
    heading: string
}

export default function ListGroup({ items, heading }: Props) {

    const [selectIndex, setSelectIndex] = useState(0)

    return (
        <>
            <h1>{heading}</h1>
            <ul className='list-group'>
                {items.map((item, index) => (
                    <li
                        key={index}
                        className={`list-group-item ${selectIndex === index ? 'active' : ''}`}
                        onClick={() => {setSelectIndex(index)}}
                    >
                        {item}
                    </li>
                ))}
            </ul>
        </>
    )
}

注意:父组件传递props是只读的,不能更改。

父组件App.tsx:

ts 复制代码
import ListGroup from './components/ListGroup.tsx';

export default function App() {

    const cities = ['北京', '上海', '南京', '重庆']

    return (
        <>
            <ListGroup items={cities} heading={'城市列表'} />
        </>
    )
}

7.子组件向父组件传递数据(回调函数)

在 React 中,子组件向父组件传递数据通常通过 回调函数 (callback function)实现。父组件将一个函数作为 prop 传递给子组件,子组件在需要时调用这个函数并传递数据。这种模式符合 React 的单向数据流原则。

比如我想在父组件App中,打印从子组件被点击的城市名:

子组件ListGroup.tsx:

ts 复制代码
import { useState } from 'react'

interface Props {
    items: string[]
    heading: string
    // 定义父组件传递的事件的props
    onSelect: (item: string) => void
}

export default function ListGroup({ items, heading, onSelect }: Props) {

    const [selectIndex, setSelectIndex] = useState(0)

    return (
        <>
            <h1>{heading}</h1>
            <ul className='list-group'>
                {items.map((item, index) => (
                    <li
                        key={index}
                        className={`list-group-item ${selectIndex === index ? 'active' : ''}`}
                        onClick={() => {
                            setSelectIndex(index)
                            onSelect(item) //调用父亲传递的事件onSelect
                        }}
                    >
                        {item}
                    </li>
                ))}
            </ul>
        </>
    )
}

父组件App.tsx:

ts 复制代码
import ListGroup from './components/ListGroup.tsx';

export default function App() {

    const cities = ['北京', '上海', '南京', '重庆']

    const handleOnSelect = (item: string) => {
        console.log(item)
    }

    return (
        <>
            <ListGroup items={ cities } heading={ '城市列表' } onSelect={ handleOnSelect }/>
        </>
    )
}

8.父亲传递children

子组件Alert.tsx:

tsx 复制代码
import { ReactNode } from "react";

interface Pops {
    children: ReactNode     // 因为父亲要传递html标签,所以children的类型要设置成ReactNode
}

export default function Alert({ children }: Pops) {
    return (
        <div className='alert alert-primary'>{children}</div>
    )
}

父组件App.tsx:

tsx 复制代码
import Alert from "./components/Alert.tsx";

export default function App() {
    return (
        <Alert>
            <h2>Alert</h2>
            <p>这是alert的内容</p>
        </Alert>
    )
}

9.内联样式

react中内联样式要用对象的形式写:

ts 复制代码
export default function App() {
    return (
        <button style={{backgroundColor: 'red', padding: '10px'}}>
            Button
        </button>
    )
}

10.循环

比如循环渲染下面的问卷调查表

ts 复制代码
export default function App() {

    const questionList = [
        { id: 'q1', title: 'question one', state: 'published' },
        { id: 'q2', title: 'question two', state: 'draft' },
        { id: 'q3', title: 'question three', state: 'published' },
        { id: 'q4', title: 'question four', state: 'published' },
    ]

    const onEdit = (id: string) => {
        console.log('edit', id)
    }

    return (
        <div>
            <h1>问卷调查列表</h1>
            <div>
                { questionList.map((question) => {
                    const { id, title, state } = question
                    return (
                        <div key={ id }>
                            <strong>{ title }</strong>&nbsp;
                            { state === 'draft' ? <span style={ { color: 'green' } }>已发布</span> : <span>未发布</span> }&nbsp;
                            <button onClick={ () => {
                                onEdit(id)
                            } }>编辑
                            </button>
                        </div>
                    )
                }) }
            </div>
        </div>
    )
}

11.使用FC函数类型

FC 是 React 提供的一个 TypeScript 类型,全称是 FunctionComponent。它明确告诉 TypeScript 这个函数是一个 React 函数组件。

但是,现代react开发中官方建议逐渐弃用 FC:

  • React 官方文档和 TypeScript 社区近年来倾向于不使用 FC
  • TypeScript 的类型推断已经足够强大,通常不需要 FC 来显式声明。

推荐写法(个人观点)

我看react官方文档写的组件都是这种格式。

12.useEffect(副作用)

我们为什么要使用useState?

我们看个例子:

ts 复制代码
import { useState } from "react";

export default function App() {

    console.log("加载ajax网络请求。。。")

    const [value, setValue] = useState(0);

    return (
        <div>
            <button onClick={()=> {setValue(value+1)}}>修改state</button>
        </div>
    )
}

我们每次点击按钮的时候,console.log里面的内容都打印出来,这是因为react中state的更新引发整个组件的重新渲染。

如果我们想要加载ajax网络请求。。。在某个特定时机执行,就需要使用useEffect

使用useEffect:

useEffect接受两个参数:

  • 第一个参数:一个回调函数,定义副作用逻辑。
  • 第二个参数(可选) :依赖数组(dependency array),控制副作用的执行时机。如果省略,则每次渲染后都会执行。

以来数组:

  • 忽略:每次组件渲染的时候都执行副作用
ts 复制代码
useEffect(() => {
    console.log("加载ajax网络请求。。。")
})
  • 空数组:不依赖任何参数,在组件初次渲染和组件卸载的时候执行
ts 复制代码
useEffect(() => {
    console.log("加载ajax网络请求。。。")
}, [])
  • 依赖数组不为空,当这个依赖参数变化时执行副作用函数
ts 复制代码
import { useEffect, useState } from "react";

export default function App() {

    const [value, setValue] = useState(0);

    useEffect(() => {
        console.log("value 的值变化了。。。")
    }, [value]);

    return (
        <div>
            <button onClick={()=> {setValue(value+1)}}>修改state</button>
        </div>
    )
}
  • 在组件卸载时进行:
ts 复制代码
- import { useState, useEffect } from "react";

function ChildComponent() {
    useEffect(() => {
        console.log("组件挂载了");

        // 返回清理函数,在组件销毁时执行
        return () => {
            console.log("组件销毁了,执行清理副作用");
        };
    }, []); // 空依赖数组,仅在挂载和卸载时运行

    return <div>我是子组件</div>;
}

export default function App() {
    const [isVisible, setIsVisible] = useState(true);

    return (
        <div>
            <button onClick={() => setIsVisible(!isVisible)}>
                {isVisible ? "隐藏子组件" : "显示子组件"}
            </button>
            {isVisible && <ChildComponent />}
        </div>
    );
}

13.useRef

虽然react,vue等前端框架不推荐直接手动操作DOM,但是有时候需要手动操作DOM,这是我们可以使用useRef:

ts 复制代码
import { useRef } from "react";

export default function App() {
    
    // 这里最好写上泛型HTMLInputElement
    const inputRef = useRef<HTMLInputElement>(null);

    const selectInput =()=> {
        const inputElement = inputRef.current
        // 这里的inputElement已经是Dom节点了
        if(inputElement) {
            inputElement.select()
            console.log('input value:', inputElement.value)
        }
    }

    return (
        <div>
            <input ref={inputRef} type="text" defaultValue="hello world" />
            <button onClick={selectInput}>选中input</button>
        </div>
    )
}

上面代码中拿到的inputElement已经是DOM节点了,现在我们想怎么操作就怎么操作。

但是注意:用useRef修改组件中变量什么,页面组件不会触发渲染(不进行rerender,就是视图不会更新):

ts 复制代码
import { useRef } from "react";

export default function App() {

    const nameRef = useRef('akbar')

    const changeName = () => {
        nameRef.current = 'saipulla'
        //nameRef.current已经是saipulla,但是视图还是akbar
        console.log(nameRef.current)
    }
    return (
        <div>
            <p>name: {nameRef.current}</p>
            <button onClick={changeName}>选中input</button>
        </div>
    )
}

14.useMemo

前面又说道,每次state变化的时候,react重新渲染整个组件(函数),这个组件里面的所有函数也会一起重新执行,如果我们在做某些耗费性能的操作,比如排序,这时组件中无论哪个state更新了,组件中的这些耗费性能的函数也一起执行,这显然是不好。这个时候我们可以使用useMemo缓存这些耗费性能的函数的结果。

比如我们先看一个没有使用useMemo的示例:

ts 复制代码
import { useState } from "react"

export default function App() {
    const [count, setCount] = useState(0)
    const [todos, setTodos] = useState<string[]>([])

    const calculation = expensiveCalculation(count)

    const increment = () => {
        setCount(count+1)
    }

    const addTodo = () => {
        setTodos([...todos, "New Todo"])
    }

    return (
        <div>
            <div>
                <h2>My Todos</h2>
                { todos.map((todo, index) => {
                    return <p key={ index }>{ todo }</p>;
                }) }
                <button onClick={ addTodo }>Add Todo</button>
            </div>
            <hr />
            <div>
                Count: { count }
                <button onClick={ increment }>+</button>
                <h2>Expensive Calculation</h2>
                { calculation }
            </div>
        </div>
    )
}

const expensiveCalculation = (num: number) => {
    console.log("Calculating...")
    for (let i = 0; i < 1000000000; i++) {
        num += 1
    }
    return num
}

当我们点击<button onClick={ addTodo }>Add Todo</button>的时候,程序等一会儿才能加一个新的todo,虽然我们没改变count的值,但这时expensiveCalculation又执行了一边。

下面看一个使用了useMemo的示例:

ts 复制代码
import { useState, useMemo } from "react"

export default function App() {
    const [count, setCount] = useState(0)
    const [todos, setTodos] = useState<string[]>([])

    const calculation = useMemo(() => expensiveCalculation(count), [count])

    const increment = () => {
        setCount(count+1);
    }
    const addTodo = () => {
        setTodos([...todos, "New Todo"]);
    }

    return (
        <div>
            <div>
                <h2>My Todos</h2>
                { todos.map((todo, index) => {
                    return <p key={ index }>{ todo }</p>;
                }) }
                <button onClick={ addTodo }>Add Todo</button>
            </div>
            <hr />
            <div>
                Count: { count }
                <button onClick={ increment }>+</button>
                <h2>Expensive Calculation</h2>
                { calculation }
            </div>
        </div>
    )
}

const expensiveCalculation = (num: number) => {
    console.log("Calculating...");
    for (let i = 0; i < 1000000000; i++) {
        num += 1;
    }
    return num;
}

在这个示例代码中就没有这种问题。这就是useMemo的作用。

15.useCallback

useCallbackuseMemo很相似,但是它缓存的是一个函数。

16.react Hook使用注意事项

  1. 只能在组件内部使用
  2. 不能在if,for等语句中使用
相关推荐
渣渣盟1 小时前
JavaScript核心概念全解析
开发语言·javascript·es6
Carlos_sam2 小时前
OpenLayers:ol-wind之渲染风场图全解析
前端·javascript
拾光拾趣录2 小时前
闭包:从“变量怎么还没死”到写出真正健壮的模块
前端·javascript
拾光拾趣录2 小时前
for..in 和 Object.keys 的区别:从“遍历对象属性的坑”说起
前端·javascript
遂心_3 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
风清云淡_A4 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界4 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架
DownToEarth4 小时前
H5实现获取当前定位
javascript
前端Hardy4 小时前
HTML&CSS:惊艳!科技感爆棚的登录页面代码解析
前端·javascript·html
我是哈哈hh4 小时前
【AJAX项目】黑马头条——数据管理平台
前端·javascript·ajax·前端框架·axios·proxy模式