React 生命周期與 Hook 理解解析

從生命周期到 Hook:React 組件演進之路

React 組件的本質是管理渲染與副作用的統一體。Class 組件通過生命周期方法實現這一目標,而函數組件則依靠 Hook 系統達成相同效果。

Class 組件生命周期詳解

生命周期完整流程

Class 組件生命周期可分為三大階段:掛載、更新和卸載。

class Clock extends React.Component {constructor(props) {super(props);this.state = { date: new Date() };console.log('constructor: 組件初始化');}componentDidMount() {console.log('componentDidMount: 組件已掛載');this.timerID = setInterval(() => this.tick(), 1000);}componentDidUpdate(prevProps, prevState) {console.log('componentDidUpdate: 組件已更新');if (prevState.date.getSeconds() !== this.state.date.getSeconds()) {document.title = `當前時間: ${this.state.date.toLocaleTimeString()}`;}}componentWillUnmount() {console.log('componentWillUnmount: 組件即將卸載');clearInterval(this.timerID);}tick() {this.setState({ date: new Date() });}render() {return <div>當前時間: {this.state.date.toLocaleTimeString()}</div>;}
}
掛載階段執行順序
  1. constructor(): 初始化狀態與綁定方法
  2. static getDerivedStateFromProps(): 根據 props 更新 state (React 16.3+)
  3. render(): 計算并返回 JSX
  4. DOM 更新
  5. componentDidMount(): DOM 掛載完成后執行,適合進行網絡請求、訂閱和DOM操作
更新階段執行順序
  1. static getDerivedStateFromProps(): 同掛載階段
  2. shouldComponentUpdate(): 決定是否繼續更新流程
  3. render(): 重新計算 JSX
  4. getSnapshotBeforeUpdate(): 在DOM更新前捕獲信息
  5. DOM 更新
  6. componentDidUpdate(): DOM更新完成后執行
卸載階段
  1. componentWillUnmount(): 清理訂閱、定時器、取消網絡請求等

Class 組件常見陷阱

