超级详细的React核心语法

React 核心语法

目前最流行的现代前端框架 无疑是vue.js和react.js,国内的大部分项目都是vue.js,并且vue3 和react很想象,学习果vue3的人看会觉得相当熟悉,并且在开发跨平台ios或者安卓端的时候 reactnative 也是个不错的选择(原生安卓 ui匮乏,flutter 需要额外掌握一门新语言,uts目前并不完善,开发体验还不成熟)

视频教程参考

某马程序员(视频很完善但是不推荐 学会vue3 看个文档很快就能上手) https://www.bilibili.com/video/BV1ZB4y1Z7o8/?spm_id_from=333.337.search-card.all.click

30分钟速成核心语法 https://www.bilibili.com/video/BV1pF411m7wV/?spm_id_from=333.337.search-card.all.click&vd_source=c4237dd4bb302a93bf1fce783ce1f8cb

官网 https://zh-hans.react.dev/learn (建议快速核心语法过一遍 直接看官网 自己上手 看某马和某谷会浪费很多时间)

生态圈

  • 路由中文文档 (对于目前主流的单页面应用路由肯定是构建大型web需要的) https://react-guide.github.io/react-router-cn/
    个人笔记:react router
  • 全局状态管理库 (和vue 的pinia类似) https://cn.redux.js.org/ 3分钟学会redux
  • reactNative (我学react 就是为了这个!!!) 跨平台 性能做过优化的 比一般的跨平台好点 稍稍弱于flutter https://reactnative.cn/
  • Zustand 一个轻量级的 React 状态管理库,可以用来管理应用中的全局状态。它的目标是提供一种简单、快速且灵活的状态管理解决方案,尤其是在一些不需要 Redux 这种重量级解决方案的场景中。

一些好看的ui

Material-UI:当下流行的 React UI 框架 (mui.com)

Arco Design - 企业级产品的完整设计和开发解决方案

Semi Design

TDesign (tencent.com)

开始

npx create-next-app@latest

使用Vite创建项目

npx create-vite

idea编译器有 可以下载对应插件

在这里插入图片描述

核心语法TSX(JSX)

TSX(JSX)是ts和js 和html的混用

{} 对应vue的{{}} 插值语法

可以渲染任意东西,下面给出结构代码实列直观发现组件结构

组件

就是一个大写方法明的方法 然后返回TSX

import {useEffect, useState} from "react";
function HookEffect() {
    const style={
        border: "1px solid #ccc",
        borderRadius: "5px",
        boxShadow: "0 0 10px rgba(0,0,0,0.3)",
        padding: "20px",
        transform: "translate(-50%, -50%)",
        color: "red",
        margin: "20px 20px",
        fontSize: "30px"
    }
    const [message, set] = useState("演示effect")
    const [count, setCount] = useState(0)
    const [isShow, setIsShow] = useState(false)


    useEffect(() => {
        set("特定监听的数据让useEffect渲染完成")
    },[count])

    return (
        <>
        <div style={style}>HookEffect
    <hr/>
        <span>渲染内容</span>
        <p>{message}</p>
        <p>计数器:{count}</p>

        <button onClick={() => setCount(count+1)}>计数器+1</button>
            {isShow && <button onClick={() => setIsShow(false)}>卸载时钟</button>}
            {!isShow && <button onClick={() => setIsShow(true)}>挂载时钟</button>}


    </div>
    <div>
        {isShow && <Clock/>}
    </div>
    </>
    )
}

const Clock = () => {
    const [time, setTime] = useState(new Date().toLocaleTimeString());

    useEffect(() => {
        const timer = setInterval(() => {
            setTime(new Date().toLocaleTimeString());
            console.log(new Date().toLocaleTimeString())
        }, 1000);


        // 清理函数,组件卸载时清除计时器
        return () => {
            clearInterval(timer);
        };
    }, []); // 依赖项为空,意味着只在组件挂载时执行

    return (
        <div>
            <h2>当前时间: {time}</h2>
        </div>
    );
};

export default HookEffect;

jsx 语法和java函数式编程一样 返回一行省略() 多行则需要

函数组件

一个大学函数名的函数为一个组件,函数的返回值为要渲染的内容

export default function Home() {
  return (
    <div>hello world</div>
  )
}

如果返回的内容只有一行,跟java的lambda 可以省略

export default function Home() {
  return <div>hello world</div>
}

如果返回的内容有多行,括号不能省略

export default function Home() {
  return (
    <div>
     <div>hello world</div>
     <div>hello world</div>
    <div>hello world</div>
     </div>
  )
}

TXS只能返回一个根元素

注意和vue中的template一样只有一个根元素

如果出现多个根元素 需要使用 标签包裹 简写<>

export default function Home() {
  return (
      <>
       <div>hello world</div>
     <div>hello world</div>
    <div>hello world</div>
	</>
  )
}

插值语法

和vue差不多

标签内容

export default function App() {
  const msg="世界和平"
    return (
    <>
    {/* 使用在标签内部 */}
      <div>{msg}</div>
    </>
  )
  }

标签属性

除去数据,样式,tsx ,方法都可以这样渲染

import React from 'react';

const DemoComponent = () => {
  // 定义一些示例数据
  const name = 'Alice';
  const age = 25;
  const isStudent = false;
  const items = ['Apple', 'Banana', 'Cherry'];
  const user = {
    name: 'Bob',
    age: 30,
    occupation: 'Engineer',
  };

  // 函数返回字符串
  const greet = () => 'Hello, World!';

  return (
    <div>
      <h1>React JSX Rendering Demo</h1>

      {/* 渲染字符串 */}
      <p>Name: {name}</p>

      {/* 渲染数值 */}
      <p>Age: {age}</p>

      {/* 渲染布尔值(显示不同的文本) */}
      <p>Status: {isStudent ? 'Student' : 'Not a Student'}</p>

      {/* 渲染数组 */}
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>

      {/* 渲染对象的属性 */}
      <p>User Info:</p>
      <ul>
        <li>Name: {user.name}</li>
        <li>Age: {user.age}</li>
        <li>Occupation: {user.occupation}</li>
      </ul>

      {/* 渲染函数的返回值 有返回值的函数都可以渲染 包括返回tsx 和各个元素 */}
      <p>{greet()}</p>
    </div>
  );
};

