【React Hooks原理 - forwardRef、useImperativeHandle】

概述

上文我們聊了useRef的使用和實現,主要兩個用途:1、用于持久化保存 2、用于綁定dom。 但是有時候我們需要在父組件中訪問子組件的dom或者屬性/方法,而React中默認是不允許父組件直接訪問子組件的dom的,這時候就可以通過forwardRef將ref傳入子組件,并暴露子組件的dom給父組件使用,但是這種方式直接暴露了子組件的dom,處于安全性能考慮,我們希望子組件只暴露我們所希望的屬性,由子組件自己決定暴露什么,這個就需要使用到useImperativeHandle來處理。 基于這些使用場景,所以本文主要從基本使用和源碼實現兩方面來介紹下forwardRef、useImperativeHandle這兩個API。

基本使用

本小節主要介紹這兩個API在Function Component中的使用,已經熟悉APi的同學可以跳過該部分,直接查看源碼解析部分。

forwardRef

forwardRef主要解決的是從父組件傳遞ref到子組件的問題,定義如下:

const SomeComponent = forwardRef(render)
  • render:組件的渲染函數。React 會調用該函數并傳入父組件傳遞的 props 和 ref。返回的 JSX 將作為組件的輸出。
  • 返回一個可以在 JSX 中渲染的 React 組件。與作為純函數定義的 React 組件不同,forwardRef 返回的組件還能夠接收 ref 屬性。

forwardRef接收一個渲染函數,然后返回一個可在JSX中渲染的組件。一般用于包裹子組件,用于ref傳遞,將子組件綁定的ref通過第二個參數傳入,并綁定到子組件dom節點暴露。可以這樣理解forwardRef 是一個接收render函數作為參數的高階函數

如下demo在子組件中暴露了input組件,使父組件可以訪問并進行例如獲取焦點等dom操作

const MyInput = forwardRef(function MyInput(props, ref) {return (<label>{props.label}<input ref={ref} /></label>);
});

進過forwardRef包裹之后,父組件就可以通過ref來訪問子組件中暴露的dom節點,但是有時候我們希望自己控制暴露哪些屬性,尤其是當作為公共組件被多方調用的時候,這時候就需要通過useImperativeHandle來實現自定義暴露屬性

forwardRef并不是一個Hook,這里主要是作為介紹useImperativeHandle的媒介,通常是將這兩個Api連用,所以這里一起簡單介紹下。所有的Hook都是用use開頭命名的

useImperativeHandle

useImperativeHandle 是 React 中的一個 Hook,它能讓你自定義由 ref 暴露出來的句柄。

useImperativeHandle(ref, createHandle, dependencies?) 
  • ref:該 ref 是你從 forwardRef 渲染函數 中獲得的第二個參數。
  • createHandle:該函數無需參數,它返回你想要暴露的 ref 的句柄。該句柄可以包含任何類型。通常,你會返回一個包含你想暴露的方法的對象。
  • dependencies:可選參數,作為函數 createHandle的依賴收集。React 會使用 Object.is 來比較每一個依賴項與其對應的之前值。如果該數組值發生改變或者數組為空數組,則會重新執行createHandle并將新的對象綁定到ref。

一般是將forwardRef和useImperativeHandle一起用,通過useImperativeHandle暴露指定屬性,然后父組件可以通過forwardRef注入的ref來進行訪問。舉例來說,假設你不想暴露出整個 DOM 節點,但你想要它其中兩個方法:focus 和 scrollIntoView。為此,用單獨額外的 ref 來指向真實的瀏覽器 DOM。然后使用 useImperativeHandle 來暴露一個句柄,它只返回你想要父組件去調用的方法:

import { forwardRef, useRef, useImperativeHandle } from 'react';const MyInput = forwardRef(function MyInput(props, ref) {const inputRef = useRef(null);useImperativeHandle(ref, () => {return {focus() {inputRef.current.focus();},scrollIntoView() {inputRef.current.scrollIntoView();},};}, []);return <input {...props} ref={inputRef} />;
});

在上述代碼中,該 ref 已不再被轉發到<input>中,而是傳入到了useImperativeHandle,可以理解為useImperativeHandle將ref進行了劫持并將暴露的屬性綁定到ref.current上。而且我們也可以自定義返回的數據,比如正常在父組件是通過ref.current訪問,這個可以自定義為ref.xxx

