Bootstrap

react速成指南

React18速成–适合有vue基础的同学

目录

前言

该博客为本人的学习记录,并不是一篇教程,请结合b站视频食用:

【30分钟学会React18核心语法 可能是你学会React最好的机会 前端开发必会框架 无废话精品视频】https://www.bilibili.com/video/BV1pF411m7wV?vd_source=0d0d6b12377aa593bc3a34f0884de98a

有vue基础的同学建议看完视频入门后去看官方文档学习
若是没有vue基础的同学可以去看长教学视频学习(本视频可看可不看)
ps:代码看不懂的话可以下载vscode插件通义千问等AI插件,一键注释

脚手架创建react项目

npx create-react-app 项目名字

jsx重点知识

1.在jsx语法中html直接内嵌在函数中并通过return返回
2.jsx只能有一个根元素(整合成一个盒子)
// @ts-nocheck
import logo from './logo.svg';
import './App.css';

function App() {
  return (
  <>
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
    <div></div>
   </>
  );
}

export default App;
3.插值

条件渲染

function App(){
  const divTitle = '标签标题'
  const flag = true;
  let divContent = ''
  if(flag){
    divContent = <span>flag为true</span>//不需要为字符串形式
  }else{
    divContent = <p>flage为false</p>
  }
  return(
    <div title={divTitle}>{divContent}</div>
  )
}

export default App;

列表渲染

function App(){
  const list = [
    {id:1,name:'xiaowu'},
    {id:2,name:'xiaoli'},
    {id:3,name:'xiaohua'}
  ] 
  const listContent = list.map(item => (
    <li key={item.id}>{item.name}</li>
  )) 
  return(
    <ul>{listContent}</ul>
  )
}

export default App;

4.事件操作

基础操作

import React, { Fragment, useState } from "react";
function App(){
  //       读        改      (名字可以自己定义)
  const [content, setContent] = useState('默认内容')
  function handleClick(){
    setContent('新内容')
  }
  return(
    <>
      <div>{content}</div>
      <button onClick={handleClick}>按钮</button>
    </>
  )
}

export default App;

对象操作

import React, { Fragment, useState } from "react";
function App(){
  const [data, setData] = useState({
    title:'默认标题',
    content:'默认内容'
  })
  function handleClick(){
    setData({
      ...data,
      title:'新标题'
    })
  }
  return(
    <>
      <div title={data.title}>{data.content}</div>
      <button onClick={handleClick}>按钮</button>
    </>
  )
}

export default App;

数组操作

import React, { Fragment, useState } from "react";
function App(){
  const [data, setData] = useState([
    {id:1,name:'xiaowu'},
    {id:2,name:'xiaoli'},
    {id:3,name:'xiaohua'}
  ])
  const listData = data.map(item=>(
    <li key={item.id}>{item.name}</li>
  ))
  let td=3;
  function handleClick(){
    setData([
      ...data,
      {id:++td,name:'xiaohuamao'}
    ])
  }
  return(
    <>
      <ul>{listData}</ul>
      <button onClick={handleClick}>按钮</button>
    </>
  )
}

export default App;


React18组件通信与插槽

1.为dom组件设置Props

在react中给dom设置class时防止与js中class重复:采用className

// @ts-nocheck
import image from './logo.svg'
function App(){
  return(
   <div>
    <img
      src={image}
      alt=""
      className=""
    />
   </div>
  )
}
export default App;

以变量形式赋值style

// @ts-nocheck
import image from './logo.svg'
function App(){
  const imgStyleObj={
    with:100,
    height:100,
    backgroundColor:'grep'
  }

  return(
   <div>
    <img
      src={image}
      alt=""
      className=""
      style={imgStyleObj}
    />
   </div>
  )
}
export default App;
 

通过jsx展开语法(与es6中展开运算符不同,不要混淆)

// @ts-nocheck
import image from './logo.svg'
function App(){
  const imgStyleObj={
    className:'small',
    with:100,
    height:100,
    backgroundColor:'grep'
  }

  return(
   <div>
    <img
      src={image}
      alt=""
      {...imgStyleObj}
    />
   </div>
  )
}
export default App;
 
2.react组件的Props

react简单函数式组件

function Article({title,content,active}){
  return(
    <div>
      <h2>{title}</h2>
      <p>{content}</p>
      <p>状态:{active ? '显示中' : '已隐藏'}</p>
    </div>
  )
}
export default function App() {
  return(
    <>
      <Article
        title="1"
        content="1"
        active
      />
    </>
  )
}

嵌套式传值

function Detail ({content,active}){
  return(
    <>
      <p>{content}</p>
      <p>状态:{active ? '显示中' : '已隐藏'}</p>
    </>
  )
}
function Article({title,detailData}){
  return(
    <div>
      <h2>{title}</h2>
      <Detail {...detailData}/>
    </div>
  )
}
export default function App() {
  const ArticleData = {
    title:'标题1',
    detailData:{
      content:'内容1',
      active:true
    }
  }
  return(
    <>
      <Article
        {...ArticleData}
      />
    </>
  )
}