class UserProfile extends React.Component {state = { userData: null };componentDidMount() {this.fetchUserData();}componentDidUpdate(prevProps) {// 常見錯誤:沒有條件判斷導致無限循環if (prevProps.userId !== this.props.userId) {this.fetchUserData();}}fetchUserData() {fetch(`/api/users/${this.props.userId}`).then(response => response.json()).then(data => this.setState({ userData: data }));}render() {// ...}
}
  1. 未在條件更新中比較props變化:導致無限循環
  2. this綁定問題:事件處理函數中this指向丟失
  3. 生命周期中的副作用管理混亂:副作用散布在多個生命周期方法中
  4. 忘記清理副作用:componentWillUnmount中未清理導致內存泄漏

函數組件與Hook系統剖析

Hook 徹底改變了React組件的編寫方式,將分散在生命周期方法中的邏輯按照關注點聚合。

常用Hook與生命周期對應關系

function Clock() {const [date, setDate] = useState(new Date());useEffect(() => {console.log('組件掛載或更新');// 相當于 componentDidMount 和 componentDidUpdateconst timerID = setInterval(() => {setDate(new Date());}, 1000);// 相當于 componentWillUnmountreturn () => {console.log('清理副作用或組件卸載');clearInterval(timerID);};}, []); // 空依賴數組等同于僅在掛載時執行useEffect(() => {document.title = `當前時間: ${date.toLocaleTimeString()}`;}, [date]); // 僅在date變化時執行return <div>當前時間: {date.toLocaleTimeString()}</div>;
}
Class生命周期Hook對應方式
constructoruseState 初始化
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [依賴項])
componentWillUnmountuseEffect(() => { return () => {} }, [])
shouldComponentUpdateReact.memo + 自定義比較

useEffect 深度解析

useEffect 是React函數組件中管理副作用的核心機制,其工作原理與調度機制決定了React應用的性能與正確性。

useEffect 執行模型
function SearchResults({ query }) {const [results, setResults] = useState([]);const [isLoading, setIsLoading] = useState(false);useEffect(() => {// 1. 執行副作用前的準備工作setIsLoading(true);// 2. 異步副作用const controller = new AbortController();const signal = controller.signal;fetchResults(query, signal).then(data => {setResults(data);setIsLoading(false);}).catch(error => {if (error.name !== 'AbortError') {setIsLoading(false);console.error('搜索失敗:', error);}});// 3. 清理函數 - 在下一次effect執行前或組件卸載時調用return () => {controller.abort();};}, [query]); // 依賴數組:僅當query變化時重新執行return (<div>{isLoading ? (<div>加載中...</div>) : (<ul>{results.map(item => (<li key={item.id}>{item.title}</li>))}</ul>)}</div>);
}
useEffect 內部執行機制
  1. 組件渲染后:React 記住需要執行的 effect 函數
  2. 瀏覽器繪制完成:React 異步執行 effect (與componentDidMount/Update不同,不會阻塞渲染)
  3. 依賴項檢查:僅當依賴數組中的值變化時才重新執行
  4. 清理上一次effect:在執行新effect前先執行上一次effect返回的清理函數

常見的 useEffect 陷阱與解決方案

function ProfilePage({ userId }) {const [user, setUser] = useState(null);// 陷阱1: 依賴項缺失useEffect(() => {fetchUser(userId).then(data => setUser(data));// 應該添加 userId 到依賴數組}, []); // 錯誤:缺少 userId 依賴// 陷阱2: 過于頻繁執行useEffect(() => {const handleResize = () => {console.log('窗口大小改變', window.innerWidth);};window.addEventListener('resize', handleResize);return () => window.removeEventListener('resize', handleResize);}); // 錯誤:缺少依賴數組,每次渲染都重新添加監聽
}
解決方案:
function ProfilePage({ userId }) {const [user, setUser] = useState(null);// 解決方案1: 完整依賴項useEffect(() => {let isMounted = true;fetchUser(userId).then(data => {if (isMounted) setUser(data);});return () => { isMounted = false };}, [userId]); // 正確:添加 userId 到依賴數組// 解決方案2: 使用useCallback防止頻繁創建函數const handleResize = useCallback(() => {console.log('窗口大小改變', window.innerWidth);}, []);useEffect(() => {window.addEventListener('resize', handleResize);return () => window.removeEventListener('resize', handleResize);}, [handleResize]); // 正確:添加handleResize到依賴數組
}

React Hook 規則與原理解析

Hook 工作原理:基于順序的依賴系統

// React內部簡化實現示意
let componentHooks = [];
let currentHookIndex = 0;// 模擬useState的實現
function useState(initialState) {const hookIndex = currentHookIndex;const hooks = componentHooks;// 首次渲染時初始化stateif (hooks[hookIndex] === undefined) {hooks[hookIndex] = initialState;}// 設置狀態的函數const setState = newState => {if (typeof newState === 'function') {hooks[hookIndex] = newState(hooks[hookIndex]);} else {hooks[hookIndex] = newState;}// 觸發重新渲染rerenderComponent(); };currentHookIndex++;return [hooks[hookIndex], setState];
}// 模擬函數組件執行
function RenderComponent(Component) {currentHookIndex = 0;const output = Component();return output;
}

Hook依賴固定的調用順序,這就是為什么:

  1. 不能在條件語句中使用Hook:會打亂Hook的調用順序
  2. 不能在循環中使用Hook:每次渲染時Hook數量必須一致
  3. 只能在React函數組件或自定義Hook中調用Hook:確保React能正確跟蹤狀態

自定義Hook:邏輯復用的最佳實踐

// 自定義Hook: 封裝數據獲取邏輯
function useDataFetching(url) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {let isMounted = true;setLoading(true);const controller = new AbortController();fetch(url, { signal: controller.signal }).then(response => {if (!response.ok) throw new Error('網絡請求失敗');return response.json();}).then(data => {if (isMounted) {setData(data);setLoading(false);}}).catch(error => {if (isMounted && error.name !== 'AbortError') {setError(error);setLoading(false);}});return () => {isMounted = false;controller.abort();};}, [url]);return { data, loading, error };
}// 使用自定義Hook
function UserProfile({ userId }) {const { data: user, loading, error } = useDataFetching(`/api/users/${userId}`);if (loading) return <div>加載中...</div>;if (error) return <div>出錯了: {error.message}</div>;return (<div><h1>{user.name}</h1><p>Email: {user.email}</p></div>);
}

自定義Hook優勢:

  1. 關注點分離:將邏輯與UI完全解耦
  2. 代碼復用:在多個組件間共享邏輯而不是組件本身
  3. 測試友好:邏輯集中,易于單元測試
  4. 清晰的依賴管理:顯式聲明數據流向

高級性能優化技巧

依賴數組優化

function SearchComponent({ defaultQuery }) {// 1. 基本狀態const [query, setQuery] = useState(defaultQuery);// 2. 衍生狀態/計算 - 優化前const [debouncedQuery, setDebouncedQuery] = useState(query);useEffect(() => {const handler = setTimeout(() => {setDebouncedQuery(query);}, 500);return () => clearTimeout(handler);}, [query]); // 每次query變化都會創建新定時器// 3. 網絡請求 - 優化前useEffect(() => {// 這個函數每次渲染都會重新創建const fetchResults = async () => {const response = await fetch(`/api/search?q=${debouncedQuery}`);const data = await response.json();// 處理結果...};fetchResults();}, [debouncedQuery]); // 問題:fetchResults每次都是新函數引用
}

優化后:

function SearchComponent({ defaultQuery }) {// 1. 基本狀態const [query, setQuery] = useState(defaultQuery);// 2. 使用useMemo緩存計算結果const debouncedQuery = useDebouncedValue(query, 500);// 3. 使用useCallback緩存函數引用const fetchResults = useCallback(async (searchQuery) => {const response = await fetch(`/api/search?q=${searchQuery}`);return response.json();}, []); // 空依賴數組,函數引用穩定// 4. 使用穩定函數引用useEffect(() => {let isMounted = true;const getResults = async () => {try {const data = await fetchResults(debouncedQuery);if (isMounted) {// 處理結果...}} catch (error) {if (isMounted) {// 處理錯誤...}}};getResults();return () => { isMounted = false };}, [debouncedQuery, fetchResults]); // fetchResults現在是穩定引用
}// 自定義Hook: 處理防抖
function useDebouncedValue(value, delay) {const [debouncedValue, setDebouncedValue] = useState(value);useEffect(() => {const handler = setTimeout(() => {setDebouncedValue(value);}, delay);return () => clearTimeout(handler);}, [value, delay]);return debouncedValue;
}

React.memo、useMemo 與 useCallback

// 阻止不必要的重渲染
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data, onItemClick }) {console.log('ExpensiveComponent渲染');return (<div>{data.map(item => (<div key={item.id} onClick={() => onItemClick(item.id)}>{item.name}</div>))}</div>);
});function ParentComponent() {const [count, setCount] = useState(0);const [items, setItems] = useState([{ id: 1, name: '項目1' },{ id: 2, name: '項目2' }]);// 問題:每次渲染都創建新函數引用,導致ExpensiveComponent重渲染const handleItemClick = (id) => {console.log('點擊項目:', id);};return (<div><button onClick={() => setCount(count + 1)}>計數: {count}</button>{/* 即使count變化,items沒變,ExpensiveComponent也會重渲染 */}<ExpensiveComponent data={items} onItemClick={handleItemClick} /></div>);
}