import { forwardRef, useRef, useImperativeHandle } from 'react';const MyInput = forwardRef(function MyInput(props, ref) {const inputRef = useRef(null);useImperativeHandle((createResult) => {ref['xxx'] = createResult;return ref}, () => {return {focus() {inputRef.current.focus();},scrollIntoView() {inputRef.current.scrollIntoView();},};}, []);return <input {...props} ref={inputRef} />;
});

這是由于在源碼實現中對于傳遞對象類型和函數類型的ref處理是不一樣的,詳情可以查看下面源碼解析。

源碼解析

上面我們介紹了這兩個API的基本語法和使用場景,下面我們將從源碼的角度,一步一步分析其內部是如何實現的。

主要涉及文件如下:

  • 代碼入口文件路徑:https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js
  • 執行代碼文件路徑: https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js
  • forwardRef文件路徑: https://github.com/facebook/react/blob/main/packages/react/src/ReactForwardRef.js
  • beginWork函數路徑: https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js

下面的都是基于生產環境下的代碼分析,以及會省略與本次解釋無關的代碼,完整代碼可以根據以上路徑前往官網github查看。

forwardRef

從代碼中能看出,在生產環境下,調用forwardRef包裹render函數之后,會將該render函數打上標簽$$typeof用于React區分當前組件是什么類型并進行不同的處理。

$$typeof: 這個屬性是一個符號常量,用來標識這是一個 forwardRef 組件類型。在 React 內部,這個符號被用來區分不同類型的 React 元素,例如函數組件、類組件、片段(fragment)等。REACT_FORWARD_REF_TYPE 的值是一個獨特的符號,確保了它在 React 內部可以被正確識別。

export function forwardRef<Props, ElementType: React$ElementType>(render: (props: Props, ref: React$Ref<ElementType>) => React$Node
) {const elementType = {$$typeof: REACT_FORWARD_REF_TYPE,render,};return elementType;
}

當完成標記之后,后續會進入到Recondiler協調器中進行fiber構造,其中會經歷beginWork階段對JSX代碼進行處理并生成Fiber節點。在這個階段會根據tag來對不同組件進行處理,這里就是ForwardRef類型

function beginWork(current, workInProgress, renderLanes) {// ...switch (workInProgress.tag) {// ...case ForwardRef:const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =disableDefaultPropsExceptForClasses ||workInProgress.elementType === type? unresolvedProps: resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);return updateForwardRef(current, // 當前頁面顯示的fiber樹workInProgress, // 內存中構建的fiber樹type, // fiber類型,即ForwardRef返回的elementTyperesolvedProps, // 傳遞給組件的屬性集合renderLanes // 優先級);// ...}
}

參數介紹如下:

  • current: 當前頁面顯示的舊的fiber節點
  • workInProgress: 內存中構建的新的fiber節點
  • type: fiber類型,即通過ForwardRef創建的elementType對象,包含$$typeof、render
  • resolvedProps: 傳遞給組件的屬性集合
  • renderLanes: 渲染優先級

調用renderWithHooks處理 hooks 邏輯,并調用實際的 render 函數,傳遞 nextProps 和 ref

function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) {const render = Component.render; // 獲取 render 函數const ref = workInProgress.ref;  // 獲取 reflet nextChildren;// 調用 renderWithHooks 處理 hooks 邏輯并調用 render 函數nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes);workInProgress.flags |= PerformedWork;// 調用 reconcileChildren 處理子節點協調reconcileChildren(current, workInProgress, nextChildren, renderLanes);return workInProgress.child;
}
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {// ...const children = Component(props, secondArg);// ...return children;
}

從以上代碼能看出,forWardRef原理就是: 給傳遞的render函數打上ForWardRef($$typeof)的標簽,讓React知道當前組件的類型,然后在beginWork階段會根據這個類型處理,將render和ref解析處理之后將ref作為render的第二個參數傳入即const children = Component(props, secondArg);

useImperativeHandle

該Hook提供了子組件自定義暴露的屬性方法的能力,同其他Hook一樣,本文也從首次渲染、更新渲染兩個方便來說明其實現。

由于本文分成了Mount、Update兩個部分介紹,所以這里簡單介紹下兩者區別:

  • Mount 階段:在組件的初始掛載(第一次渲染)階段。主要負責初始化 Hook 的狀態和隊列,并建立 Hook 之間的鏈式關系。
  • Update 階段:組件在其 props 或 state 改變后被重新渲染。主要負責處理狀態更新,將新的狀態應用到 memoizedState 中,以便下一次渲染使用,并更新 Hook 鏈表(復用/克隆現有Hook)及其更新隊列updateQueue。