export default DemoComponent;

条件渲染

采用 &&与运算 实现 v-if

采用三元运算 x? case1:case2 实现 if else

案列也演示了样式的渲染方式

  • {}插值行内代码块
  • 设为变量 再插值
  • 单独写一个css文件 为元素写classname 属性作为类名
const bluecolor={display:"flex",justifyContent:"space-between",alignItems:'center' ,backgroundColor:"blue"}

/**
 * 函数组件大写开头
 * @constructor
 */
function Chose() {

    const [isBlue, setColor] = useState(false)

    return(
        <div >
        <h1>你喜欢蓝色吗</h1>

            <div style={{display:"flex",justifyContent:"space-between",alignItems:'center'}}>
                <button onClick={()=>setColor(true)}>是</button>
                <button onClick={()=>setColor(false)}>否</button>
            </div>
       {/*类似v-if的用法,但是可以用jsx的语法来实现 如果是v-show则是通过改变style的display来实现*/}
                {isBlue && ( //为true时才渲染
                    <div style={bluecolor}>
                        这是一个蓝色的内容
                    </div>
                )}
            {/*等效三元运算符*/}
            {isBlue ? (
                <div style={{display:"flex",justifyContent:"space-between",alignItems:'center' ,backgroundColor:"skyblue"}}>
                    这是一个蓝色的内容
                </div>
            ) : (<div style={{
                display: "flex",
                justifyContent: "space-between",
                alignItems: 'center',
                backgroundColor: "red"
            }}>
                这是一个红色的内容
            </div>)}


        </div>
    )

}

列表渲染

先写的todolist再回过头来看基本语法,map在react中用的应该是非常多

列表中的key用于保证元素的唯一性,类似vue的v-for中的key

type Student = {
    name: string,
    age: number
}

const  students:Student[]=[{name:"John",age:25},{name:"Mary",age:23},{name:"Bob",age:24}]

function Index() {
    const ref = useRef(null);
    const [array, setStudents] = useState(students)
    const [newName, setName] = useState("")
    const [newAge, setAge] = useState(0)
  return (
      <div className={"container"}>
          <h1>遍历多个元素</h1>
          {/*只要有返回值无论是样式渲染的对象还是函数都可以直接渲染*/}
          {array.map((item, index) =>
              <div key={index}>{item.name} - {item.age} </div>
          )}
          <h2>添加元素</h2>
          <input type="text" placeholder="请输入姓名" ref={ref} value={newName} onChange={(e) => setName(e.target.value)}/>
          <input type="number" placeholder="请输入年龄" value={newAge}
                 onChange={(e) => setAge(Number(e.target.value))}/>
          <button onClick={() => {
              const newStudent = {name: newName, age: newAge};
              // array.push(newStudent); // 直接修改数组
             ref.current.focus(); // 聚焦到输入框
              setStudents([...array, newStudent]); // 使用扩展运算符创建新数组
              setName(""); // 清空输入框
              setAge(0); // 清空输入框
          }}>添加元素
          </button>

      </div>)
}

补充js的数组可以使用类似java的lambda 表达式的一些方法,filter ,map,foreach等,由于{}需要返回值 这里用的是map

事件处理

标签的属性通常使用驼峰

点击事件:onClick ,输入改变事件 onChange…等等等

jsx 语法和java函数式编程一样 一行代码省略{} 多行则需要

这里演示需要借助一个的响应式效果 ,react是通过管理状态来实现响应式的

useState API实现 下面是详细介绍

https://zh-hans.react.dev/learn/state-a-components-memory#adding-a-state-variable

const [value,setValue]=useState()

value 变量名 setvalue()可以实现动态改变数值的方法 从而实现数据的响应式 (直接修改这个数值没有意义,必须调用api)

大部分的事件

function EventTestComponent() {
    const [inputValue, setInputValue] = useState('');
    const [message, setMessage] = useState('');

    const handleClick = (event) => {
        setMessage(`点击事件: ${event.type}`);
    };

    const handleDoubleClick = (event) => {
        setMessage(`双击事件: ${event.type}`);
    };

    const handleMouseEnter = (event) => {
        setMessage(`鼠标进入事件: ${event.type}`);
    };

    const handleMouseLeave = (event) => {
        setMessage(`鼠标离开事件: ${event.type}`);
    };

    /**
     * 可以搭配
     * 鼠标悬停事件在 React 中可以通过组合使用 onMouseEnter 和 onMouseLeave 来实现。
     * onMouseEnter 事件在光标进入元素时触发,而 onMouseLeave 事件在光标离开元素时触发。通过这两个事件,您可以实现鼠标悬停效果。
     * @param event
     */
    const handleMouseMove = (event) => {

        setMessage(`鼠标移动事件: ${event.type}`);
    };

    const handleMouseDown = (event) => {
        setMessage(`鼠标按下事件: ${event.type}`);
    };

    const handleMouseUp = (event) => {
        setMessage(`鼠标释放事件: ${event.type}`);
    };

    const handleKeyDown = (event) => {
        setMessage(`按键按下事件: ${event.type}, 键: ${event.key}`);
    };

    const handleKeyPress = (event) => {
        setMessage(`按键按下事件: ${event.type}, 键: ${event.key}`);
    };

    const handleKeyUp = (event) => {
        setMessage(`按键释放事件: ${event.type}, 键: ${event.key}`);
    };

    const handleChange = (event) => {
        setInputValue(event.target.value);
        setMessage(`输入改变事件: ${event.type}, 值: ${event.target.value}`);
    };

    const handleInput = (event) => {
        setMessage(`输入事件: ${event.type}, 值: ${event.target.value}`);
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        setMessage(`表单提交事件: ${event.type}`);
    };

    const handleFocus = (event) => {
        setMessage(`获得焦点事件: ${event.type}`);
    };

    const handleBlur = (event) => {
        setMessage(`失去焦点事件: ${event.type}`);
    };

    const handleTouchStart = (event) => {
        setMessage(`触摸开始事件: ${event.type}`);
    };

    const handleTouchMove = (event) => {
        setMessage(`触摸移动事件: ${event.type}`);
    };

    const handleTouchEnd = (event) => {
        setMessage(`触摸结束事件: ${event.type}`);
    };

    const handleCopy = (event) => {
        setMessage(`复制事件: ${event.type}`);
    };

    const handleCut = (event) => {
        setMessage(`剪切事件: ${event.type}`);
    };

    const handlePaste = (event) => {
        setMessage(`粘贴事件: ${event.type}`);
    };

    return (
        <div>
            <h1>事件测试组件</h1>
            <div
                onClick={handleClick}
                onDoubleClick={handleDoubleClick}
                onMouseEnter={handleMouseEnter}
                onMouseLeave={handleMouseLeave}
                onMouseMove={handleMouseMove}
                onMouseDown={handleMouseDown}
                onMouseUp={handleMouseUp}
                onTouchStart={handleTouchStart}
                onTouchMove={handleTouchMove}
                onTouchEnd={handleTouchEnd}
                style={{ padding: '20px', border: '1px solid black', margin: '10px' }}
            >
                <p>尝试点击、双击、移动鼠标或触摸此区域</p>
            </div>
            <input
                type="text"
                value={inputValue}
                onChange={handleChange}
                onInput={handleInput}
                onKeyDown={handleKeyDown}
                onKeyPress={handleKeyPress}
                onKeyUp={handleKeyUp}
                onFocus={handleFocus}
                onBlur={handleBlur}
                onCopy={handleCopy}
                onCut={handleCut}
                onPaste={handlePaste}
                style={{ margin: '10px' }}
            />
            <form onSubmit={handleSubmit} style={{ margin: '10px' }}>
                <button type="submit">提交表单</button>
            </form>
            <p>事件信息: {message}</p>
        </div>
    );
}

