<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [toc] ## React 面試專題 ### React.js是 MVVM 框架嗎? React就是Facebook的一個開源JS框架,專注的層面為View層,不包括數據訪問層或者那種Hash路由(不過React 有插件支持),與Angularjs,Emberjs等大而全的框架不同,React專注的中心是Component,即組件。React認為一切頁面元 素都可以抽象成組件,比如一個表單,或者表單中的某一項。 React可以作為MVVM中第二個V,也就是View,但是并不是MVVM框架。MVVM一個最顯著的特征:雙向綁定。React沒有這個,它是單向數據綁定的。React是一個單向數據流的庫,狀態驅動視圖。react整體是函數式的思想,把組件設計成純組件,狀態和邏輯通過參數傳入,所以在react中,是單向數據流,推崇結合immutable來實現數據不可變。 ### hooks用過嗎?聊聊react中class組件和函數組件的區別 類組件是使用ES6 的 class?來定義的組件。 函數組件是接收一個單一的?`props`?對象并返回一個React元素。 關于React的兩套API(類(class)API 和基于函數的鉤子(hooks) API)。官方推薦使用鉤子(函數),而不是類。因為鉤子更簡潔,代碼量少,用起來比較"輕",而類比較"重"。而且,鉤子是函數,更符合 React 函數式的本質。 函數一般來說,只應該做一件事,就是返回一個值。 如果你有多個操作,每個操作應該寫成一個單獨的函數。而且,數據的狀態應該與操作方法分離。根據函數這種理念,React 的函數組件只應該做一件事情:返回組件的 HTML 代碼,而沒有其他的功能。函數的返回結果只依賴于它的參數。不改變函數體外部數據、函數執行過程里面沒有副作用。 類(class)是數據和邏輯的封裝。 也就是說,組件的狀態和操作方法是封裝在一起的。如果選擇了類的寫法,就應該把相關的數據和操作,都寫在同一個 class 里面。 **類組件的缺點** : 大型組件很難拆分和重構,也很難測試。\ 業務邏輯分散在組件的各個方法之中,導致重復邏輯或關聯邏輯。\ 組件類引入了復雜的編程模式,比如 render props 和高階組件。\ 難以理解的 class,理解 JavaScript 中?`this`?的工作方式。 **區別**: 函數組件的性能比類組件的性能要高,因為類組件使用的時候要實例化,而函數組件直接執行函數取返回結果即可。 1.狀態的有無\ hooks出現之前,函數組件`沒有實例`,`沒有生命周期`,`沒有state`,`沒有this`,所以我們稱函數組件為無狀態組件。 hooks出現之前,react中的函數組件通常只考慮負責UI的渲染,沒有自身的狀態沒有業務邏輯代碼,是一個純函數。它的輸出只由參數props決定,不受其他任何因素影響。 2.調用方式的不同\ 函數組件重新渲染,將重新調用組件方法返回新的react元素。類組件重新渲染將new一個新的組件實例,然后調用render類方法返回react元素,這也說明為什么類組件中this是可變的。 3.因為調用方式不同,在函數組件使用中會出現問題\ 在操作中改變狀態值,類組件可以獲取最新的狀態值,而函數組件則會按照順序返回狀態值 **React Hooks(鉤子的作用)** *Hook*?是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。 React Hooks的幾個常用鉤子: 1. `useState()` //狀態鉤子 1. `useContext()` //共享狀態鉤子 1. `useReducer()` //action 鉤子 1. `useEffect()` //副作用鉤子 還有幾個不常見的大概的說下,后續會專門寫篇文章描述下 - 1.useCallback 記憶函數 一般把**函數式組件理解為class組件render函數的語法糖**,所以每次重新渲染的時候,函數式組件內部所有的代碼都會重新執行一遍。而有了 useCallback 就不一樣了,你可以通過 useCallback 獲得一個記憶后的函數。 ```js function App() { const memoizedHandleClick = useCallback(() => { console.log('Click happened') }, []); // 空數組代表無論什么情況下該函數都不會發生改變 return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>; } ``` 第二個參數傳入一個數組,數組中的每一項一旦值或者引用發生改變,useCallback 就會重新返回一個新的記憶函數提供給后面進行渲染。 - 2.useMemo 記憶組件 useCallback 的功能完全可以由 useMemo 所取代,如果你想通過使用 useMemo 返回一個記憶函數也是完全可以的。 唯一的區別是:**useCallback 不會執行第一個參數函數,而是將它返回給你,而 useMemo 會執行第一個函數并且將函數執行結果返回給你**。\ 所以 useCallback 常用記憶事件函數,生成記憶后的事件函數并傳遞給子組件使用。而 useMemo 更適合經過函數計算得到一個確定的值,比如記憶組件。 - 3.useRef 保存引用值 useRef 跟 createRef 類似,都可以用來生成對 DOM 對象的引用。useRef 返回的值傳遞給組件或者 DOM 的 ref 屬性,就可以通過 ref.current 值**訪問組件或真實的 DOM 節點,重點是組件也是可以訪問到的**,從而可以對 DOM 進行一些操作,比如監聽事件等等。 - 4.useImperativeHandle 穿透 Ref 通過 useImperativeHandle 用于讓父組件獲取子組件內的索引 - 5.useLayoutEffect 同步執行副作用 大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調用一些副作用,比如對 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用會在 DOM 更新之后同步執行。 **useEffect和useLayoutEffect有什么區別**:簡單來說就是調用時機不同,useLayoutEffect和原來componentDidMount&componentDidUpdate一致,在react完成DOM更新后馬上同步調用的代碼,會阻塞頁面渲染。而useEffect是會在整個頁面渲染完才會調用的代碼。`官方建議優先使用useEffect` ### React 組件通信方式 react組件間通信常見的幾種情況: - 1. 父組件向子組件通信 - 2. 子組件向父組件通信 - 3. 跨級組件通信 - 4. 非嵌套關系的組件通信 #### 1)父組件向子組件通信 父組件通過 props 向子組件傳遞需要的信息。父傳子是在父組件中直接綁定一個正常的屬性,這個屬性就是指具體的值,在子組件中,用props就可以獲取到這個值 ```js // 子組件: Child const Child = props =>{ return <p>{props.name}</p> } // 父組件 Parent const Parent = ()=>{ return <Child name="京程一燈"></Child> } ``` #### 2)子組件向父組件通信 props+回調的方式,使用公共組件進行狀態提升。子傳父是先在父組件上綁定屬性設置為一個函數,當子組件需要給父組件傳值的時候,則通過props調用該函數將參數傳入到該函數當中,此時就可以在父組件中的函數中接收到該參數了,這個參數則為子組件傳過來的值 ```js // 子組件: Child const Child = props =>{ const cb = msg =>{ return ()=>{ props.callback(msg) } } return ( <button onClick={cb("京程一燈歡迎你!")}>京程一燈歡迎你</button> ) } // 父組件 Parent class Parent extends Component { callback(msg){ console.log(msg) } render(){ return <Child callback={this.callback.bind(this)}></Child> } } ``` #### 3)跨級組件通信 即父組件向子組件的子組件通信,向更深層子組件通信。 - 使用props,利用中間組件層層傳遞,但是如果父組件結構較深,那么中間每一層組件都要去傳遞props,增加了復雜度,并且這些props并不是中間組件自己需要的。 - 使用context,context相當于一個大容器,我們可以把要通信的內容放在這個容器中,這樣不管嵌套多深,都可以隨意取用,對于跨越多層的全局數據可以使用context實現。 ```js // context方式實現跨級組件通信 // Context 設計目的是為了共享那些對于一個組件樹而言是“全局”的數據 const BatteryContext = createContext(); // 子組件的子組件 class GrandChild extends Component { render(){ return ( <BatteryContext.Consumer> { color => <h1 style={{"color":color}}>我是紅色的:{color}</h1> } </BatteryContext.Consumer> ) } } // 子組件 const Child = () =>{ return ( <GrandChild/> ) } // 父組件 class Parent extends Component { state = { color:"red" } render(){ const {color} = this.state return ( <BatteryContext.Provider value={color}> <Child></Child> </BatteryContext.Provider> ) } } ``` #### 4)非嵌套關系的組件通信 即沒有任何包含關系的組件,包括兄弟組件以及不在同一個父級中的非兄弟組件。 - 1. 可以使用自定義事件通信(發布訂閱模式),使用pubsub-js - 2. 可以通過redux等進行全局狀態管理 - 3. 如果是兄弟組件通信,可以找到這兩個兄弟節點共同的父節點, 結合父子間通信方式進行通信。 - 4. 也可以new一個 Vue 的 EventBus,進行事件監聽,一邊執行監聽,一邊執行新增 VUE的eventBus 就是發布訂閱模式,是可以在React中使用的; ### setState 既存在異步情況也存在同步情況 1.異步情況 在`React事件當中是異步操作` 2.同步情況 如果是在`setTimeout事件或者自定義的dom事件`中,都是同步的 ```js //setTimeout事件 import React,{ Component } from "react"; class Count extends Component{ constructor(props){ super(props); this.state = { count:0 } } render(){ return ( <> <p>count:{this.state.count}</p> <button onClick={this.btnAction}>增加</button> </> ) } btnAction = ()=>{ //不能直接修改state,需要通過setState進行修改 //同步 setTimeout(()=>{ this.setState({ count: this.state.count + 1 }); console.log(this.state.count); }) } } export default Count; ``` ```js //自定義dom事件 import React,{ Component } from "react"; class Count extends Component{ constructor(props){ super(props); this.state = { count:0 } } render(){ return ( <> <p>count:{this.state.count}</p> <button id="btn">綁定點擊事件</button> </> ) } componentDidMount(){ //自定義dom事件,也是同步修改 document.querySelector('#btn').addEventListener('click',()=>{ this.setState({ count: this.state.count + 1 }); console.log(this.state.count); }); } } export default Count; ``` ### 生命周期 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8bae01e6eb804d849e5bb889f787707d~tplv-k3u1fbpfcp-zoom-1.image) ```js 安裝 當組件的實例被創建并插入到 DOM 中時,這些方法按以下順序調用: constructor() static getDerivedStateFromProps() render() componentDidMount() 更新中 更新可能由道具或狀態的更改引起。當重新渲染組件時,這些方法按以下順序調用: static getDerivedStateFromProps() shouldComponentUpdate() render() getSnapshotBeforeUpdate() componentDidUpdate() 卸載 當組件從 DOM 中移除時調用此方法: componentWillUnmount() ``` ### 說一下 react-fiber #### 1)背景 react-fiber 產生的根本原因,是`大量的同步計算任務阻塞了瀏覽器的 UI 渲染`。默認情況下,JS 運算、頁面布局和頁面繪制都是運行在瀏覽器的主線程當中,他們之間是互斥的關系。如果 JS 運算持續占用主線程,頁面就沒法得到及時的更新。當我們調用`setState`更新頁面的時候,React 會遍歷應用的所有節點,計算出差異,然后再更新 UI。如果頁面元素很多,整個過程占用的時機就可能超過 16 毫秒,就容易出現掉幀的現象。 #### 2)實現原理 - react內部運轉分三層: - Virtual DOM 層,描述頁面長什么樣。 - Reconciler 層,負責調用組件生命周期方法,進行 Diff 運算等。 - Renderer 層,根據不同的平臺,渲染出相應的頁面,比較常見的是 ReactDOM 和 ReactNative。 `Fiber 其實指的是一種數據結構,它可以用一個純 JS 對象來表示`: ```js const fiber = { stateNode, // 節點實例 child, // 子節點 sibling, // 兄弟節點 return, // 父節點 } ``` - 為了實現不卡頓,就需要有一個調度器 (Scheduler) 來進行任務分配。優先級高的任務(如鍵盤輸入)可以打斷優先級低的任務(如Diff)的執行,從而更快的生效。任務的優先級有六種: - synchronous,與之前的Stack Reconciler操作一樣,同步執行 - task,在next tick之前執行 - animation,下一幀之前執行 - high,在不久的將來立即執行 - low,稍微延遲執行也沒關系 - offscreen,下一次render時或scroll時才執行 - Fiber Reconciler(react )執行過程分為2個階段: - 階段一,生成 Fiber 樹,得出需要更新的節點信息。這一步是一個漸進的過程,可以被打斷。階段一可被打斷的特性,讓優先級更高的任務先執行,從框架層面大大降低了頁面掉幀的概率。 - 階段二,將需要更新的節點一次過批量更新,這個過程不能被打斷。 - Fiber樹:React 在 render 第一次渲染時,會通過 React.createElement 創建一顆 Element 樹,可以稱之為 Virtual DOM Tree,由于要記錄上下文信息,加入了 Fiber,每一個 Element 會對應一個 Fiber Node,將 Fiber Node 鏈接起來的結構成為 Fiber Tree。Fiber Tree 一個重要的特點是鏈表結構,將遞歸遍歷編程循環遍歷,然后配合 requestIdleCallback API, 實現任務拆分、中斷與恢復。 從Stack Reconciler到Fiber Reconciler,源碼層面其實就是干了一件遞歸改循環的事情 傳送門 ?[# 深入了解 Fiber](https://juejin.cn/post/7002250258826657799) ### Portals Portals 提供了一種一流的方式來將子組件渲染到存在于父組件的 DOM 層次結構之外的 DOM 節點中。結構不受外界的控制的情況下就可以使用portals進行創建 ### 何時要使用異步組件?如和使用異步組件 - 加載大組件的時候 - 路由異步加載的時候 react 中要配合 Suspense 使用 ```js // 異步懶加載 const Box = lazy(()=>import('./components/Box')); // 使用組件的時候要用suspense進行包裹 <Suspense fallback={<div>loading...</div>}> {show && <Box/>} </Suspense> ``` ### React 事件綁定原理 React并不是將click事件綁在該div的真實DOM上,而是`在document處監聽所有支持的事件`,當事件發生并冒泡至document處時,React將事件內容封裝并交由真正的處理函數運行。這樣的方式不僅減少了內存消耗,還能在組件掛載銷毀時統一訂閱和移除事件。\ 另外冒泡到 document 上的事件也不是原生瀏覽器事件,而是 React 自己實現的合成事件(SyntheticEvent)。因此我們如果不想要事件冒泡的話,調用 event.stopPropagation 是無效的,而應該調用 `event.preventDefault`。 ![react事件綁定原理](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2089718f74b342869de15f01588f033f~tplv-k3u1fbpfcp-zoom-1.image) ### React.lazy() 實現的原理 React的懶加載示例: ```js import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); } ``` **React.lazy 原理** 以下 React 源碼基于 16.8.0 版本 React.lazy 的源碼實現如下: export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> { let lazyType = { $$typeof: REACT_LAZY_TYPE, _ctor: ctor, // React uses these fields to store the result. _status: -1, _result: null, }; return lazyType; } 可以看到其返回了一個 LazyComponent 對象。 而對于 LazyComponent 對象的解析: ```js case LazyComponent: { const elementType = workInProgress.elementType; return mountLazyComponent( current, workInProgress, elementType, updateExpirationTime, renderExpirationTime, ); } ``` ```js function mountLazyComponent( _current, workInProgress, elementType, updateExpirationTime, renderExpirationTime, ) { ... let Component = readLazyComponentType(elementType); ... } ``` ```js // Pending = 0, Resolved = 1, Rejected = 2 export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T { const status = lazyComponent._status; const result = lazyComponent._result; switch (status) { case Resolved: { const Component: T = result; return Component; } case Rejected: { const error: mixed = result; throw error; } case Pending: { const thenable: Thenable<T, mixed> = result; throw thenable; } default: { // lazyComponent 首次被渲染 lazyComponent._status = Pending; const ctor = lazyComponent._ctor; const thenable = ctor(); thenable.then( moduleObject => { if (lazyComponent._status === Pending) { const defaultExport = moduleObject.default; lazyComponent._status = Resolved; lazyComponent._result = defaultExport; } }, error => { if (lazyComponent._status === Pending) { lazyComponent._status = Rejected; lazyComponent._result = error; } }, ); // Handle synchronous thenables. switch (lazyComponent._status) { case Resolved: return lazyComponent._result; case Rejected: throw lazyComponent._result; } lazyComponent._result = thenable; throw thenable; } } } ``` 注:如果 readLazyComponentType 函數多次處理同一個 lazyComponent,則可能進入Pending、Rejected等 case 中。 從上述代碼中可以看出,對于最初 React.lazy() 所返回的 LazyComponent 對象,其 _status 默認是 -1,所以首次渲染時,會進入 readLazyComponentType 函數中的 default 的邏輯,這里才會真正異步執行 import(url)操作,由于并未等待,隨后會檢查模塊是否 Resolved,如果已經Resolved了(已經加載完畢)則直接返回moduleObject.default(動態加載的模塊的默認導出),否則將通過 throw 將 thenable 拋出到上層。 為什么要 throw 它?這就要涉及到 Suspense 的工作原理,我們接著往下分析。 **Suspense 原理** 由于 React 捕獲異常并處理的代碼邏輯比較多,這里就不貼源碼,感興趣可以去看 throwException 中的邏輯,其中就包含了如何處理捕獲的異常。簡單描述一下處理過程,React 捕獲到異常之后,會判斷異常是不是一個 thenable,如果是則會找到 SuspenseComponent ,如果 thenable 處于 pending 狀態,則會將其 children 都渲染成 fallback 的值,一旦 thenable 被 resolve 則 SuspenseComponent 的子組件會重新渲染一次。 為了便于理解,我們也可以用 componentDidCatch 實現一個自己的 Suspense 組件,如下: ```js class Suspense extends React.Component { state = { promise: null } componentDidCatch(err) { // 判斷 err 是否是 thenable if (err !== null && typeof err === 'object' && typeof err.then === 'function') { this.setState({ promise: err }, () => { err.then(() => { this.setState({ promise: null }) }) }) } } render() { const { fallback, children } = this.props const { promise } = this.state return <>{ promise ? fallback : children }</> } } ``` 至此,我們分析完了 React 的懶加載原理。簡單來說,React利用 React.lazy與import()實現了渲染時的動態加載 ,并利用Suspense來處理異步加載資源時頁面應該如何顯示的問題。 參考傳送門? [React Lazy 的實現原理](https://thoamsy.github.io/blogs/react-lazy/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看