当前位置: 首页 > news >正文

React速通笔记

相关视频:

黑马程序员前端React18入门到实战视频教程,从react+hooks核心基础到企业级项目开发实战(B站评论、极客园项目等)及大厂面试全通关_哔哩哔哩_bilibili

一、React介绍

React由Meta公司开发,是一个用于 构建Web和原生交互界面的库

React的优势

相较于传统基于DOM开发的优势:

  1. 组件化的开发方式

  2. 不错的性能

相较于其它前端框架的优势:

  1. 丰富的生态

  2. 跨平台支持

React的市场情况:全球最流行,大厂必备

开发环境创建

使用vite安装:

npm create vite@latest my-app -- --template react

二、JSX基础

什么是JSX

概念:JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式

const message = 'this is message'
​
function App(){return (<div><h1>this is title</h1>{message}</div>)
}

优势:

  1. HTML的声明式模版写法

  2. JavaScript的可编程能力

JSX的本质

JSX并不是标准的JS语法,它是 JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中使用

JSX高频场景-JS表达式

在JSX中可以通过 大括号语法{} 识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等

  1. 使用引号传递字符串

  2. 使用JS变量

  3. 函数调用和方法调用

  4. 使用JavaScript对象

注意:if语句、switch语句、变量声明不属于表达式,不能出现在{}中

const message = 'this is message'
​
function getAge(){return 18
}
​
function App(){return (<div><h1>this is title</h1>{/* 字符串识别 */}{'this is str'}{/* 变量识别 */}{message}{/* 变量识别 */}{message}{/* 函数调用 渲染为函数的返回值 */}{getAge()}</div>)
}

JSX高频场景-列表渲染

在JSX中可以使用原生js种的map方法 实现列表渲染

const list = [{id:1001, name:'Vue'},{id:1002, name: 'React'},{id:1003, name: 'Angular'}
]
​
function App(){return (<ul>{list.map(item=><li key={item.id}>{item}</li>)}</ul>)
}

JSX高频场景-条件渲染

在React中,可以通过逻辑与运算符&&、三元表达式(?:) 实现基础的条件渲染

const flag = true
const loading = falsefunction App(){return (<>{flag && <span>this is span</span>}{loading ? <span>loading...</span>:<span>this is span</span>}</>)
}

JSX高频场景-复杂条件渲染

需求:列表中需要根据文章的状态适配 解决方案:自定义函数 + 判断语句

const type = 1  // 0|1|3
​
function getArticleJSX(){if(type === 0){return <div>无图模式模版</div>}else if(type === 1){return <div>单图模式模版</div>}else(type === 3){return <div>三图模式模版</div>}
}
​
function App(){return (<>{ getArticleJSX() }</>)
}

React的事件绑定

基础实现

React中的事件绑定,通过语法 on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法

function App(){const clickHandler = ()=>{console.log('button按钮点击了')}return (<button onClick={clickHandler}>click me</button>)
}

使用事件参数

在事件回调函数中设置形参e即可

function App(){const clickHandler = (e)=>{console.log('button按钮点击了', e)}return (<button onClick={clickHandler}>click me</button>)
}

传递自定义参数

语法:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参

function App(){const clickHandler = (name)=>{console.log('button按钮点击了', name)}return (<button onClick={()=>clickHandler('jack')}>click me</button>)
}

注意:不能直接写函数调用,这里事件绑定需要一个函数引用

同时传递事件对象和自定义参数

语法:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应

function App(){const clickHandler = (name,e)=>{console.log('button按钮点击了', name,e)}return (<button onClick={(e)=>clickHandler('jack',e)}>click me</button>)
}

React组件基础使用

组件是什么

概念:一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以服用多次

组件基础使用

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可

// 1. 定义组件
function Button(){return <button>click me</button>
}
​
// 2. 使用组件
function App(){return (<div>{/* 自闭和 */}<Button/>{/* 成对标签 */}<Button></Button></div>)
}

组件状态管理-useState

基础使用

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果 和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)

function App(){const [ count, setCount ] = React.useState(0)return (<div><button onClick={()=>setCount(count+1)}>{ count }</button></div>)
}

状态的修改规则

在React中状态被认为是只读的,我们应该始终替换它而不是修改它, 直接修改状态不能引发视图更新

修改对象状态