react中插槽传值

function List ({children}) {
  return (
    <ul>
      {children}
    </ul>
  )
}
export default function App() {
  return(
    <>
      <list>
        <li></li>
        <li></li>
        <li></li>
      </list>
    </>
  )
}

react父子传值

// @ts-nocheck
import {useState} from "react"

function Detail({onActive}){
  const [status,setStatus] = useState(false)
  function handleClick(){
    setStatus(!status)
    onActive(status)
  }
  return (
    <div>
      <button onClick={handleClick}>按钮</button>
      <p style={{
        display:status ? 'block' : 'none'
      }}>Detail的内容</p>
    </div>
  )
}
export default function App() {
  function handleActive(status){
    console.log(status)
  }
  return(
    <>
      <Detail
        onActive={handleActive}
      />
    </>
  )
}

React Hooks

1.reducer
// @ts-nocheck
import {useReducer, useState} from "react"

function countReducer(state,action){
  switch(action.type){
    case "increment":
      return state + 1
    case "decrement:":
      return state - 1
    default:
      throw new Error()
  }
}
export default function App() {
  const [state,dispatch] = useReducer(countReducer,0)
  const handleIncrement = () => dispatch({type:"increment"})
  const handleDecrement = () => dispatch({type:"decrement"})
  return(
    <div style={{padding:10}}>
      <button onClick={handleIncrement}>-</button>
      <span>{count}</span>
      <button onClick={handleDecrement}>+</button>
    </div>
  )
}
2.useRef

记录修改前的状态

记录上一次的值

// @ts-nocheck
import {useReducer, useState} from "react"

export default function App() {
  const [count,setCount] = useState(0)
  const prevCount = useRef()

  function handleClick(){
    prevCount.current = count
    setCount(count + 1)
  }
  return(
    <div>
      <p>最新的count:{count}</p>
      <p>上次的count:{prevCount.current}</p>
      <button onClick={handleClick}>增大count</button>
    </div>
  )
}

通过ref实现子组件功能访问

// @ts-nocheck
import { forwardRef, useImperativeHandle, useReducer, useState } from "react"

/**
 * 定义一个名为Child的组件,使用forwardRef创建一个引用。
 * 这个组件通过引用来提供一个自定义的focus方法。
 * 
 * @param {Object} props - 组件的props,不包含特定的属性。
 * @param {Object} ref - 一个用于获取组件实例的引用。
 * @returns 返回一个简单的div元素,这个div通过ref暴露了focus方法。
 */
const Child = forwardRef((props, ref) => {
  // 使用useImperativeHandle钩子来定义ref的行为。
  useImperativeHandle(ref, () => ({
    focus: () => {
      console.log('focus')
    }
  }))
  return (
    <div>子组件</div>
  )
})

/**
 * App组件是应用程序的主入口点。
 * 它使用了一个ref来直接访问Child组件的实例,并且提供了一个按钮来触发Child组件的focus方法。
 * 
 * @returns 返回一个包含Child组件和一个按钮的div元素。
 */
export default function App() {
  // 创建一个ref来保存Child组件的实例。
  const childRef = useRef()
  
  /**
   * 点击按钮时调用的函数,用于触发Child组件的focus方法。
   */
  function handleClick() {
    childRef.current.focus()
  }
  
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleClick}>聚焦</button>
    </div>
  )
}
3.useEffect
// @ts-nocheck
import { useState,useEffect } from "react"

export default function App() {
  const [count, setCount] = useState(0)
  const handleIncrement = () => {
    setCount(count + 1)
  }
  const handleDecrement = () => {
    setCount(count - 1)
  }
  useEffect(() => {
    console.log('useEffect')
  },[count]);
  return (
    <div style={{padding:10}}>
      <button onClick={handleIncrement}>-</button>
      <span>{count}</span>
      <button onClick={handleDecrement}>+</button>
    </div>
  )
}
4.useMemo

如何使用useMemo

  1. 确定计算结果: 首先,识别出你的组件中哪些计算是昂贵的,并且在每次渲染时都不需要重新计算,除非某些特定的状态或属性发生了改变。
  2. 创建记忆化的值: 使用useMemo来包装这个计算,将计算逻辑放在useMemo的第一个参数的函数中,这个函数被称为工厂函数。
  3. 指定依赖项: 在useMemo的第二个参数中,提供一个依赖项数组。这个数组应该包含所有影响计算结果的变量。如果依赖项数组中的任何值发生变化,useMemo将重新执行工厂函数。
  4. 使用记忆化的值: 在组件的其他部分使用useMemo返回的记忆化值,而不是直接调用计算函数。

假设你有一个组件,它需要根据用户的输入动态生成一个复杂的图表数据集。你可以使用useMemo来避免在每次渲染时都重新生成这个数据集。

import React, { useState, useMemo } from 'react';

function ChartDataGenerator() {
  const [inputValue, setInputValue] = useState(0);

  const complexChartData = useMemo(() => {
    // 这个函数模拟一个复杂的计算过程
    const data = Array.from({ length: inputValue }, (_, i) => Math.random());
    return data;
  }, [inputValue]);

  return (
    <div>
      <input type="number" value={inputValue} onChange={e => setInputValue(Number(e.target.value))} />
      <pre>{JSON.stringify(complexChartData, null, 2)}</pre>
    </div>
  );
}