传值说明

 onKeyDown={handleKeyDown} //默认传递事件对象 
 onClick={()=>onclick(textname)//传递自定义参数
onClick={(e)=>onclick(textname,e)//即传递参数 又传递事件对象

props(父传子)

和vue一样,props 实现父组件传递属性给子组件 可以是传递任意值

父组件在引用的子组件上直接传递数据 <子组件 数据名 ={传递数据}> ,子组件函数的参数添加一个对应的参数props对象就行了 还是很简单的和vue 的组合式api 还是很想

这里用的ts演示 如果是js的话就很简单,无需写类型

细节: props和vue的也是一样的,props只可读 不能修改,如果需要响应式 需要useSate 进行初始化


/**
 * 演示props的使用
 * 父组件向子组件传递数据
 * 只能读取无法修改 需要修改则需要useState
 */
function Parent() {
  return (
    <div>
      <h1>父组件</h1>
      <Child name="小明" age={20} />
      <Child2 name="小明" age={20}
              techs={['TypeScript', 'React']}
              space={<span> 父组件传递了 JSX 元素 </span>}
              data={{ name: '小明', age: 20 } }
              click={() => console.log('点击了子组件')}
      />
        <Child3 >
            <div
                style={{backgroundColor: 'yellow', padding: '10px', border: '1px solid #ccc'
            }}
            >
            <p>子组件3</p>
            <p>姓名:小明</p>
            <p>年龄:20</p>
            <p>这是直接传递的 代码块</p>
            </div>
        </Child3>
    </div>
  );
}
function Child(props: { name: string; age: number }) {

    return (
        <div>
            <h2>子组件</h2>
            <p>姓名:{props.name}</p>
            <p>年龄:{props.age}</p>
        </div>
    )
    ;

}

// 子组件3 实现类似插槽
function Child3(porps: {children: JSX.Element}) {

    return (
        <div>
            <h2>子组件3</h2>
            {/*插入的代码块*/}
            {porps.children}
        </div>
    )
}
function Child2(props:
                    { name: string; age:
                            number ; techs: string[];
                            space: JSX.Element;
                            data: { name: string; age: number }
                            click: () => void
}) {
  return (
    <div>
      <h2>子组件2</h2>
      <p>姓名:{props.name}</p>
      <p>年龄:{props.age}</p>
        <Child4 name={props.name} age={props.age}></Child4>
        <p>技术栈:{props.techs.join(', ')}</p>
        <p> JSX 元素:{props.space}</p>
        <p> 对象数据:{JSON.stringify(props.data)}</p>
        <button onClick={props.click}> 点击子组件 </button>
    </div>
  );
}
//这样就实现了 父组件->子组件 ->孙组件
function Child4(props: { name: string; age: number }) {
  return(
      <>
          <h3>孙子组件</h3>
      <p>姓名:{props.name}</p>
    <p>年龄:{props.age}</p>
          </>)
}

export default Parent;

插槽(slot)

react的插槽 也就是这样实现了


function Parent() {
  return (
    <div>
      <h1>父组件</h1>

        <Child3 >
            <div
                style={{backgroundColor: 'yellow', padding: '10px', border: '1px solid #ccc'
            }}
            >
            <p>子组件3</p>
            <p>姓名:小明</p>
            <p>年龄:20</p>
            <p>这是直接传递的 代码块</p>
            </div>
        </Child3>
    </div>
  );
}

// 子组件3 实现类似插槽
function Child3(porps: {children: JSX.Element}) {

    return (
        <div>
            <h2>子组件3</h2>
            {/*插入的代码块*/}
            {porps.children}
        </div>
    )
}

插入多段代码块

和vue 一样可以使用名字来进行区分

import React from 'react';

// 父组件
function Parent() {
  return (
    <div>
      <h1>父组件</h1>
      <Child3
        header={
          <div style={{ backgroundColor: 'lightblue', padding: '10px' }}>
            <h3>这是头部插槽</h3>
          </div>
        }
        footer={
          <div style={{ backgroundColor: 'lightgreen', padding: '10px' }}>
            <p>这是底部插槽</p>
          </div>
        }
      >
        {/* 默认插槽内容 */}
        <div
          style={{
            backgroundColor: 'yellow',
            padding: '10px',
            border: '1px solid #ccc',
          }}
        >
          <p>子组件内容</p>
          <p>姓名:小明</p>
          <p>年龄:20</p>
        </div>
      </Child3>
    </div>
  );
}

// 子组件3 实现具名插槽
function Child3({ header, footer, children }) {
  return (
    <div>
      <h2>子组件3</h2>
      {/* 具名插槽:头部 */}
      <div>{header}</div>
      {/* 默认插槽 没有名 */}
      <div>{children}</div>
      {/* 具名插槽:底部 */}
      <div>{footer}</div>
    </div>
  );
}

export default Parent;

为了方便记忆 对比vue

<template>
  <div>
    <h1>父组件</h1>
    <Child>
      <!-- 默认插槽 -->
      <template v-slot:default>
        <p>默认内容</p>
      </template>
      <!-- 具名插槽 -->
      <template v-slot:header>
        <p>头部插槽</p>
      </template>
      <template v-slot:footer>
        <p>底部插槽</p>
      </template>
    </Child>
  </div>
</template>

<script>
export default {
  components: {
    Child: {
      template: `
        <div>
          <header><slot name="header"></slot></header>
          <main><slot></slot></main>
          <footer><slot name="footer"></slot></footer>
        </div>
      `,
    },
  },
};
</script>

自定义事件(子传父)

和vue 也是通过类似 自定义事件的方式 子组件创建事件,父组件实现,这样就可以子组件参数传递给父组件( 其实有点rpc的思想,rpc 服务定义接口 具体服务交给后端实现)

function  EmitDemo() {
    const [message  , setMessage] = useState("")
    // 首先父组件定义方法和实现
    const handleClick = (message:string) => {
        setMessage(message)
    }

    return (
        <div style={{padding: "20px",
            boxShadow: "0 0 10px rgba(0,0,0,0.1)",
            border: "1px solid #ccc",
            borderRadius: "5px",
        }}>
            <h1>Emit Demo</h1>
            <span>来自子组件的消息父组件显示</span>
            <br/>
            <p>{message}</p>
            
            <Demoson click={handleClick} ></Demoson>
           
        </div>
    )
}
function Demoson(props:{
    click:(msg:string) => void,

})
{
    const [inputValue, setinput] = useState("")
    function changeinput(e) {
        setinput(e.currentTarget.value);
    }
    return (
        <div>
            <h2>子组件</h2>
            <span>
                <input type="text" onChange={changeinput} value={inputValue}></input>
            </span>
            <button onClick={() => props.click(inputValue)}>点击出传递给父组件显示</button>
        </div>
    )
}

此时可以实现一个子组件传递给兄弟组件的逻辑了 ,子1 ->父组件->子2 ,就是先兄弟之间的传递

function  EmitDemo() {
    const [message  , setMessage] = useState("")
    // 首先父组件定义方法和实现
    const handleClick = (message:string) => {
        setMessage(message)
    }

    return (
        <div style={{padding: "20px",
            boxShadow: "0 0 10px rgba(0,0,0,0.1)",
            border: "1px solid #ccc",
            borderRadius: "5px",
        }}>
            <h1>Emit Demo</h1>
            <span>来自子组件的消息父组件显示</span>
            <br/>
            <p>{message}</p>
            <Demoson click={handleClick} ></Demoson>
            {/*传递给兄弟组件*/}
            <Brother message={message}></Brother>
        </div>
    )
}
function Demoson(props:{
    click:(msg:string) => void,

})
{
    const [inputValue, setinput] = useState("")
    function changeinput(e) {
        setinput(e.currentTarget.value);
    }
    return (
        <div>
            <h2>子组件</h2>
            <span>
                <input type="text" onChange={changeinput} value={inputValue}></input>
            </span>
            <button onClick={() => props.click(inputValue)}>点击出传递给父组件显示</button>
        </div>
    )
}

/**
 * 子组件
 * 演示兄弟组件的使用
 * 子组件传值给了父组件 父组件传值个另一个子组件
 * @constructor
 */
function Brother(props:{
    message:string
}) {
    return (<div style={{padding: "30px 20px",boxShadow: "0 0 10px rgba(0,0,0,0.1)",border: "1px solid blue",borderRadius: "5px"}}>
        <h2>兄弟组件</h2>
        <br/>
        <p>{props.message}</p>
    </div>)
}

上下文 (多层级传参)

虽然刚才实现了兄弟组件传值 ,但是那样太麻烦了 数据经历了 俩次传递,所以上下文对象就诞生了,和后端一些web框架(go:gin,java :spring)一样,一个请求的处理是有专门的链式处理的,在一开始设置上下文,全链的处理都可以拿到这个数据

类似vue 的provider 和imject

  1. 创建上下文对象

  2. <上下文对象.Provider value={value}> 标签包裹 需要上下文环境的子对象

  3. 子组件 孙. 已经更下层级的组件都可以使用 const value = useContext(MyContext); // 使用 useContext Hook 来获取上下文值


// 主应用组件用来显示
function Container() {
    return (
        <>
            {/*范围内的统计对象可以使用 A设置上下文 包裹bc*/}
        <ComponentA>
            <ComponentB />
            <ComponentC></ComponentC>
        </ComponentA>

        </>
    );
}


/**
 * Context 是一个全局的共享对象,可以让组件之间共享数据,而不必显式地通过 props 传递。
 * 它可以使得 props 在组件树中的任何位置都可以访问到,并且可以避免层层传递 props。
 * 类似vue的provider 和inject
 * 1.创建一个 Context 对象,并通过 createContext 函数创建。
 * 2.使用 Provider 组件包装组件,并提供 Context 值。
 * 3.使用 useContext Hook 来获取上下文值。
 */

// 1.创建一个 Context
const MyContext = createContext({
    text: "",
    number: 0,
    component: <div>没有赋值成功</div>,
    func: ()=>console.log("没有赋值成功"),
});

// 包装组件,提供 Context 值
function ComponentA(props:{ children: JSX.Element }) {
    const value = {
        text: "这是上下文包装对象!",
        number: 42,
        component: <div>This is a component passed through Context.</div>,
        func: () => alert("上下文传递的函数!")
    };

    return (
        //2.使用 Provider 组件包装组件,并提供 Context 值 并且将需要使用到上下文的对象插入插槽中
        <MyContext.Provider value={value}>
            {props.children}
        </MyContext.Provider>
    );
}

// 消费组件,使用上下文值
function ComponentB() {
    const value = useContext(MyContext); // 使用 useContext Hook 来获取上下文值
    return (
        <div>
            <p>{value.text}</p>
            <p>{value.number}</p>
            {value.component}
            <button onClick={value.func}>Click me</button>
            {/*孙级的对象也可以使用*/}
            <ComponentC></ComponentC>
        </div>
    );
}function ComponentC() {
    const value = useContext(MyContext); // 使用 useContext Hook 来获取上下文值
    return (
        <div>
            <p>{value.text}</p>
            <p>{value.number}</p>
            {value.component}
            <button onClick={value.func}>Click me</button>
        </div>
    );
}


获取dom元素

这个完全和vue一模一样

1.使用useref() api

2.在想获取的组件或者标签使用ref=api返回的对象,就可以获取

3.然后再函数内使用ref.current 获取当前dom 元素



/**
 * 演示获取虚拟对象
 * 和vue3的类似 使用useRef获取虚拟对象
 * dom可用时 ref.current 指向真实dom对象
 * dom不可用时 ref.current 指向undefined
 */
function Demo() {
  const ref = useRef(null)
    const [value, setValue] = useState("")
  return (
    <div>
      <h1>Hello World</h1>
     <input type="text" ref={ref}  value={value}  onKeyDown={()=>console.log("keydown")} onChange={e => setValue(e.target.value)}/>
      <button onClick={() => {
          if (ref.current) {
              // 显示输入框的值
              alert(ref.current.value);
              // 输出后继续剧情聚焦避免用户还要点击输入框才能技术输入
              ref.current.focus();
              alert("输入数据"+value);

          } else {
              alert('DOM 元素未找到');
          }

      }}>获取DOM</button>
    </div>
  );
}
export default Demo;

自定义hook

自定义 Hook 是 React 中非常强大的一个特性,它允许你将组件逻辑抽取到一个独立的函数中,从而在多个组件中复用这部分逻辑。自定义 Hook 和普通的函数非常类似,唯一的区别是它的名称必须以 use 开头,并且可以调用其他 Hook(比如 useStateuseEffect 等)。

创建自定义 Hook 的步骤

  • 确定要复用的逻辑:通常情况下,你会发现某些组件中使用的逻辑可以复用,比如表单处理、API 请求、窗口尺寸变化等。
  • 抽取逻辑到自定义 Hook:将这部分逻辑移到一个函数中,这个函数返回相关的状态和操作方法。
  • 使用自定义 Hook:在需要的组件中调用这个自定义 Hook,并使用它提供的状态和方法。

官网演示的是检测网络连接 那么这里就做一个检查窗口大小的hook

useWindowSize 自定义 Hook

/**
 * 自定义 Hook: 用于获取并跟踪窗口的宽度和高度。
 * 使用了 `useState` 来存储窗口尺寸,`useEffect` 来监听窗口尺寸变化事件。
 */
function useWindowSize() {
    // 1. 初始化宽度和高度的状态
    const [windowSize, setWindowSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight
    });

    useEffect(() => {
        // 2. 定义更新窗口尺寸的函数
        const handleResize = () => {
            setWindowSize({
                width: window.innerWidth,
                height: window.innerHeight
            });
        };

        // 3. 监听窗口尺寸变化事件
        window.addEventListener('resize', handleResize);

        // 4. 清除事件监听器
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, [windowSize]); // 窗口对象 确保对象该改变数据实时更新  

    // 5. 返回窗口尺寸
    return windowSize;
}

export default useWindowSize;

然后组件内进行使用


import useWindowSize from '../../hooks/useWindowSize.ts'; // 假设自定义 Hook 保存在 useWindowSize.js 文件中

/**
 * 这个组件使用了自定义 Hook `useWindowSize` 来显示和跟踪窗口的尺寸。
 */
function WindowSizeComponent() {
    // 1. 使用自定义 Hook 来获取窗口尺寸
    const { width, height } = useWindowSize();

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', textAlign: 'center' }}>
            <h2>当前窗口尺寸</h2>
            <p>宽度: {width}px</p>
            <p>高度: {height}px</p>
        </div>
    );
}

export default WindowSizeComponent;

还可以再写一个 axios的hook

import { useState, useEffect } from 'react';
import axios from 'axios';

/**
 * 自定义 Axios Hook: 用于发起 HTTP 请求并处理数据的加载状态、错误和返回数据。
 * @param {Object} config - Axios 请求配置,包括 method, url, data 等。
 * @param {boolean} immediate - 是否立即发起请求,默认 true。
 */
const useAxios = (config, immediate = true) => {
  const [data, setData] = useState(null);  // 存储请求返回的数据
  const [loading, setLoading] = useState(false);  // 加载状态
  const [error, setError] = useState(null);  // 错误状态
  const [trigger, setTrigger] = useState(immediate);  // 是否立即触发请求

  const fetchData = async () => {
    setLoading(true);
    try {
      const response = await axios(config);  // 通过 Axios 发起请求
      setData(response.data);  // 请求成功,保存返回的数据
      setError(null);  // 清除错误
    } catch (err) {
      setError(err.message || 'Something went wrong');  // 请求失败,保存错误信息
    } finally {
      setLoading(false);  // 结束加载状态
    }
  };

  useEffect(() => {
    if (trigger) {
      fetchData();
    }
  }, [trigger]);  // 当 `trigger` 变化时发起请求

  // 提供一个手动触发请求的方法
  const refetch = () => setTrigger(true);

  // 返回数据、加载状态、错误信息和重新请求的方法
  return { data, loading, error, refetch };
};

export default useAxios;

使用的组件

import React, { useState } from 'react';
import useAxios from './useAxios';

function AxiosComponent() {
  // 1. 配置请求
  const config = {
    method: 'GET',
    url: 'https://jsonplaceholder.typicode.com/posts/1',
  };

  // 2. 使用自定义 Hook
  const { data, loading, error, refetch } = useAxios(config);

  // 3. 处理加载状态、错误、数据展示
  return (
    <div style={{ padding: '20px' }}>
      <h2>Axios Hook 请求演示</h2>
      {loading && <p>加载中...</p>}
      {error && <p>错误: {error}</p>}
      {data && (
        <div>
          <h3>{data.title}</h3>
          <p>{data.body}</p>
        </div>
      )}
      <button onClick={refetch} style={{ marginTop: '20px' }}>
        重新加载数据
      </button>
    </div>
  );
}

export default AxiosComponent;

React Hooks

刚才用的useState ,UseRef ,UseContent 都属于hook 接下来说其他的hooks

建议直接看这个 官方文档 https://zh-hans.react.dev/reference/react/hooks

effect

useEffect 是 React 中的一个 Hook,用于在组件渲染后执行副作用操作。useEffect 有几种使用形式,根据依赖项的不同,它可以控制副作用执行的时机。接下来,我们将详细分析 useEffect 的多种使用方式

1. 无依赖项数组(每次渲染都执行)

如果 useEffect 中没有依赖项数组,意味着每次组件更新时,都会执行 useEffect,即无论组件的任何状态或属性发生任何变化,useEffect 都会在每次重新渲染后运行。

useEffect(() => {
    set("useEffect渲染完成" + new Date().toLocaleTimeString());
});
特点:
  • 这种形式会在每次渲染完成后执行,包括初次挂载和之后的每次更新。
  • 注意:这样会导致频繁调用,可能会影响性能,尤其是在组件频繁更新的情况下。
2. 空依赖项数组(只在组件挂载时执行)

当依赖项数组为空 [] 时,useEffect 只会在组件**第一次渲染(挂载)**时执行一次,并且在组件卸载时清除(如果有清理函数)。

useEffect(() => {
    set("useEffect渲染完成" + new Date().toLocaleTimeString());
}, []);
特点:
  • 只会在组件初次渲染时执行一次(类似于 componentDidMount)。用来做初始渲染
  • 对性能更加友好,因为它不会在每次组件更新时都执行。
3. 依赖项数组(依赖特定变量变化时执行)

如果你提供了一个依赖项数组,useEffect 只会在数组中的依赖项发生变化时执行。你可以将状态或属性值放入依赖项数组中,这样只有这些值发生变化时,useEffect 才会运行。

useEffect(() => {
    set("useEffect渲染完成" + new Date().toLocaleTimeString());
}, [count, name]);
特点:
  • 只会在依赖项数组中的变量变化时执行(类似于 componentDidUpdate vue watch)。
  • 有效控制了副作用的执行时机,避免不必要的调用,提升性能。
4. 带清理函数的 useEffect(处理副作用清理)

useEffect 还可以返回一个清理函数,这通常用于处理订阅、计时器或其他需要在组件卸载时清除的副作用。

useEffect(() => {
    const timer = setInterval(() => {
        set("useEffect渲染完成" + new Date().toLocaleTimeString());
    }, 1000);

    // 返回清理函数
    return () => {
        clearInterval(timer);
    };
}, []);
特点:
  • 当组件卸载或者依赖项变化时,清理函数会执行,用于清除副作用(类似于 componentWillUnmount)。
  • 常用于计时器、事件监听、网络请求等副作用的清理。

总结几种 useEffect 的使用方式:

  1. 不传依赖项:每次组件渲染时都会执行。
  2. 空依赖项数组 []:只在组件初次渲染时执行一次。
  3. 传递依赖项 [dep1, dep2]:仅在依赖项发生变化时执行。
  4. 带清理函数:在组件卸载或依赖项更新时清理副作用。

在 React 中,如果你想通过 useEffect 监听对象或数组中的某个值、对象本身或数组本身的变化,有几种不同的方式可以实现。这取决于你是否希望监听单个属性或元素的变化,还是整个对象或数组的变化。

1. 监听对象中的某个值

你可以在依赖项数组中直接指定对象的某个属性。如果只想监听对象中的某个属性的变化,可以将该属性作为依赖项。

const [person, setPerson] = useState({ name: "Alice", age: 25 });

useEffect(() => {
    console.log("person.name changed:", person.name);
}, [person.name]);  // 只监听对象的 name 属性变化
2. 监听数组中的某个值

类似地,可以直接监听数组中的某个元素,通常会基于状态或其他条件动态指定依赖项。

const [numbers, setNumbers] = useState([1, 2, 3, 4]);

useEffect(() => {
    console.log("numbers[0] changed:", numbers[0]);
}, [numbers[0]]);  // 只监听数组第一个元素的变化
3. 监听整个对象

如果你想监听整个对象的变化,可以将整个对象作为依赖项。不过需要注意的是,如果对象是复杂的引用类型,即使对象的内部属性改变,React 可能不会重新触发 useEffect,除非整个对象本身被替换。因此,更新对象时最好使用 浅拷贝结构赋值,以确保 useEffect 能够监听到变化。

const [person, setPerson] = useState({ name: "Alice", age: 25 });

useEffect(() => {
    console.log("Person object changed");
}, [person]);  // 监听整个 person 对象的变化

// 示例:更新对象属性时,确保重新创建对象引用
const updateAge = () => {
    setPerson(prev => ({ ...prev, age: prev.age + 1 }));
};
注意:

如果仅仅更新对象的某个属性而不替换整个对象(即直接修改引用类型的属性),不会触发 useEffect 重新运行。为了触发更新,对象需要重新创建

4. 监听整个数组

同样的,监听整个数组也可以直接将数组作为依赖项。当数组中的内容发生变化(例如通过 setNumbers([...newNumbers]) 更新数组时),会重新触发 useEffect

const [numbers, setNumbers] = useState([1, 2, 3, 4]);

useEffect(() => {
    console.log("Numbers array changed:", numbers);
}, [numbers]);  // 监听整个数组的变化

// 示例:更新数组时,确保创建新的数组引用
const addNumber = () => {
    setNumbers(prev => [...prev, 5]);
};
5. 监听对象或数组中的深层属性

React 的 useEffect 默认不会对深层属性进行深度比较,只会对引用进行浅比较。如果你想监听对象或数组中的深层嵌套属性变化,简单的依赖项数组可能不会有效。这种情况下,可以考虑使用 JSON.stringify 进行深度比较(仅适合简单的场景),或者使用自定义的比较逻辑。

const [person, setPerson] = useState({ name: "Alice", age: 25, address: { city: "New York" } });

useEffect(() => {
    console.log("Person's city changed:", person.address.city);
}, [JSON.stringify(person.address)]);  // 深层监听 address 对象变化
演示代码
import {useEffect, useState} from "react";
function HookEffect() {
    const style={
        border: "1px solid #ccc",
        borderRadius: "5px",
        boxShadow: "0 0 10px rgba(0,0,0,0.3)",
        padding: "20px",
        transform: "translate(-50%, -50%)",
        color: "red",
        margin: "20px 20px",
        fontSize: "30px"
    }
    const [message, set] = useState("演示effect")
    const [count, setCount] = useState(0)
    const [isShow, setIsShow] = useState(false)

   //如果 useEffect 中没有依赖项数组,意味着每次组件更新时,都会执行 useEffect,即无论组件的状态或属性发生任何变化,useEffect 都会在每次重新渲染后运行
   //  useEffect(() => {
   //      set("useEffect渲染完成"+new Date().toLocaleTimeString())
   //  })
   //  useEffect(() => {
   //      set("任一数据改变 useEffect渲染完成"+new Date().toLocaleTimeString())
   //  },[])
    useEffect(() => {
        set("特定监听的数据让useEffect渲染完成")
    },[count])

    return (
        <>
        <div style={style}>HookEffect
    <hr/>
        <span>渲染内容</span>
        <p>{message}</p>
        <p>计数器:{count}</p>

        <button onClick={() => setCount(count+1)}>计数器+1</button>
            {isShow && <button onClick={() => setIsShow(false)}>卸载时钟</button>}
            {!isShow && <button onClick={() => setIsShow(true)}>挂载时钟</button>}


    </div>
    <div>
        {isShow && <Clock/>}
    </div>
    </>
    )
}


const Clock = () => {
    const [time, setTime] = useState(new Date().toLocaleTimeString());

    useEffect(() => {
        const timer = setInterval(() => {
            setTime(new Date().toLocaleTimeString());
            console.log(new Date().toLocaleTimeString())
        }, 1000);


        // 清理函数,组件卸载时清除计时器
        return () => {
            clearInterval(timer);
        };
    }, []); // 依赖项为空,意味着只在组件挂载时执行

    return (
        <div>
            <h2>当前时间: {time}</h2>
        </div>
    );
};



export default HookEffect;
  • useXXX的hook函数只能在组件中使用
  • 并且不运行在for,if等逻辑判断里面执行,只能在top 顶层使用
  • 监听对象中的某个值:可以直接将该值(如 person.name)作为 useEffect 的依赖项。
  • 监听数组中的某个值:可以直接将该值(如 numbers[0])作为依赖项。
  • 监听整个对象或数组:将整个对象或数组作为依赖项,并且在更新时创建新的对象或数组引用。
  • 深层属性监听:可以通过 JSON.stringify 等技巧强制比较对象或数组的深层属性变化,但这并不是最佳方案,复杂场景下需要定制逻辑。

性能优化hook

当出现复杂计算结果的时候,比如cons agesum= 元素之和 ,由于响应式,每次有状态更新就会触发组件整体刷新,那么这个复杂结果也会更新,这样重复的计算 对性能会进行拖累,为此有一个类似计算属性的api 解决了问题

useMemo(()=>返回计算结果的回调函数,[监听对象列表])

缓存复杂计算的结果,只有第二个参数对象数组的状态发生改变才进行重新渲染


function Index() {
    const ref = useRef(null);
    const [students, setStudents] = useState<Student[]>(initialStudents);
    const [newName, setName] = useState("");
    const [newAge, setAge] = useState(0);
    const [value, setValue] = useState(false);

    // 复杂计算的示例函数
    const computeExpensiveValue = (students: Student[]) => {
        console.log("计算复杂值");
        // 模拟复杂计算
        return students.map(student => student.age * 2).reduce((a, b) => a + b, 0);
    };

    // 使用 useMemo 缓存复杂计算的结果,只有当 newname 或者回调参数变化时才进行调用对调 的到计算属性 
    //一般第二个参数就是回调参数的传参 这里是为了演示
    const totalAge = useMemo(() => computeExpensiveValue(students), [newName]);

    return (
        <div className="container">
            <h1>学生列表</h1>
            {/* 显示缓存计算结果 */}
            <p>学生总年龄(*2后的和):{totalAge}</p>
            <div>
                {students.map((item, index) => (
                    <div key={index}>{item.name} - {item.age}</div>
                ))}
            </div>
            <h2>添加新学生</h2>
            <input
                type="text"
                placeholder="请输入姓名"
                ref={ref}
                value={newName}
                onChange={(e) => setName(e.target.value)}
            />
            <input
                type="number"
                placeholder="请输入年龄"
                value={newAge}
                onChange={(e) => setAge(Number(e.target.value))}
            />
            <button
                onClick={() => {
                    const newStudent = { name: newName, age: newAge };
                    ref.current.focus(); // 聚焦到输入框
                    setStudents([...students, newStudent]); // 使用扩展运算符创建新数组
                    setName(""); // 清空输入框
                    setAge(0); // 清空输入框
                }}
            >
                添加学生
            </button>
            {/* 模拟组件的状态变化引发更新 */}
            <span>状态变量:{value.toString()}</span>
            <button onClick={() => setValue(!value)}>切换状态</button>
           
        </div>
    );
}

所以这里我点击切换状态刷新重新渲染以后复杂计算函数的回调,不会触发回调的日志,说明没有重新渲染,只有监听的属性改变时才会重新输出

子组件频繁渲染

下面这种情况,当一个父组件中调用了一个子组件的时候,父组件任意state更新 state 发生变化,会导致父组件更新页面重新渲染,而子组件虽然没有发生改变,但也会进行更新。简单的理解下,当一个页面内容非常复杂,模块非常多的时候,函数式组件会从头更新到尾,只要一处改变,所有的模块都会进行刷新,这种情况显然是没有必要的。

// 定义学生类型
type Student = {
    name: string;
    age: number;
};

// 初始学生列表
const initialStudents: Student[] = [
    { name: "John", age: 25 },
    { name: "Mary", age: 23 },
    { name: "Bob", age: 24 }
];

function Song() {
    console.log("我是子组件进行渲染");
    return <div>我是子组件</div>;
}

function Index() {
    const ref = useRef(null);
    const [students, setStudents] = useState<Student[]>(initialStudents);
    const [newName, setName] = useState("");
    const [newAge, setAge] = useState(0);
    const [value, setValue] = useState(false);

    // 复杂计算的示例函数
    const computeExpensiveValue = (students: Student[]) => {
        console.log("计算复杂值");
        // 模拟复杂计算
        return students.map(student => student.age * 2).reduce((a, b) => a + b, 0);
    };

    // 使用 useMemo 缓存复杂计算的结果,只有当 students 变化时才重新计算
    const totalAge = useMemo(() => computeExpensiveValue(students), [students]);

    return (
        <div className="container">
            <h1>学生列表</h1>
            {/* 显示缓存计算结果 */}
            <p>学生总年龄(*2后的和):{totalAge}</p>
            <div>
                {students.map((item, index) => (
                    <div key={index}>{item.name} - {item.age}</div>
                ))}
            </div>
            <h2>添加新学生</h2>
            <input
                type="text"
                placeholder="请输入姓名"
                ref={ref}
                value={newName}
                onChange={(e) => setName(e.target.value)}
            />
            <input
                type="number"
                placeholder="请输入年龄"
                value={newAge}
                onChange={(e) => setAge(Number(e.target.value))}
            />
            <button
                onClick={() => {
                    const newStudent = { name: newName, age: newAge };
                    ref.current.focus(); // 聚焦到输入框
                    setStudents([...students, newStudent]); // 使用扩展运算符创建新数组
                    setName(""); // 清空输入框
                    setAge(0); // 清空输入框
                }}
            >
                添加学生
            </button>
            {/* 模拟组件的状态变化引发更新 */}
            <span>状态变量:{value.toString()}</span>
            <button onClick={() => setValue(!value)}>切换状态</button>
            <Song />
        </div>
    );
}

只要点击切换状态 那么子组件就会重新更新

在这里插入图片描述

优化 使用memo 此时只有传递的props:{} 属性发生变化子组件才进行更新

// 使用 React.memo 来优化 Song 组件
const Song = memo(() => {
    console.log("我是子组件进行渲染");
    return <div>我是子组件</div>;
});

// 使用 React.memo 包裹 Song 组件,并添加 props
const Song = memo(({ message }) => {
    console.log("我是子组件进行渲染");
    return <div>{message}</div>;
});

useCallback

缓存回调函数

useCallback返回的是缓存函数运行的结果

注意:这个函数是父组件传递子组件的一个函数,防止做无关的刷新,所以这个组件必须配合memo,否则不但不会提升性能

优化后

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

// 定义学生类型
type Student = {
    name: string;
    age: number;
};

// 初始学生列表
const initialStudents: Student[] = [
    { name: "John", age: 25 },
    { name: "Mary", age: 23 },
    { name: "Bob", age: 24 }
];

// 使用 React.memo 
const Song = memo(() => {
    console.log("我是子组件进行渲染");
    return <div>我是子组件</div>;
});

function Index() {
    const ref = useRef(null);
    const [students, setStudents] = useState<Student[]>(initialStudents);
    const [newName, setName] = useState("");
    const [newAge, setAge] = useState(0);
    const [value, setValue] = useState(false);

    // 模拟复杂计算函数
    const computeExpensiveValue = useCallback((students: Student[]) => {
        console.log("计算复杂值");
        return students.map(student => student.age * 2).reduce((a, b) => a + b, 0);
    }, []); // 依赖项为空,这个函数在组件挂载时只会创建一次

    // 使用 useMemo 缓存复杂计算的结果,只有当 students 变化时才重新计算
    const totalAge = useMemo(() => {
        console.log("重新计算总年龄");
        return computeExpensiveValue(students);
    }, [students, computeExpensiveValue]); // 依赖项为 students 和 computeExpensiveValue

    // 缓存的输入处理函数
    const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
        console.log("Name 变化");
        setName(e.target.value);
    }, []); // 依赖项为空,函数在挂载时只会创建一次 其他时候不创建函数实列 配合memo 保证子组件的函数随着子组件的挂载才进行创建

    const handleAgeChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
        console.log("Age 变化");
        setAge(Number(e.target.value));
    }, []); // 依赖项为空

    // 缓存的添加学生函数,依赖于 newName 和 newAge
    const handleAddStudent = useCallback(() => {
        console.log("添加学生");
        const newStudent = { name: newName, age: newAge };
        ref.current.focus(); // 聚焦到输入框
        setStudents(prevStudents => [...prevStudents, newStudent]); // 使用扩展运算符创建新数组
        setName(""); // 清空输入框
        setAge(0); // 清空输入框
    }, [newName, newAge]); // 依赖于 newName 和 newAge

    return (
        <div className="container">
            <h1>学生列表</h1>
            {/* 显示缓存计算结果 */}
            <p>学生总年龄(*2后的和):{totalAge}</p>
            <div>
                {students.map((item, index) => (
                    <div key={index}>{item.name} - {item.age}</div>
                ))}
            </div>
            <h2>添加新学生</h2>
            <input
                type="text"
                placeholder="请输入姓名"
                ref={ref}
                value={newName}
                onChange={handleNameChange} // 使用缓存的输入处理函数
            />
            <input
                type="number"
                placeholder="请输入年龄"
                value={newAge}
                onChange={handleAgeChange} // 使用缓存的输入处理函数
            />
            <button onClick={handleAddStudent}>添加学生</button>

            {/* 模拟状态变化 */}
            <span>状态变量:{value.toString()}</span>
            <button onClick={() => setValue(!value)}>切换状态</button>

            <Song />
        </div>
    );
}

export default Index;

其他hooks

其他的hooks使用场景较少 官方都单独把他们分到一起

在这里插入图片描述

官方文档 https://zh-hans.react.dev/reference/react/hooks

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子不是胖子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值