对于对象类型的状态变量,应该始终给set方法一个全新的对象 来进行修改

组件的基础样式处理

React组件基础的样式控制有俩种方式,行内样式和class类名控制

<div style={{ color:'red'}}>this is div</div>
.foo{color: red;
}
import './index.css'
​
function App(){return (<div><span className="foo">this is span</span></div>)
}

React表单控制

受控绑定

概念:使用React组件的状态(useState)控制表单的状态


function App(){const [value, setValue] = useState('')return (<input type="text" value={value} onChange={e => setValue(e.target.value)}/>)
}

非受控绑定

概念:通过获取DOM的方式获取表单的输入数据

function App(){const inputRef = useRef(null)
​const onChange = ()=>{console.log(inputRef.current.value)}return (<input type="text" ref={inputRef}onChange={onChange}/>)
}

三、React组件通信

概念:组件通信就是组件之间的数据传递, 根据组件嵌套关系的不同,有不同的通信手段和方法

  1. A-B 父子通信

  2. B-C 兄弟通信

  3. A-E 跨层通信

父子通信-父传子

基础实现

实现步骤

  1. 父组件传递数据 - 在子组件标签上绑定属性

  2. 子组件接收数据 - 子组件通过props参数接收数据

function Son(props){return <div>{ props.name }</div>
}
​
​
function App(){const name = 'this is app name'return (<div><Son name={name}/></div>)
}

props说明

props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX

props是只读对象 子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改

特殊的prop-chilren

场景:当我们把内容嵌套在组件的标签内部时,组件会自动在名为children的prop属性中接收该内容

父子通信-子传父

核心思路:在子组件中调用父组件中的函数并传递参数

function Son({ onGetMsg }){const sonMsg = 'this is son msg'return (<div>{/* 在子组件中执行父组件传递过来的函数 */}<button onClick={()=>onGetMsg(sonMsg)}>send</button></div>)
}
​
​
function App(){const getMsg = (msg)=>console.log(msg)return (<div>{/* 传递父组件中的函数到子组件 */}<Son onGetMsg={ getMsg }/></div>)
}

兄弟组件通信

实现思路: 借助 状态提升 机制,通过共同的父组件进行兄弟之间的数据传递

  1. A组件先通过子传父的方式把数据传递给父组件App

  2. App拿到数据之后通过父传子的方式再传递给B组件

// 1. 通过子传父 A -> App
// 2. 通过父传子 App -> B
​
import { useState } from "react"
​
function A ({ onGetAName }) {// Son组件中的数据const name = 'this is A name'return (<div>this is A compnent,<button onClick={() => onGetAName(name)}>send</button></div>)
}
​
function B ({ name }) {return (<div>this is B compnent,{name}</div>)
}
​
function App () {const [name, setName] = useState('')const getAName = (name) => {setName(name)}return (<div>this is App<A onGetAName={getAName} /><B name={name} /></div>)
}
​
export default App

跨层组件通信

实现步骤:

  1. 使用 createContext方法创建一个上下文对象Ctx

  2. 在顶层组件(App)中通过 Ctx.Provider 组件提供数据

  3. 在底层组件(B)中通过 useContext 钩子函数获取消费数据

// App -> A -> B
​
import { createContext, useContext } from "react"
​
// 1. createContext方法创建一个上下文对象
​
const MsgContext = createContext()
​
function A () {return (<div>this is A component<B /></div>)
}
​
function B () {// 3. 在底层组件 通过useContext钩子函数使用数据const msg = useContext(MsgContext)return (<div>this is B compnent,{msg}</div>)
}
​
function App () {const msg = 'this is app msg'return (<div>{/* 2. 在顶层组件 通过Provider组件提供数据 */}<MsgContext.Provider value={msg}>this is App<A /></MsgContext.Provider></div>)
}
​
export default App

React副作用管理-useEffect

useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 比 如发送AJAX请求,更改DOM等等

说明:上面的组件中没有发生任何的用户事件,组件渲染完毕之后就需要和服务器要数据,整个过程属于“只由渲染引起的操作”

基础使用

需求:在组件渲染完毕之后,立刻从服务端获取平道列表数据并显示到页面中

说明:

  1. 参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作

  2. 参数2是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次

 warning:接口地址:http://geek.itheima.net/v1_0/channels

useEffect依赖说明

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现