優化后:

function ParentComponent() {const [count, setCount] = useState(0);const [items, setItems] = useState([{ id: 1, name: '項目1' },{ id: 2, name: '項目2' }]);// 使用useCallback固定函數引用const handleItemClick = useCallback((id) => {console.log('點擊項目:', id);}, []); // 空依賴數組表示函數引用永不變化// 使用useMemo緩存復雜計算結果const processedItems = useMemo(() => {console.log('處理items數據');return items.map(item => ({...item,processed: true}));}, [items]); // 僅當items變化時重新計算return (<div><button onClick={() => setCount(count + 1)}>計數: {count}</button>{/* 現在count變化不會導致ExpensiveComponent重渲染 */}<ExpensiveComponent data={processedItems} onItemClick={handleItemClick} /></div>);
}

從生命周期到Hook的遷移策略

漸進式遷移Class組件

// 步驟1: 從Class組件提取邏輯到獨立函數
class UserManager extends React.Component {state = {user: null,loading: true,error: null};componentDidMount() {this.fetchUser();}componentDidUpdate(prevProps) {if (prevProps.userId !== this.props.userId) {this.fetchUser();}}fetchUser() {this.setState({ loading: true });fetchUserAPI(this.props.userId).then(data => this.setState({ user: data, loading: false })).catch(error => this.setState({ error, loading: false }));}render() {// 渲染邏輯...}
}// 步驟2: 創建等效的自定義Hook
function useUser(userId) {const [state, setState] = useState({user: null,loading: true,error: null});useEffect(() => {let isMounted = true;setState(s => ({ ...s, loading: true }));fetchUserAPI(userId).then(data => {if (isMounted) {setState({ user: data, loading: false, error: null });}}).catch(error => {if (isMounted) {setState({ user: null, loading: false, error });}});return () => { isMounted = false };}, [userId]);return state;
}// 步驟3: 創建函數組件版本
function UserManager({ userId }) {const { user, loading, error } = useUser(userId);// 渲染邏輯...
}

優雅處理復雜狀態

// Class組件中復雜狀態管理
class FormManager extends React.Component {state = {values: { name: '', email: '', address: '' },errors: {},touched: {},isSubmitting: false,submitError: null,submitSuccess: false};// 大量狀態更新邏輯...
}// 使用useReducer優化復雜狀態管理
function FormManager() {const initialState = {values: { name: '', email: '', address: '' },errors: {},touched: {},isSubmitting: false,submitError: null,submitSuccess: false};const [state, dispatch] = useReducer((state, action) => {switch (action.type) {case 'FIELD_CHANGE':return {...state,values: { ...state.values, [action.field]: action.value },touched: { ...state.touched, [action.field]: true }};case 'VALIDATE':return { ...state, errors: action.errors };case 'SUBMIT_START':return { ...state, isSubmitting: true, submitError: null };case 'SUBMIT_SUCCESS':return { ...state, isSubmitting: false, submitSuccess: true };case 'SUBMIT_ERROR':return { ...state, isSubmitting: false, submitError: action.error };case 'RESET':return initialState;default:return state;}}, initialState);// 使用dispatch來更新狀態const handleFieldChange = (field, value) => {dispatch({ type: 'FIELD_CHANGE', field, value });};// 表單提交邏輯const handleSubmit = async (e) => {e.preventDefault();dispatch({ type: 'SUBMIT_START' });try {await submitForm(state.values);dispatch({ type: 'SUBMIT_SUCCESS' });} catch (error) {dispatch({ type: 'SUBMIT_ERROR', error });}};// 渲染表單...
}

未來:React 18+ 與 Concurrent 模式

隨著 React 18 的發布,并發渲染模式將改變副作用的執行模型。Hook 系統設計與并發渲染天然契合,為未來的 React 應用提供更優雅的狀態與副作用管理。

// React 18 中的新Hook: useTransition
function SearchResults() {const [query, setQuery] = useState('');const [isPending, startTransition] = useTransition();const handleChange = (e) => {// 立即更新輸入框setQuery(e.target.value);// 標記低優先級更新,可被中斷startTransition(() => {// 復雜搜索邏輯,在空閑時執行performSearch(e.target.value);});};return (<div><input value={query} onChange={handleChange} />{isPending ? <div>搜索中...</div> : <ResultsList />}</div>);
}

最后的話

從 Class 組件生命周期到函數組件 Hook 的演進,體現了 React 設計思想的核心變化:從基于時間的生命周期轉向基于狀態的聲明式副作用。這種轉變使組件邏輯更加內聚、可測試和可復用。

理解 React 組件的工作原理和 Hook 系統的設計哲學,是掌握 React 高級開發的關鍵。

在實際開發中,我們應該遵循 Hook 的核心規則,合理管理依賴數組,并善用 useMemo、useCallback 進行性能優化。

參考資源

  • React 官方文檔 - useEffect 指南
  • React 生命周期圖解
  • Dan Abramov - A Complete Guide to useEffect
  • Kent C. Dodds - React Hooks: What’s going to happen to my tests?
  • React Hooks FAQ
  • Amelia Wattenberger - Thinking in React Hooks
  • Rudi Yardley - Why Do React Hooks Rely on Call Order?

如果你覺得這篇文章有幫助,歡迎點贊收藏,也期待在評論區看到你的想法和建議!👇

終身學習,共同成長。

咱們下一期見

💻

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/82800.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/82800.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/82800.shtml

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

相關文章

數字孿生技術賦能西門子安貝格工廠:全球智能制造標桿的數字化重構實踐

在工業4.0浪潮席卷全球制造業的當下&#xff0c;西門子安貝格電子制造工廠&#xff08;Electronic Works Amberg, EWA&#xff09;憑借數字孿生技術的深度應用&#xff0c;構建起全球制造業數字化轉型的典范。這座位于德國巴伐利亞州的“未來工廠”&#xff0c;通過虛實融合的數…

從Homebrew找到openssl.cnf文件并拷貝到Go項目下使用

安裝OpenSSL 在 macOS 上下載和安裝 OpenSSL 最常見和推薦的方式是使用 Homebrew&#xff0c;這是一個 macOS 缺失的包管理器。 如果您還沒有安裝 Homebrew&#xff0c;請先安裝它。安裝 Homebrew 后&#xff0c;安裝 OpenSSL 只需要一條命令。 步驟 1&#xff1a;安裝 Home…

Qt 的簡單示例 -- 地址簿

這個工程里有兩個窗口&#xff0c;都是QWidget派生的窗口 主窗口&#xff1a; 1. 運用了布局&#xff0c;按鈕控件&#xff0c;單行編輯框&#xff0c;富文本編輯框等窗口部件&#xff1b; 2. 運用了 QMap 類&#xff1b; 3. 實現了點擊按鈕彈出子窗口的功能&#xff0c;這里子…

kubernate解決 “cni0“ already has an IP address different from 10.244.0.1/24問題

問題 NetworkPlugin cni failed to set up pod “coredns-5d4b4db-jkmnl_kube-system” network: failed to set bridge addr: “cni0” already has an IP address different from 10.244.0.1/24 解決方案 這個問題通常是由于Flannel網絡插件殘留配置導致的IP地址沖突。以下…

QT+opecv如何更改圖片的拍攝路徑

如何更改相機拍攝圖片的路徑 前言&#xff1a;基礎夯實&#xff1a;效果展示&#xff1a;實現功能&#xff1a;遇到問題&#xff1a;未解決&#xff1a; 核心代碼&#xff1a; 前言&#xff1a; 最近在項目開發中遇到需要讓用戶更改相機拍攝路徑的問題&#xff0c;用戶可自己選…

66常用控件_QTableWidget的使用

目錄 代碼示例:使用QTableWidget Table Widget 使? QTableWidget 表??個表格控件. ?個表格中包含若??, 每???包含若?列. 表格中的每個單元格, 是?個 QTableWidgetItem 對象. QTableWidget 核??法 方法說明item(int row, int column)根據行數數列獲取指定的…

記一次edu未授權訪問漏洞

首先進入該網址是一個登錄界面&#xff0c;查看源代碼&#xff0c;找到js文件&#xff0c;發現存在js.map前端信息泄露&#xff0c;于是我們進行js還原。 得到前端的一些源代碼&#xff0c;以及路由API等&#xff0c;我們就可以通過這個源代碼&#xff0c;進行目錄遍歷&#xf…

MySQL 索引和事務

目錄 前言 一、MySQL 索引介紹 1. 索引概述 2. 索引作用 3. 索引的分類 3.1 普通索引 3.2 唯一索引 3.3 主鍵索引 3.4 組合索引 (最左前綴) 3.5 全文索引 (FULLTEXT) 3.6 創建索引的原則依據 3.7 查看索引 3.8 刪除索引 二、MySQL 事務 1. 事務的 ACID 原則 MYS…

HTML實戰項目:高考加油和中考加油

設計思路 頁面加載后會自動顯示高考內容&#xff0c;點擊頂部按鈕可以切換到中考內容。倒計時會每秒更新&#xff0c;為考生提供實時的備考時間參考。 使用代表希望的藍色和金色漸變作為主色調 頂部導航欄可切換高考/中考內容 添加動態倒計時功能 設計勵志名言卡片和備考小貼…

What is Predictive Maintenance (PdM)? Learn How Industrial IoT Enables PdM

文章大綱 從預防性維護到預測性維護服務的轉變傳統預防性維護的局限性預測性維護的定義工業物聯網(IIoT)如何助力預測性維護預測性維護帶來的成本效益實施預測性維護面臨的挑戰企業轉向預測性維護的原因參考文獻大家好!今天,讓我們一起深入了解一下預測性維護技術。它正在徹…

CCPC shandong 2025 G

題目鏈接&#xff1a;https://codeforces.com/gym/105930/problem/G 題目背景&#xff1a; n 名工人加工 m 個工件&#xff0c;第 i 個工件在第 ti 分鐘的開頭加入 工人 wi 的收件箱。 每分鐘&#xff0c;工人從收件箱里拿出一個工件&#xff0c;完成加工后放入下 一個工人的收…

UE路徑追蹤Path Tracing和Lumen的區別

在Unreal Engine&#xff08;UE&#xff0c;虛幻引擎&#xff09;中&#xff0c;Path Tracing 和 Lumen 是兩種不同的全局光照&#xff08;Global Illumination, GI&#xff09;和反射技術&#xff0c;各自適用于不同的使用場景。以下是它們的主要區別&#xff1a; &#x1f31…

JaCoCo 是什么

JaCoCo&#xff08;Java Code Coverage&#xff09;是一款廣泛使用的 Java 代碼覆蓋率工具&#xff0c;用于分析測試用例對項目代碼的覆蓋程度&#xff0c;幫助開發者識別未被測試的代碼區域&#xff0c;從而提升軟件質量。它通常與 JUnit、TestNG 等測試框架及 Maven、Gradle …

火山引擎扣子系列

您提到的“火山引擎扣子系列”指的應該是 **火山引擎推出的智能AI對話開發與應用平臺——Coze&#xff08;中文名&#xff1a;扣子&#xff09;**。這是一個由字節跳動旗下火山引擎開發的、面向開發者和非技術用戶的**低代碼/無代碼AI Bot開發平臺**&#xff0c;旨在幫助用戶快…

OpenLayers 加載ArcGIS瓦片數據

注&#xff1a;當前使用的是 ol 5.3.0 版本&#xff0c;天地圖使用的key請到天地圖官網申請&#xff0c;并替換為自己的key 隨著GIS應用的不斷發展&#xff0c;Web地圖也越來越豐富&#xff0c;除了像ESRI、超圖、中地數碼這樣GIS廠商有各自的數據源格式&#xff0c;也有Google…

大模型是什么?

大模型&#xff0c;英文名叫Large Model&#xff0c;也被稱為基礎模型&#xff08;Foundation Model&#xff09;。我們通常說的大模型&#xff0c;主要指的是其中最常用的一類——大語言模型&#xff08;Large Language Model&#xff0c;簡稱LLM&#xff09;。除此之外&#…

LLaMaFactory 微調QwenCoder模型

步驟一&#xff1a;準備LLamaFactory環境 首先,讓我們嘗試使用github的方式克隆倉庫: git config --global http.sslVerify false && git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git # 創建新環境&#xff0c;指定 Python 版本&#xff08;以 3.…

【位運算】判斷字符是否唯?(easy)

33. 判斷字符是否唯?&#xff08;easy&#xff09; 題?描述&#xff1a;解法&#xff08;位圖的思想&#xff09;&#xff1a;C 算法代碼&#xff1a;Java 算法代碼&#xff1a; 題?鏈接&#xff1a;?試題 01.01. 判定字符是否唯? 題?描述&#xff1a; 實現?個算法&…

滿天星之canvas實現【canvas】

展示 文章目錄 展示Canvas 介紹【基礎】簡介兼容性關鍵特性注意事項應用場景&#xff1a;基本示例 滿天星代碼實現【重點】代碼解釋 全量代碼【來吧&#xff0c;盡情復制吧少年】html引入JS代碼 參考資源 Canvas 介紹【基礎】 簡介 Canvas是一個基于HTML5的繪圖技術&#xff0…

可視化提示詞(Prompt)在訓練過程中的優化過程:visualize_prompt_evolution

可視化提示詞(Prompt)在訓練過程中的優化過程:visualize_prompt_evolution 這個函數 visualize_prompt_evolution 的作用是可視化提示詞(Prompt)在訓練過程中的優化過程,通過對比每個訓練輪次(Epoch)的提示詞與初始提示詞的差異,直觀展示哪些Token被保留、哪些被修改…