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.pswp.cn/web/77654.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77654.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77654.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

人工智能與機器學習:Python從零實現K-Means 算法

&#x1f9e0; 向所有學習者致敬&#xff01; “學習不是裝滿一桶水&#xff0c;而是點燃一把火。” —— 葉芝 我的博客主頁&#xff1a; https://lizheng.blog.csdn.net &#x1f310; 歡迎點擊加入AI人工智能社區&#xff01; &#x1f680; 讓我們一起努力&#xff0c;共創…

【神經網絡與深度學習】訓練集與驗證集的功能解析與差異探究

引言 在深度學習模型的訓練過程中&#xff0c;訓練集和驗證集是兩個關鍵組成部分&#xff0c;它們在模型性能的提升和評估中扮演著不可替代的角色。通過分析這兩者的區別和作用&#xff0c;可以幫助我們深入理解模型的學習過程和泛化能力&#xff0c;同時為防止過擬合及優化超…

Macos m系列芯片環境下python3安裝mysqlclient系列問題

最近學習python3&#xff0c;在安裝mysqlclient的時候遇到了一些問題&#xff0c;直接使用哦pip install mysqlclient 直接報錯了&#xff0c;記錄一下解決方案。 環境信息 設備&#xff1a;Macbook Pro m1 系統&#xff1a;macos Sequoia 15.3.2 最終成功的python版本&#xf…

微信小程序-van-uploader的preview-size

preview-size支持數組格式 修改前修改后1、升級微信小程序里面的van版本:2、 重新構建npm3、重啟微信開發工具 修改前 引用van組件的上傳文件&#xff0c;設置預覽圖尺寸&#xff0c;剛開始設置的是preview-size“140”&#xff0c;出來的效果就是一個正方形。 修改后 1、升級…

2. 第一個網頁:前端基礎入門

第一個網頁&#xff1a;前端基礎入門 一、網頁文件基礎認知 1. 文件擴展名 .htm 或 .html 均為網頁文件后綴&#xff0c;二者功能完全一致擴展名隱藏方法 系統設置 → 文件夾選項 → 查看 → 取消勾選「隱藏已知文件類型的擴展名」 二、前端發展簡史 1. 瀏覽器戰爭與標準混…

云原生--核心組件-容器篇-7-Docker私有鏡像倉庫--Harbor

1、Harbor的定義與核心作用 定義&#xff1a; Harbor是由VMware開源的企業級容器鏡像倉庫系統&#xff0c;后捐贈給 CNCF (Cloud Native Computing Foundation)。它基于Docker Registry擴展了企業級功能&#xff0c;用于存儲、分發和管理容器鏡像&#xff08;如Docker、OCI標準…

Java項目與技術棧場景題深度解析

Java項目與技術棧場景題深度解析 在互聯網大廠Java求職者的面試中&#xff0c;經常會被問到關于Java項目或技術棧的場景題。本文通過一個故事場景來展示這些問題的實際解決方案。 第一輪提問 面試官&#xff1a;馬架構&#xff0c;歡迎來到我們公司的面試現場。請問您對Java…

SpringMVC 靜態資源處理 mvc:default-servlet-handler

我們先來看看效果,當我把這一行注釋掉的時候&#xff1a; 我們來看看頁面&#xff1a; 現在我把注釋去掉&#xff1a; 、 可以看到的是&#xff0c;這個時候又可以訪問了 那么我們就可以想&#xff0c;這個 <mvc:default-servlet-handler />它控制著我們頁面的訪問…

【leetcode】最長公共子路徑問題

滾動hash 滾動哈希&#xff08;rolling hash&#xff09;也叫 Rabin-Karp 字符串哈希算法&#xff0c;它是將某個字符串看成某個進制下的整數&#xff0c;并將其對應的十進制整數作為hash值。 滾動hash算法的推導 假設有一個長度為n的數組a[0],a[1],a[2],…a[n-1]&#xff0…

【Linux網絡】:套接字之UDP

一、UDP和TCP協議 TCP &#xff08;Transmission Control Protocol 傳輸控制協議&#xff09;的特點&#xff1a; 傳輸層協議有連接&#xff08;在正式通信前要先建立連接&#xff09;可靠傳輸&#xff08;在內部幫我們做可靠傳輸工作&#xff09;面向字節流 UDP &#xff08;U…