依赖项副作用功函数的执行时机
没有依赖项组件初始渲染 + 组件更新时执行
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 依赖项变化时执行

清除副作用

概念:在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用

说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行

import { useEffect, useState } from "react"function Son () {// 1. 渲染时开启一个定时器useEffect(() => {const timer = setInterval(() => {console.log('定时器执行中...')}, 1000)return () => {// 清除副作用(组件卸载时)clearInterval(timer)}}, [])return <div>this is son</div>
}function App () {// 通过条件渲染模拟组件卸载const [show, setShow] = useState(true)return (<div>{show && <Son />}<button onClick={() => setShow(false)}>卸载Son组件</button></div>)
}export default App

自定义Hook实现

概念:自定义Hook是以 use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

// 封装自定义Hook// 问题: 布尔切换的逻辑 当前组件耦合在一起的 不方便复用// 解决思路: 自定义hookimport { useState } from "react"function useToggle () {// 可复用的逻辑代码const [value, setValue] = useState(true)const toggle = () => setValue(!value)// 哪些状态和回调函数需要在其他组件中使用 returnreturn {value,toggle}
}// 封装自定义hook通用思路// 1. 声明一个以use打头的函数
// 2. 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
// 3. 把组件中用到的状态或者回调return出去(以对象或者数组)
// 4. 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用function App () {const { value, toggle } = useToggle()return (<div>{value && <div>this is div</div>}<button onClick={toggle}>toggle</button></div>)
}export default App

React Hooks使用规则

  1. 只能在组件中或者其他自定义Hook函数中调用

  2. 只能在组件的顶层调用,不能嵌套在if、for、其它的函数中


四、Redux介绍

Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行 作用:通过集中管理的方式管理应用的状态

为什么要使用Redux?

  1. 独立于组件,无视组件之间的层级关系,简化通信问题

  2. 单项数据流清晰,易于定位bug

  3. 调试工具配套良好,方便调试

Redux快速体验

1. 实现计数器

需求:不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器

使用步骤:

  1. 定义一个 reducer 函数 (根据当前想要做的修改返回一个新的状态)

  2. 使用createStore方法传入 reducer函数 生成一个store实例对象

  3. 使用store实例的 subscribe方法 订阅数据的变化(数据一旦变化,可以得到通知)

  4. 使用store实例的 dispatch方法提交action对象 触发数据变化(告诉reducer你想怎么改数据)

  5. 使用store实例的 getState方法 获取最新的状态数据更新到视图中

代码实现:

<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>
​
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
​
<script>// 定义reducer函数 // 内部主要的工作是根据不同的action 返回不同的statefunction counterReducer (state = { count: 0 }, action) {switch (action.type) {case 'INCREMENT':return { count: state.count + 1 }case 'DECREMENT':return { count: state.count - 1 }default:return state}}// 使用reducer函数生成store实例const store = Redux.createStore(counterReducer)
​// 订阅数据变化store.subscribe(() => {console.log(store.getState())document.getElementById('count').innerText = store.getState().count
​})// 增const inBtn = document.getElementById('increment')inBtn.addEventListener('click', () => {store.dispatch({type: 'INCREMENT'})})// 减const dBtn = document.getElementById('decrement')dBtn.addEventListener('click', () => {store.dispatch({type: 'DECREMENT'})})
</script>

2. Redux数据流架构

Redux的难点是理解它对于数据修改的规则, 下图动态展示了在整个数据的修改中,数据的流向

为了职责清晰,Redux代码被分为三个核心的概念,我们学redux,其实就是学这三个核心概念之间的配合,三个概念分别是:

  1. state: 一个对象 存放着我们管理的数据

  2. action: 一个对象 用来描述你想怎么改数据

  3. reducer: 一个函数 根据action的描述更新state

Redux与React - 环境准备

Redux虽然是一个框架无关可以独立运行的插件,但是社区通常还是把它与React绑定在一起使用,以一个计数器案例体验一下Redux + React 的基础使用

1. 配套工具

在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux

  1. Redux Toolkit(RTK)- 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式

  2. react-redux - 用来 链接 Redux 和 React组件 的中间件

安装配套工具:

npm i @reduxjs/toolkit  react-redux 