在这个例子中,complexChartData只有在inputValue改变时才会重新计算。这可以显著提高组件的性能,特别是在complexChartData的生成是一个昂贵的操作时。

注意事项

  • useMemo主要用于避免不必要的计算,但它不应该被过度使用。在大多数情况下,React的虚拟DOM算法已经足够高效,不需要额外的优化。
  • 如果依赖项数组为空,useMemo将只在组件挂载时计算一次。
  • 如果依赖项数组中的某个值是引用类型(如对象或数组),即使其内部状态没有改变,只要引用本身改变了,useMemo也会重新计算。
  • 不要在useMemo的依赖项数组中包含函数或类实例,因为它们在每次渲染时都会创建新的引用,这会导致useMemo每次都重新执行。
5.useCallBack

与useMemo很像

区别:useCallBack作用于变量,useMemo作用于函数

一个例子说明区别:

import React, { useState, useCallback, useMemo } from 'react';

function CounterApp() {
  const [counter, setCounter] = useState(0);

  // 使用 useCallback 缓存 函数 以避免在每次渲染时创建新的函数引用
  const incrementCounter = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);

  // 使用 useMemo 缓存计算结果( 变量 ),避免在每次渲染时重复计算
  const fibonacci = useMemo(() => {
    if (counter <= 1) return counter;
    return fibonacci(counter - 1) + fibonacci(counter - 2);
  }, [counter]);

  return (
    <div>
      <h1>Counter: {counter}</h1>
      <button onClick={incrementCounter}>Increment</button>
      <p>Fibonacci of {counter}: {fibonacci}</p>
    </div>
  );
}

export default CounterApp;
  1. useCallback 示例:

    在上面的例子中,incrementCounter 函数使用 useCallback 进行缓存。这意味着只要 counter 的值没有改变,incrementCounter 函数的引用就不会改变。这对于性能优化很重要,特别是在将函数作为 prop 传递给子组件时,可以避免不必要的子组件重新渲染。

  2. useMemo 示例:

    fibonacci 计算使用了 useMemo。由于斐波那契数列的计算可能是昂贵的操作,我们不想在每次组件渲染时都重新计算它,除非 counter 的值确实改变了。useMemo 确保了只有当依赖项 counter 发生变化时,计算才被执行,否则它将返回上一次计算的结果。

总结

  • useCallback 用于优化函数引用,避免在每次渲染时创建新的函数实例,这有助于减少不必要的子组件重新渲染。
  • useMemo 用于优化计算结果的缓存,避免在每次渲染时重复执行相同的计算,这有助于减少不必要的计算开销。

在上述示例中,useCallbackuseMemo 分别用于优化事件处理器和计算密集型操作,共同提高了应用的性能。

TodoList综合案例react18+ts

page.tsx

import AddTodo from '../components/AddTodo'
import TodoList from '../components/TodoList'
import TodoFilter from '@/components/TodoFilter'
import { useState } from 'react';
import { Todo } from '@/type';
export default function Home() {
  const [todos, setTodos] = useState<Todo[]>([]);
  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(todos => todos.id !== id))
  }
  //切换功能
  const toggleTodo = (id:number) => {
    setTodos(todos.map(todo => {
      if(todo.id === id){
        return {
          ...todo,
          completed:!todo.completed
        }
      }
      return todo
    }))
  }
  //过滤功能
  const getFilteredTodos = () =>{
    switch(filter){
      case 'all':
        return todos
      case 'completed':
        return todos.filter(todo => todo.completed)
      case 'active':
        return todos.filter(todo => !todo.completed)
      default:
        return todos
    }
  }  
  return (
    <div>
      <h1>TodoList</h1>
      <AddTodo addTodo={addTodo}></AddTodo>
      <TodoList todos={getFilteredTodos()} deleteTodo={deleteTodo} toggleTodo={toggleTodo}></TodoList>
      <TodoFilter setFilter={setFilter}></TodoFilter>
    </div>
    
  )
}

type.ts
export interface Todo{
    id:number;
    text:string;
    completed:boolean;
}
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() === ''){
            return
        }
        addTodo(text)
        setText('')
    }
    return (
        <form>
            <input 
            type="text"
            value={text} 
            onChange={e => setText(e.target.value)} 
            />
            <button>新建事项</button>
        </form>
    )
   }
export default AddTodo
TodoItem.tsx
import { Todo } from "@/type"
interface TodoItemProps {
    todo:Todo;
    toggleTodo: (id:number) => void;
    deleteTodo: (id:number) => void;
}
function TodoItem({todo,toggleTodo,deleteTodo}:TodoItemProps){
    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
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
TodoList.tsx
import { Todo } from "../type";
import TodoItem from "./TodoItem";
interface TodoListProps {
  todos: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}></TodoItem>
            ))
        }
    </ul>
  );
}
export default TodoList
;