React19 useOptimistic 用法

用法 樂觀更新 發起異步請求時&#xff0c;先假設請求會成功立即更新 UI 給用戶反饋若請求最終失敗&#xff0c;再將 UI 恢復到之前的狀態 const [optimisticState, addOptimistic] useOptimistic(state, updateFn) 參數 state&#xff1a;實際值&#xff0c;可以是 useSta…

Deepseek-v3+cline+vscode java自動化編程

1、Deepseek DeepSeek 充值后&#xff0c;創建apikey 2、vscode Visual Studio Code - Code Editing. Redefined 3、下載插件cline 4、配置deepeseek-v3 的密鑰到cline 5、不可用 在開始的幾次調用能正常使用起來&#xff0c;用了幾次后&#xff0c;不能使用了&#xff0c;請求…

數據分析案例:環境數據分析

目錄 數據分析案例&#xff1a;環境數據分析1. 項目背景2. 數據加載與預處理2.1 數據說明2.2 讀取與清洗 3. 探索性數據分析&#xff08;EDA&#xff09;3.1 時序趨勢3.2 日內變化3.3 氣象與污染物相關性 4. 特征工程4.1 時間特征4.2 滯后與滾動統計4.3 目標變量 5. 模型構建與…

網絡原理 - 8

目錄 補充 網絡層 IP 協議 基本概念&#xff1a; 協議頭格式 地址管理 如何解決 IP 地址不夠用呢&#xff1f;&#xff1f;&#xff1f; 1. 動態分配 IP 地址&#xff1a; 2. NAT 機制&#xff08;網絡地址映射&#xff09; 3. IPv6 網段劃分 一些特殊的 IP 地址 …

向量檢索新選擇:FastGPT + OceanBase,快速構建RAG

隨著人工智能的快速發展&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;檢索增強生成&#xff09;技術日益受到關注。向量數據庫作為 RAG 系統的核心基礎設施&#xff0c;堪稱 RAG 的“記憶中樞”&#xff0c;其性能直接關系到大模型生成內容的精準度與…

dify對接飛書云文檔,并且將圖片傳入飛書文檔

前面講了如何讓dify展示圖片&#xff0c;但是如果想讓智能體回答的帶圖片的內容生成個文檔該怎么弄呢&#xff1f;今天來實踐一下。 dify工具帶的有飛書云文檔&#xff0c;正好&#xff0c;咱們就利用飛書云文檔。 1、首先配置飛書云文檔的key跟secret 注意要開頭左側的權限&a…

Linux系統之設置開機啟動運行桌面環境

Linux 開機運行級別介紹與 Ubuntu 桌面環境配置指南 一、Linux 開機運行級別(Runlevel) 在傳統的 Linux 系統(如 SysV init 初始化系統)中,運行級別定義了系統啟動時加載的服務和資源。常見的運行級別如下: 運行級別模式用途0Halt(停機模式)關閉系統1Single User Mode…

Spring Cloud Gateway配置雙向SSL認證(完整指南)

本文將詳細介紹如何為Spring Cloud Gateway配置雙向SSL認證,包括證書生成、配置和使用。 目錄結構 /my-gateway-project ├── /certs │ ├── ca.crt # 根證書 │ ├── ca.key # 根私鑰 │ ├── gateway.crt # 網關證書 │ ├── …

【虛幻5藍圖Editor Utility Widget:創建高效模型材質自動匹配和資產管理工具,從3DMax到Unreal和Unity引擎_系列第二篇】

虛幻5藍圖Editor Utility Widget 一、基礎框架搭建背景&#xff1a;1. 創建Editor Utility Widget2.根控件選擇窗口3.界面功能定位與階段4.查看繼承樹5.目標效果 二、模塊化設計流程1.材質替換核心流程&#xff1a;2.完整代碼如下 三、可視化界面UI布局1. 添加標題欄2. 構建滾動…

LabVIEW實現DMM與開關模塊掃描測量

該程序基于 LabVIEW&#xff0c;用于控制數字萬用表&#xff08;DMM&#xff09;與開關模塊進行測量掃描。通過合理配置觸發源、測量參數等&#xff0c;實現對多路信號的自動化測量與數據獲取&#xff0c;在電子測試、工業測量等領域有廣泛應用。 ? 各步驟功能詳解 開關模塊…