2. store目录结构设计

  1. 通常集中状态管理的部分都会单独创建一个单独的 store 目录

  2. 应用通常会有很多个子store模块,所以创建一个 modules 目录,在内部编写业务分类的子store

  3. store中的入口文件 index.js 的作用是组合modules中所有的子模块,并导出store

Redux与React - 实现counter

1. 整体路径熟悉

2. 使用React Toolkit 创建 counterStore

import { createSlice } from '@reduxjs/toolkit'
​
const counterStore = createSlice({// 模块名称独一无二name: 'counter',// 初始数据initialState: {count: 1},// 修改数据的同步方法reducers: {increment (state) {state.count++},decrement(state){state.count--}}
})
// 结构出actionCreater
const { increment,decrement } = counter.actions
​
// 获取reducer函数
const counterReducer = counterStore.reducer
​
// 导出
export { increment, decrement }
export default counterReducer
import { configureStore } from '@reduxjs/toolkit'
​
import counterReducer from './modules/counterStore'
​
export default configureStore({reducer: {// 注册子模块counter: counterReducer}
})

3. 为React注入store

react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,链接正式建立

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'
​
ReactDOM.createRoot(document.getElementById('root')).render(// 提供store数据<Provider store={store}><App /></Provider>
)

4. React组件使用store中的数据

在React组件中使用store中的数据,需要用到一个钩子函数 - useSelector,它的作用是把store中的数据映射到组件中,使用样例如下:

5. React组件修改store中的数据

React组件中修改store中的数据需要借助另外一个hook函数 - useDispatch,它的作用是生成提交action对象的dispatch函数,使用样例如下:

Redux与React - 提交action传参

需求:组件中有俩个按钮 add to 10add to 20 可以直接把count值修改到对应的数字,目标count值是在组件中传递过去的,需要在提交action的时候传递参数

实现方式:在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传递参数,参数会被传递到action对象payload属性上

Redux与React - 异步action处理

需求理解

实现步骤

  1. 创建store的写法保持不变,配置好同步修改状态的方法

  2. 单独封装一个函数,在函数内部return一个新函数,在新函数中 2.1 封装异步请求获取数据 2.2 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交

  3. 组件中dispatch的写法保持不变

代码实现

测试接口地址: http://geek.itheima.net/v1_0/channels

import { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'
​
const channelStore = createSlice({name: 'channel',initialState: {channelList: []},reducers: {setChannelList (state, action) {state.channelList = action.payload}}
})
​
​
// 创建异步
const { setChannelList } = channelStore.actions
const url = 'http://geek.itheima.net/v1_0/channels'
// 封装一个函数 在函数中return一个新函数 在新函数中封装异步
// 得到数据之后通过dispatch函数 触发修改
const fetchChannelList = () => {return async (dispatch) => {const res = await axios.get(url)dispatch(setChannelList(res.data.data.channels))}
}
​
export { fetchChannelList }
​
const channelReducer = channelStore.reducer
export default channelReducer
import { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchChannelList } from './store/channelStore'
​
function App () {// 使用数据const { channelList } = useSelector(state => state.channel)useEffect(() => {dispatch(fetchChannelList())}, [dispatch])
​return (<div className="App"><ul>{channelList.map(task => <li key={task.id}>{task.name}</li>)}</ul></div>)
}
​
export default App

五、Zustand快速上手

store/index.js - 创建store

import { create } from 'zustand'
​
const useStore = create((set) => {return {count: 0,inc: () => {set(state => ({ count: state.count + 1 }))}}
})
​
export default useStore

app.js - 绑定组件

import useStore from './store/useCounterStore.js'
​
function App() {const { count, inc } = useStore()return <button onClick={inc}>{count}</button>
}
​
export default App

异步支持

对于异步操作的支持不需要特殊的操作,直接在函数中编写异步逻辑,最后把接口的数据放到set函数中返回即可

store/index.js - 创建store

import { create } from 'zustand'
​
const URL = 'http://geek.itheima.net/v1_0/channels'
​
const useStore = create((set) => {return {count: 0,ins: () => {return set(state => ({ count: state.count + 1 }))},channelList: [],fetchChannelList: async () => {const res = await fetch(URL)const jsonData = await res.json()set({channelList: jsonData.data.channels})}}
})
​
export default useStore
app.js - 绑定组件
import { useEffect } from 'react'
import useChannelStore from './store/channelStore'
​
function App() {const { channelList, fetchChannelList } = useChannelStore()useEffect(() => {fetchChannelList()}, [fetchChannelList])
​return (<ul>{channelList.map((item) => (<li key={item.id}>{item.name}</li>))}</ul>)
}
​
export default App

切片模式

场景:当我们单个store比较大的时候,可以采用一种切片模式进行模块拆分再组合

拆分并组合切片

import { create } from 'zustand'
​
// 创建counter相关切片
const createCounterStore = (set) => {return {count: 0,setCount: () => {set(state => ({ count: state.count + 1 }))}}
}
​
// 创建channel相关切片
const createChannelStore = (set) => {return {channelList: [],fetchGetList: async () => {const res = await fetch(URL)const jsonData = await res.json()set({ channelList: jsonData.data.channels })}}
}
​
// 组合切片
const useStore = create((...a) => ({...createCounterStore(...a),...createChannelStore(...a)
}))

组件使用

function App() {const {count, inc, channelList, fetchChannelList } = useStore()return (<><button onClick={inc}>{count}</button><ul>{channelList.map((item) => (<li key={item.id}>{item.name}</li>))}</ul></>)
}
​
export default App

六、路由快速上手

1. 什么是前端路由

一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染

安装最新的ReactRouter包:

npm i react-router-dom

2. 快速开始

import React from 'react'
import ReactDOM from 'react-dom/client'const router = createBrowserRouter([{path:'/login',element: <div>登录</div>},{path:'/article',element: <div>文章</div>}
])ReactDOM.createRoot(document.getElementById('root')).render(<RouterProvider router={router}/>
)

抽象路由模块

路由导航

1. 什么是路由导航

路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信

2. 声明式导航

声明式导航是指通过在模版中通过 <Link/> 组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行

语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可

3. 编程式导航

编程式导航是指通过 useNavigate 钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活

语法说明:通过调用navigate方法传入地址path实现跳转

导航传参

嵌套路由配置

1. 什么是嵌套路由

在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由,例如:

2. 嵌套路由配置

实现步骤

  1. 使用 children属性配置路由嵌套关系

  2. 使用 <Outlet/> 组件配置二级路由渲染位置

3. 默认二级路由

当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true

4. 404路由配置

场景:当浏览器输入url的路径在整个路由配置中都找不到对应的 path,为了用户体验,可以使用 404 兜底组件进行渲染

实现步骤:

  1. 准备一个NotFound组件

  2. 在路由表数组的末尾,以*号作为路由path配置路由

5. 俩种路由模式

各个主流框架的路由常用的路由模式有俩种,history模式和hash模式, ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建

路由模式url表现底层原理是否需要后端支持
historyurl/loginhistory对象 + pushState事件需要
hashurl/#/login监听hashChange事件不需要

七、React 内置 Hook

1. useReducer

基础使用

作用: 让 React 管理多个相对关联的状态数据

import { useReducer } from 'react'
​
// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {switch (action.type) {case 'INC':return state + 1case 'DEC':return state - 1default:return state}
}
​
function App() {// 2. 使用useReducer分派actionconst [state, dispatch] = useReducer(reducer, 0)return (<>{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}<button onClick={() => dispatch({ type: 'DEC' })}>-</button>{state}<button onClick={() => dispatch({ type: 'INC' })}>+</button></>)
}
​
export default App

更新流程

分派action传参

做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数

// 定义reducer
​
import { useReducer } from 'react'
​
// 1. 根据不同的action返回不同的新状态
function reducer(state, action) {console.log('reducer执行了')switch (action.type) {case 'INC':return state + 1case 'DEC':return state - 1case 'UPDATE':return state + action.payloaddefault:return state}
}
​
function App() {// 2. 使用useReducer分派actionconst [state, dispatch] = useReducer(reducer, 0)return (<>{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}<button onClick={() => dispatch({ type: 'DEC' })}>-</button>{state}<button onClick={() => dispatch({ type: 'INC' })}>+</button><button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}>update to 100</button></>)
}
​
export default App

2. useMemo

作用:它在每次重新渲染的时候能够缓存计算的结果。

看个场景

下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费

// useMemo
// 作用:在组件渲染时缓存计算的结果
​
import { useState } from 'react'
​
function factorialOf(n) {console.log('斐波那契函数执行了')return n <= 0 ? 1 : n * factorialOf(n - 1)
}
​
function App() {const [count, setCount] = useState(0)// 计算斐波那契之和const sumByCount = factorialOf(count)
​const [num, setNum] = useState(0)
​return (<>{sum}<button onClick={() => setCount(count + 1)}>+count:{count}</button><button onClick={() => setNum(num + 1)}>+num:{num}</button></>)
}
​
export default App

useMemo缓存计算结果

思路: 只有count发生变化时才重新进行计算

import { useMemo, useState } from 'react'
​
function fib (n) {console.log('计算函数执行了')if (n < 3) return 1return fib(n - 2) + fib(n - 1)
}
​
function App() {const [count, setCount] = useState(0)// 计算斐波那契之和// const sum = fib(count)// 通过useMemo缓存计算结果,只有count发生变化时才重新计算const sum = useMemo(() => {return fib(count)}, [count])
​const [num, setNum] = useState(0)
​return (<>{sum}<button onClick={() => setCount(count + 1)}>+count:{count}</button><button onClick={() => setNum(num + 1)}>+num:{num}</button></>)
}
​
export default App

3. React.memo

作用:允许组件在props没有改变的情况下跳过重新渲染

组件默认的渲染机制

默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染

// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染
​
import { useState } from 'react'
​
function Son() {console.log('子组件被重新渲染了')return <div>this is son</div>
}
​
function App() {const [, forceUpdate] = useState()console.log('父组件重新渲染了')return (<><Son /><button onClick={() => forceUpdate(Math.random())}>update</button></>)
}
​
export default App

使用React.memo优化

机制:只有props发生变化时才重新渲染 下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染

import React, { useState } from 'react'
​
const MemoSon = React.memo(function Son() {console.log('子组件被重新渲染了')return <div>this is span</div>
})
​
function App() {const [, forceUpdate] = useState()console.log('父组件重新渲染了')return (<><MemoSon /><button onClick={() => forceUpdate(Math.random())}>update</button></>)
}
​
export default App

props变化重新渲染

import React, { useState } from 'react'
​
const MemoSon = React.memo(function Son() {console.log('子组件被重新渲染了')return <div>this is span</div>
})
​
function App() {console.log('父组件重新渲染了')
​const [count, setCount] = useState(0)return (<><MemoSon count={count} /><button onClick={() => setCount(count + 1)}>+{count}</button></>)
}
​
export default App

props的比较机制

对于props的比较,进行的是‘浅比较’,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性

import React, { useState } from 'react'
​
const MemoSon = React.memo(function Son() {console.log('子组件被重新渲染了')return <div>this is span</div>
})
​
function App() {// const [count, setCount] = useState(0)const [list, setList] = useState([1, 2, 3])return (<><MemoSon list={list} /><button onClick={() => setList([1, 2, 3])}>{JSON.stringify(list)}</button></>)
}
​
export default App

说明:虽然俩次的list状态都是 [1,2,3] , 但是因为组件App俩次渲染生成了不同的对象引用list,所以传给MemoSon组件的props视为不同,子组件就会发生重新渲染

自定义比较函数

如果上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现

import React, { useState } from 'react'
​
// 自定义比较函数
function arePropsEqual(oldProps, newProps) {console.log(oldProps, newProps)return (oldProps.list.length === newProps.list.length &&oldProps.list.every((oldItem, index) => {const newItem = newProps.list[index]console.log(newItem, oldItem)return oldItem === newItem}))
}
​
const MemoSon = React.memo(function Son() {console.log('子组件被重新渲染了')return <div>this is span</div>
}, arePropsEqual)
​
function App() {console.log('父组件重新渲染了')const [list, setList] = useState([1, 2, 3])return (<><MemoSon list={list} /><button onClick={() => setList([1, 2, 3])}>内容一样{JSON.stringify(list)}</button><button onClick={() => setList([4, 5, 6])}>内容不一样{JSON.stringify(list)}</button></>)
}
​
export default App

4. useCallback

看个场景

上一小节我们说到,当给子组件传递一个引用类型prop的时候,即使我们使用了memo 函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback缓存回调函数

// useCallBack
​
import { memo, useState } from 'react'
​
const MemoSon = memo(function Son() {console.log('Son组件渲染了')return <div>this is son</div>
})
​
function App() {const [, forceUpate] = useState()console.log('父组件重新渲染了')const onGetSonMessage = (message) => {console.log(message)}
​return (<div><MemoSon onGetSonMessage={onGetSonMessage} /><button onClick={() => forceUpate(Math.random())}>update</button></div>)
}
​
export default App

useCallback缓存函数

useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用

// useCallBack
​
import { memo, useCallback, useState } from 'react'
​
const MemoSon = memo(function Son() {console.log('Son组件渲染了')return <div>this is son</div>
})
​
function App() {const [, forceUpate] = useState()console.log('父组件重新渲染了')const onGetSonMessage = useCallback((message) => {console.log(message)}, [])
​return (<div><MemoSon onGetSonMessage={onGetSonMessage} /><button onClick={() => forceUpate(Math.random())}>update</button></div>)
}
​
export default App

5. forwardRef(已废弃)

作用:允许组件使用ref将一个DOM节点暴露给父组件

import { forwardRef, useRef } from 'react'
​
const MyInput = forwardRef(function Input(props, ref) {return <input {...props} type="text" ref={ref} />
}, [])
​
function App() {const ref = useRef(null)
​const focusHandle = () => {console.log(ref.current.focus())}
​return (<div><MyInput ref={ref} /><button onClick={focusHandle}>focus</button></div>)
}
​
export default App

从 React 19 开始, ref 可作为 prop 使用 。在 React 18 及更早版本中,需要通过 forwardRef 来获取 ref 。

6. useImperativeHandle

作用:如果我们并不想暴露子组件中的DOM而是想暴露子组件内部的方法

import { forwardRef, useImperativeHandle, useRef } from 'react'
​
const MyInput = forwardRef(function Input(props, ref) {// 实现内部的聚焦逻辑const inputRef = useRef(null)const focus = () => inputRef.current.focus()
​// 暴露子组件内部的聚焦方法useImperativeHandle(ref, () => {return {focus,}})
​return <input {...props} ref={inputRef} type="text" />
})
​
function App() {const ref = useRef(null)
​const focusHandle = () => ref.current.focus()
​return (<div><MyInput ref={ref} /><button onClick={focusHandle}>focus</button></div>)
}
​
export default App

八、Hooks 与 TypeScript

useState

简单场景

简单场景下,可以使用TS的自动推断机制,不用特殊编写类型注解,运行良好

const [val, toggle] = React.useState(false)
​
// `val` 会被自动推断为布尔类型
// `toggle` 方法调用时只能传入布尔类型

复杂场景

复杂数据类型,useState支持通过泛型参数指定初始参数类型以及setter函数的入参类型

type User = {name: stringage: number
}
const [user, setUser] = React.useState<User>({name: 'jack',age: 18
})
// 执行setUser
setUser(newUser)
// 这里newUser对象只能是User类型

没有具体默认值

实际开发时,有些时候useState的初始值可能为null或者undefined,按照泛型的写法是不能通过类型校验的,此时可以通过完整的类型联合null或者undefined类型即可

type User = {name: Stringage: Number
}
const [user, setUser] = React.useState<User>(null)
// 上面会类型错误,因为null并不能分配给User类型
​
const [user, setUser] = React.useState<User | null>(null)
// 上面既可以在初始值设置为null,同时满足setter函数setUser的参数可以是具体的User类型

useRef

在TypeScript的环境下,useRef 函数返回一个只读 或者 可变 的引用,只读的场景常见于获取真实dom,可变的场景,常见于缓存一些数据,不跟随组件渲染,下面分俩种情况说明

获取dom

获取DOM时,通过泛型参数指定具体的DOM元素类型即可

function Foo() {// 尽可能提供一个具体的dom type, 可以帮助我们在用dom属性时有更明确的提示// divRef的类型为 RefObject<HTMLDivElement>const inputRef = useRef<HTMLDivElement>(null)
​useEffect(() => {inputRef.current.focus()})
​return <div ref={inputRef}>etc</div>
}
// 如果你可以确保divRef.current 不是null,也可以在传入初始值的位置// 添加非null标记
const divRef = useRef<HTMLDivElement>(null!)
// 不再需要检查`divRef.current` 是否为null
doSomethingWith(divRef.current)

稳定引用存储器

当做为可变存储容器使用的时候,可以通过泛型参数指定容器存入的数据类型, 在还为存入实际内容时通常把null作为初始值,所以依旧可以通过联合类型做指定

interface User {age: number
}
​
function App(){const timerRef = useRef<number | undefined>(undefined)const userRes = useRef<User | null> (null)useEffect(()=>{timerRef.current = window.setInterval(()=>{console.log('测试')},1000)return ()=>clearInterval(timerRef.current)})return <div> this is app</div>
}

九、Component 与 TypeScript

为Props添加类型

props作为React组件的参数入口,添加了类型之后可以限制参数输入以及在使用props有良好的类型提示

使用interface接口

interface Props {className: string
}
​
export const Button = (props:Props)=>{const { className } = propsreturn <button className={ className }>Test</button>
}

使用自定义类型Type

type Props =  {className: string
}
​
export const Button = (props:Props)=>{const { className } = propsreturn <button className={ className }>Test</button>
}

为Props的chidren属性添加类型

children属性和props中其他的属性不同,它是React系统中内置的,其它属性我们可以自由控制其类型,children属性的类型最好由React内置的类型提供,兼容多种类型

type Props = {children: React.ReactNode
}
​
export const Button = (props: Props)=>{const { children } = propsreturn <button>{ children }</button>
}

说明:React.ReactNode是一个React内置的联合类型,包括 React.ReactElementstringnumber React.ReactFragmentReact.ReactPortalbooleannullundefined

为事件prop添加类型

// props + ts
type Props = {onGetMsg?: (msg: string) => void
}
​
function Son(props: Props) {const { onGetMsg } = propsconst clickHandler = () => {onGetMsg?.('this is msg')}return <button onClick={clickHandler}>sendMsg</button>
}
​
function App() {const getMsgHandler = (msg: string) => {console.log(msg)}return (<><Son onGetMsg={(msg) => console.log(msg)} /><Son onGetMsg={getMsgHandler} /></>)
}
​
export default App

为事件handle添加类型

为事件回调添加类型约束需要使用React内置的泛型函数来做,比如最常见的鼠标点击事件和表单输入事件:

function App(){const changeHandler: React.ChangeEventHandler<HTMLInputElement> = (e)=>{console.log(e.target.value)}const clickHandler: React.MouseEventHandler<HTMLButtonElement> = (e)=>{console.log(e.target)}
​return (<><input type="text" onChange={ changeHandler }/><button onClick={ clickHandler }> click me!</button></>)
}

http://www.xdnf.cn/news/196525.html

相关文章:

  • 初识Python
  • 【Keil5-开发指南】
  • Java实现基数排序算法
  • 机器学习day2
  • 深入理解链表:从基础操作到高频面试题解析
  • 省哲学社科基金项目申请书(论证活页)模版框架参考
  • 013几何数学——算法备赛
  • web技术与Nginx网站服务
  • word2Vec与GloVe的区别
  • LeetCode 1482. 制作 m 束花所需的最少天数
  • 【SpringMVC】详解参数传递与实战指南
  • MANIPTRANS:通过残差学习实现高效的灵巧双手操作迁移
  • 策略模式:灵活的算法封装与切换
  • 实验研究 | 千眼狼高速摄像机驱动精密制造创新
  • 9.学习笔记-springboot(P90-P104)
  • Spring MVC 基础 - 从零构建企业级Web应用
  • 从零到一MCP详细教程——入门
  • 深度相机(一)——深度相机模型及用途介绍
  • vuex刷新数据丢失解决方案-vuex-persist
  • 软考-软件设计师中级备考 6、数据结构 图
  • springboot 实现敏感信息脱敏
  • 昆明理工大学2025年891计算机专业核心考研真题解析
  • react中有哪几种数据结构?分别是干什么的?
  • 易基因:何川团队开发新m6A测序方法 可温和条件下高分辨率/低背景噪声检测m6A修饰|Nature子刊
  • MCU通用输入输出端口(GPIO)设计指南
  • 在另外一台可以科学下载的电脑用ollama下载模型后,怎么导入到另外一台服务器的ollama使用
  • 龙虎榜——20250428
  • 前端excel导出
  • 北重数控滑台加工厂家:汽车零部件试验铁地板-安全性能的测试方法
  • dameng-mcp-server达梦MCP服务