mount首次渲染

雖然我們在使用時只是useImperativeHandle函數,但是在React內部通過dispatcher進行了派發,在mount階段執行的mountImperativeHandle函數

function mountImperativeHandle<T>(ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,create: () => T,deps: Array<mixed> | void | null
): void {// TODO: If deps are provided, should we skip comparing the ref itself?const effectDeps =deps !== null && deps !== undefined ? deps.concat([ref]) : null;let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;mountEffectImpl(fiberFlags,HookLayout,imperativeHandleEffect.bind(null, create, ref),effectDeps);
}

mountImperativeHandle函數作為入口函數,主要就是調用mountEffectImpl創建副作用:

  • 獲取依賴,并將ref(第一個參數)作為依賴添加,以便后面對比判斷是否更新
  • 獲取Flag標識當前副作用
  • 通過bind綁定imperativeHandleEffect,然后調用mountEffectImpl創建副作用

上面知道我們傳入的第二個參數即create函數,在調用時實際執行的imperativeHandleEffect函數來對ref進行處理,其中該函數邏輯如下:


function imperativeHandleEffect<T>(create: () => T,ref: { current: T | null } | ((inst: T | null) => mixed) | null | void
): void | (() => void) {if (typeof ref === "function") {const refCallback = ref;const inst = create(); // 創建實例const refCleanup = refCallback(inst); // 執行 refCallback 并返回結果return () => {if (typeof refCleanup === "function") {refCleanup(); // 如果 refCleanup 是函數,則調用它} else {refCallback(null); // 否則調用 refCallback(null) 清除引用}};} else if (ref !== null && ref !== undefined) {const refObject = ref;const inst = create();refObject.current = inst;return () => {refObject.current = null;};}
}

從代碼能看出來,由于ref可以是對象、或者函數,所以這里進行了差別處理。當為對象時,會將暴露的對象綁定在current中,即可以通過ref.current來訪問暴露的屬性,然后會返回一個清除函數在組件卸載時會調用,來清除引用便于垃圾收回。當ref是函數時,會講create執行結果作為入參傳遞給ref函數,然后自行處理(通過ref.current不能訪問),根據返回的值是否是函數判斷進一步處理清除函數,方便垃圾回收。

mountEffectImpl函數如下:

function mountEffectImpl(fiberFlags: Flags,hookFlags: HookFlags,create: () => (() => void) | void,deps: Array<mixed> | void | null
): void {const hook = mountWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;currentlyRenderingFiber.flags |= fiberFlags;hook.memoizedState = pushEffect(HookHasEffect | hookFlags,create,createEffectInstance(),nextDeps);
}

主要就是通過mountWorkInProgressHook基于當前fiber創建一個初始化hook,然后將依賴和create傳入pushEffect處理副作用列表。

pushEffect函數如下:

function pushEffect(tag: HookFlags,create: () => (() => void) | void,inst: EffectInstance,deps: Array<mixed> | null
): Effect {const effect: Effect = {tag,create,inst,deps,// Circularnext: (null: any),};let componentUpdateQueue: null | FunctionComponentUpdateQueue =(currentlyRenderingFiber.updateQueue: any);// 首次渲染時 為nullif (componentUpdateQueue === null) {componentUpdateQueue = createFunctionComponentUpdateQueue();currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);componentUpdateQueue.lastEffect = effect.next = effect;} else {const lastEffect = componentUpdateQueue.lastEffect;if (lastEffect === null) {componentUpdateQueue.lastEffect = effect.next = effect;} else {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;componentUpdateQueue.lastEffect = effect;}}return effect;
}createFunctionComponentUpdateQueue = () => {return {lastEffect: null,events: null,stores: null,memoCache: null,};
};

主要邏輯就是根據當前配置創建effect副作用,并將其添加到更新隊列updateQueue中。在代碼中通過判斷 currentlyRenderingFiber.updateQueue是否為null來判斷當前是否有其他的更新任務,如果沒有則通過createFunctionComponentUpdateQueue創建初始更新隊列,反之則直接添加到鏈表尾部。

updateQueue更新隊列也是是通過lastEffect指向尾節點的循環鏈表,可以更好的進行插入和快速找到頭節點

至此我們介紹了在mount階段依次調用的函數鏈, mountImperativeHandle - mountEffectImpl - mountWorkInProgressHook - pushEffect 最終初始化構建了從fiber到更新的鏈式關系。其中本次需要更新的狀態保存在updateQueue中,而memoizedState中保存的是上一次渲染更新的狀態,為了方便狀態的追蹤和新狀態的基準值。
在這里插入圖片描述

Update更新渲染

在這里先介紹下函數調用關系,然后再針對該調用鏈以此介紹。通過dispatcher派發之后函數調用如下:updateImperativeHandle - updateEffectImpl - imperativeHandleEffect - updateWorkInProgressHook - pushEffect 其中 imperativeHandleEffectpushEffect在Mount階段已經講過,所以這里就跳過,主要介紹其他函數。

updateImperativeHandle函數

function updateImperativeHandle<T>(ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,create: () => T,deps: Array<mixed> | void | null
): void {// TODO: If deps are provided, should we skip comparing the ref itself?const effectDeps =deps !== null && deps !== undefined ? deps.concat([ref]) : null;updateEffectImpl(UpdateEffect,HookLayout,imperativeHandleEffect.bind(null, create, ref),effectDeps);
}

從代碼能看出該函數主要工作就是調用updateEffectImpl來處理副作用:

  • 獲取deps依賴數組,并將ref(第一個參數)作為依賴添加
  • 綁定imperativeHandleEffect處理ref,并調用updateEffectImpl更新副作用列表

updateEffectImpl函數:

function updateEffectImpl(fiberFlags: Flags,hookFlags: HookFlags,create: () => (() => void) | void,deps: Array<mixed> | void | null
): void {const hook = updateWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;const effect: Effect = hook.memoizedState;const inst = effect.inst;// currentHook is null on initial mount when rerendering after a render phase// state update or for strict mode.// 如果 currentHook 存在,表示這是一個更新操作,否則是一個初始化操作。if (currentHook !== null) {if (nextDeps !== null) {const prevEffect: Effect = currentHook.memoizedState;const prevDeps = prevEffect.deps;if (areHookInputsEqual(nextDeps, prevDeps)) {// 依賴沒有變化,則傳入hookFlags,不需要更新hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);return;}}}// 通過位或運算,更新flag表示當前fiber需要更新currentlyRenderingFiber.flags |= fiberFlags;hook.memoizedState = pushEffect(HookHasEffect | hookFlags,create,inst,nextDeps);
}// 通過for循環遍歷依賴數組,然后通過Object.is判斷是否變化
function areHookInputsEqual(nextDeps: Array<mixed>,prevDeps: Array<mixed> | null,): boolean {if (prevDeps === null) {return false;}// $FlowFixMe[incompatible-use] found when upgrading Flowfor (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {// $FlowFixMe[incompatible-use] found when upgrading Flowif (is(nextDeps[i], prevDeps[i])) {continue;}return false;}return true;}

該函數主要功能就是創建更新任務然后添加到Hook中,并對比deps是否變化來決定是否觸發更新,最后更新memoizedState緩存狀態。

  • 通過updateWorkInProgressHook函數復用Hook并添加更新任務
  • 通過areHookInputsEqual對比依賴變化,通過傳入Flag來判斷是否跳過更新
  • 通過pushEffect添加副作用,并更新memoizedState緩存值

updateWorkInProgressHook函數在前面文章已經介紹過,主要就是復用Hook鏈表,優先復用workInProgress中的Hook,沒有則克隆當前頁面顯示的current Hook 詳情可以查看這篇文章:【React Hooks原理 - useState】

Hook數據結構中和fiber數據結構中都有memoizedState字段,但是表達的意義不同,Hook中是作為緩存的state值,但是fiber中是指向的當前fiber下的hooks隊列的首個hook(hook是鏈表結構,指向首個,就意味著可以訪問整個hooks隊列)

至此Mount階段和Update階段就介紹完了,總的來說就是在Mount階段進行初始化,在Update階段創建更新任務添加到更新列表,等待Scheduler調度更新。

總結

總的來說React默認不允許父組件訪問子組件中的DOM,所以需要通過forwardRef來將ref注入到子組件中,通過在子組件中綁定dom來讓父組件訪問。但是我們又想自定義暴露哪些屬性,所以需要useImperativeHandle這個Hook來幫助完成。

forwardRef的本質就是返回一個帶有特定標識符$$typeof的對象,React根據這個表示知道當前組件是ForWardRef類型,則會在執行函數組件渲染時將ref作為第二個參數傳入即Component(props, ref)

useImperativeHandle可以理解為這個Hook是對forwardRef傳入的ref進行了攔截,根據不同數據類型的ref做了不同處理,對于對象類型,直接將暴露的對象綁定到ref.current中(因為ref是通過useRef創建,默認會帶有current屬性),而函數類型則將暴露的對象作為ref函數的入參由開發者自行控制。所以ref是對象時,父組件可以通過ref.curren訪問,而ref是函數時則需要根據設置訪問,此時ref.current === null

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

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

相關文章

數據庫SQL Server列拼接Join和Union

文章目錄 JOINJOIN的基本語法如下&#xff1a; UNIONUNION的基本語法如下&#xff1a; 在 SQL Server中&#xff0c; JOIN和 UNION是兩種不同的操作&#xff0c;它們用于合并來自兩個或多個表的數據。 JOIN JOIN操作用于將兩個或多個表中的行結合起來&#xff0c;基于它們之…

Jmeter二次開發Demo

Jmeter二次開發Demo 前言 在上一集&#xff0c;我們已經完成了JMX腳本的分析&#xff0c;大致了解了JMX腳本的基本元素。 那么在這一集&#xff0c;我們將會介紹一下Jmeter二次開發的Demo。 Demo代碼 那么話不多說&#xff0c;我們就直接上代碼。 public class TestStress…

SpringBoot+HttpClient實現文件上傳下載

服務端&#xff1a;SpringBoot Controller package com.liliwei.controller;import java.io.File; import java.io.FileInputStream; import java.io.IOException;import javax.servlet.http.HttpServletResponse;import org.springframework.http.HttpHeaders; import org.s…

Cesium 判斷位置是否在當前視口范圍內

詳細步驟都在注釋里,不過多贅述了。 /*** @param {Object} position - Cartesian3坐標* @return {Boolean} 是否在視口中*/ function isPositionInViewport(position) {// 獲取當前視口范圍let viewport = viewer.camera.computeViewRectangle();// 2D模式下拾取不到坐標,vi…

類和對象的簡述(c++篇)

開局之前&#xff0c;先來個小插曲&#xff0c;放松一下&#xff1a; 讓我們的熊二來消滅所有bug 各位&#xff0c;在這祝我們&#xff1a; 放松過后&#xff0c;開始步入正軌吧。愛學習的鐵子們&#xff1a; 目錄&#xff1a; 一類的定義&#xff1a; 1.簡述&#xff1a; 2…

【JavaScript 算法】貪心算法:局部最優解的構建

&#x1f525; 個人主頁&#xff1a;空白詩 文章目錄 一、貪心算法的基本概念貪心算法的適用場景 二、經典問題及其 JavaScript 實現1. 零錢兌換問題2. 活動選擇問題3. 分配問題 三、貪心算法的應用四、總結 貪心算法&#xff08;Greedy Algorithm&#xff09;是一種逐步構建解…

mybatisPlus和mybatis的版本沖突問題、若依換成MP、解決git無法推送、使用若依框架的swagger、以后再遇到團隊項目應該怎么做。

20240716 一. mybatisPlus和mybatis的版本沖突問題1. 使用前的準備2. 我遇到了一個很嚴重的問題。3. 解決問題&#xff0c;好吧也沒解決&#xff0c;發現問題&#xff01;&#xff01; 二、該死的git&#xff01;&#xff01;&#xff01;&#xff01;1. 解決無法在idea中使用g…

【Outlook】從Outlook新版回歸經典版全攻略

引言 在微軟宣布計劃于2024年底淘汰郵件應用&#xff08;Mail app&#xff09;之后&#xff0c;許多用戶發現新版Outlook應用&#xff08;Outlook (new)&#xff09;在他們的Windows 11/10系統上自動啟動。如果您更傾向于使用經典版Outlook&#xff08;Outlook (classic)&…

webpack優化

優化方向 熱更新 概念 /** hmr: hot module replacement 熱模塊替換 / 模塊熱更新作用&#xff1a; 一個模塊發生改變&#xff0c;只會重新打包這一個模塊&#xff08;而不是打包所有模塊&#xff09;&#xff0c;極大的提升了構建速度樣式文件&#xff1a; 可以使用hmr功能…

Facebook:數字時代的社交瑰寶

在當今數字化飛速發展的時代&#xff0c;社交媒體已經成為人們日常生活中不可或缺的一部分&#xff0c;而Facebook作為其中的領軍者&#xff0c;不僅連接了全球數十億的用戶&#xff0c;更深刻地改變了人們的社交方式和生活方式。本文將探討Facebook如何成為數字時代的社交瑰寶…

python如何創建SQLite 數據庫連接,如何將數據庫存儲在內存中?

嗨&#xff0c;大家好&#xff0c;我是蘭若姐姐。今天給大家說下如何創建SQLite 數據庫連接,并將數據庫存儲在內存中,這是一種臨時的、私有的數據存儲空間&#xff0c;一般用于以下情形&#xff1a; 什么都不說&#xff0c;先上代碼&#xff1a; import sqlite3創建數據庫連接…

再談有關JVM中的四種引用

1.強引用 強引用就是我們平時使用最多的那種引用&#xff0c;就比如以下的代碼 //創建一個對象 Object obj new Object();//強引用 這個例子就是創建了一個對象并建立了強引用&#xff0c;強引用一般就是默認支持的當內存不足的時候&#xff0c;JVM開始垃圾回收&#xff0c…

防火墻的冗余基礎知識+實驗檢測

將之前先理清需要注意的知識點&#xff1a; 1、注意防火墻冗余時的會話表必須保持一致&#xff0c;這里HRP技術已經做到 2、vrrp是自動開啟搶占的&#xff0c;且是根據優先級進行搶占的 3、免費ARP的作用&#xff1a;告訴交換機的某個IP的mac地址變成了我的這個mac地址 4、HRP …

C++ | Leetcode C++題解之第231題2的冪

題目&#xff1a; 題解&#xff1a; class Solution { private:static constexpr int BIG 1 << 30;public:bool isPowerOfTwo(int n) {return n > 0 && BIG % n 0;} };

強化學習——多臂老虎機問題(MAB)【附python代碼】

文章目錄 一、問題描述1.1 問題定義1.2 形式化描述1.3 累積懊悔1.4 估計期望獎勵 二、解決方法2.1 ?-貪婪算法2.2 上置信界算法2.3 湯普森采樣算法2.4 小結 一、問題描述 1.1 問題定義 有一個用于 K 根拉桿的老虎機&#xff0c;每一根拉桿都對應一個關于獎勵的概率分布 R 。每…

【C++題解】1154. 數組元素的查找

問題&#xff1a;1154. 數組元素的查找 類型&#xff1a;數組找數 題目描述&#xff1a; 給你 m 個整數&#xff0c;查找其中有無值為 n 的數&#xff0c;有則輸出該數第一次出現的位置,沒有則輸出 ?1 。 輸入&#xff1a; 第一行一個整數 m 代表數的個數 ( 0≤m≤100 ) 。…

Qt基礎 | Qt全局定義 | qglobal頭文件中的數據類型、函數、宏定義

文章目錄 一、數據類型定義二、函數三、宏定義 QtGlobal頭文件包含了 Qt 類庫的一些全局定義 &#xff0c;包括基本數據類型、函數和宏&#xff0c;一般的Qt類的頭文件都會包含該文件。 詳細內容可參考&#xff1a;https://doc.qt.io/qt-5/qtglobal.html 一、數據類型定義 為了…

數據可視化在智慧醫療中的重要應用

在現代智慧醫療的推動下&#xff0c;數據可視化技術正日益成為醫療領域的重要工具。通過將復雜的醫療數據轉換為直觀的圖表和圖形&#xff0c;數據可視化不僅提升了醫療服務的效率&#xff0c;還極大地改善了患者的就醫體驗。 在智慧醫療中&#xff0c;數據可視化首先在電子病歷…

客流統計系統優化景區服務流程,增強游客滿意度

在當今旅游業蓬勃發展的時代&#xff0c;景區面臨著越來越多的挑戰和機遇。如何提供更優質、更高效的服務&#xff0c;滿足游客日益增長的需求&#xff0c;成為了景區管理者們關注的焦點。客流統計系統作為一種創新的技術手段&#xff0c;正逐漸成為優化景區服務流程、增強游客…

MySQL主從同步的原理與思考

摘要 分析主從同步出現的原因&#xff0c;MySQL實現主從同步的原理&#xff0c;思考實現原理的局限性和優點 背景 在實際應用中主從同步常用于實現備份、負載均衡和高可用。數據冗余的目的是提高數據的安全性&#xff0c;避免因磁盤損壞導致數據丟失的問題。讀寫分離的目的是…