1、setState 是異步還是同步?
合成事件中是異步
鉤子函數(shù)中的是異步
原生事件中是同步
setTimeout中是同步
相關(guān)鏈接:你真的理解setState嗎?:
2、聊聊 react@16.4 + 的生命周期
圖片
相關(guān)連接:React 生命周期 我對(duì) React v16.4 生命周期的理解
3、useEffect(fn, []) 和 componentDidMount 有什么差異?
useEffect 會(huì)捕獲 props 和 state。所以即便在回調(diào)函數(shù)里,你拿到的還是初始的 props 和 state。如果想得到“最新”的值,可以使用ref。
4、hooks 為什么不能放在條件判斷里?
以 setState 為例,在 react 內(nèi)部,每個(gè)組件(Fiber)的 hooks 都是以鏈表的形式存在 memoizeState 屬性中:
圖片
update 階段,每次調(diào)用 setState,鏈表就會(huì)執(zhí)行 next 向后移動(dòng)一步。如果將 setState 寫在條件判斷中,假設(shè)條件判斷不成立,沒有執(zhí)行里面的 setState 方法,會(huì)導(dǎo)致接下來所有的 setState 的取值出現(xiàn)偏移,從而導(dǎo)致異常發(fā)生。
參考鏈接:烤透 React Hook
5、fiber 是什么?
React Fiber 是一種基于瀏覽器的單線程調(diào)度算法。
React Fiber 用類似 requestIdleCallback 的機(jī)制來做異步 diff。但是之前數(shù)據(jù)結(jié)構(gòu)不支持這樣的實(shí)現(xiàn)異步 diff,于是 React 實(shí)現(xiàn)了一個(gè)類似鏈表的數(shù)據(jù)結(jié)構(gòu),將原來的 遞歸diff 變成了現(xiàn)在的 遍歷diff,這樣就能做到異步可更新了。
圖片
相關(guān)鏈接:React Fiber 是什么?
6、聊一聊 diff 算法
傳統(tǒng) diff 算法的時(shí)間復(fù)雜度是 O(n^3),這在前端 render 中是不可接受的。為了降低時(shí)間復(fù)雜度,react 的 diff 算法做了一些妥協(xié),放棄了最優(yōu)解,最終將時(shí)間復(fù)雜度降低到了 O(n)。
那么 react diff 算法做了哪些妥協(xié)呢?,參考如下:
1、tree diff:只對(duì)比同一層的 dom 節(jié)點(diǎn),忽略 dom 節(jié)點(diǎn)的跨層級(jí)移動(dòng)
如下圖,react 只會(huì)對(duì)相同顏色方框內(nèi)的 DOM 節(jié)點(diǎn)進(jìn)行比較,即同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)不存在時(shí),則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。
這樣只需要對(duì)樹進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹的比較。
圖片
這就意味著,如果 dom 節(jié)點(diǎn)發(fā)生了跨層級(jí)移動(dòng),react 會(huì)刪除舊的節(jié)點(diǎn),生成新的節(jié)點(diǎn),而不會(huì)復(fù)用。
2、component diff:如果不是同一類型的組件,會(huì)刪除舊的組件,創(chuàng)建新的組件
圖片
3、element diff:對(duì)于同一層級(jí)的一組子節(jié)點(diǎn),需要通過唯一 id 進(jìn)行來區(qū)分
如果沒有 id 來進(jìn)行區(qū)分,一旦有插入動(dòng)作,會(huì)導(dǎo)致插入位置之后的列表全部重新渲染。
這也是為什么渲染列表時(shí)為什么要使用唯一的 key。
7、調(diào)用 setState 之后發(fā)生了什么?
在 setState 的時(shí)候,React 會(huì)為當(dāng)前節(jié)點(diǎn)創(chuàng)建一個(gè) updateQueue 的更新列隊(duì)。
然后會(huì)觸發(fā) reconciliation 過程,在這個(gè)過程中,會(huì)使用名為 Fiber 的調(diào)度算法,開始生成新的 Fiber 樹, Fiber 算法的最大特點(diǎn)是可以做到異步可中斷的執(zhí)行。
然后 React Scheduler 會(huì)根據(jù)優(yōu)先級(jí)高低,先執(zhí)行優(yōu)先級(jí)高的節(jié)點(diǎn),具體是執(zhí)行 doWork 方法。
在 doWork 方法中,React 會(huì)執(zhí)行一遍 updateQueue 中的方法,以獲得新的節(jié)點(diǎn)。然后對(duì)比新舊節(jié)點(diǎn),為老節(jié)點(diǎn)打上 更新、插入、替換 等 Tag。
當(dāng)前節(jié)點(diǎn) doWork 完成后,會(huì)執(zhí)行 performUnitOfWork 方法獲得新節(jié)點(diǎn),然后再重復(fù)上面的過程。
當(dāng)所有節(jié)點(diǎn)都 doWork 完成后,會(huì)觸發(fā) commitRoot 方法,React 進(jìn)入 commit 階段。
在 commit 階段中,React 會(huì)根據(jù)前面為各個(gè)節(jié)點(diǎn)打的 Tag,一次性更新整個(gè) dom 元素。
8、為什么虛擬dom 會(huì)提高性能?
虛擬dom 相當(dāng)于在 JS 和真實(shí) dom 中間加了一個(gè)緩存,利用 diff 算法避免了沒有必要的 dom 操作,從而提高性能。
9、錯(cuò)誤邊界是什么?它有什么用?
在 React 中,如果任何一個(gè)組件發(fā)生錯(cuò)誤,它將破壞整個(gè)組件樹,導(dǎo)致整頁(yè)白屏。這時(shí)候我們可以用錯(cuò)誤邊界優(yōu)雅地降級(jí)處理這些錯(cuò)誤。
例如下面封裝的組件:
class ErrorBoundary extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
// 更新 state 使下一次渲染能夠顯示降級(jí)后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 可以將錯(cuò)誤日志上報(bào)給服務(wù)器
console.log('組件奔潰 Error', error);
console.log('組件奔潰 Info', errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定義降級(jí)后的 UI 并渲染
return this.props.content;
}
return this.props.children;
}
}
10、什么是 Portals?
Portal 提供了一種將子節(jié)點(diǎn)渲染到存在于父組件以外的 DOM 節(jié)點(diǎn)的優(yōu)秀的方案。
ReactDOM.createPortal(child, container)
11、React 組件間有那些通信方式?
父組件向子組件通信
1、 通過 props 傳遞
子組件向父組件通信
1、 主動(dòng)調(diào)用通過 props 傳過來的方法,并將想要傳遞的信息,作為參數(shù),傳遞到父組件的作用域中
跨層級(jí)通信
1、 使用 react 自帶的 Context 進(jìn)行通信,createContext 創(chuàng)建上下文, useContext 使用上下文。
參考下面代碼:
import React, { createContext, useContext } from 'react';
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
export default App;
2、使用 Redux 或者 Mobx 等狀態(tài)管理庫(kù)
3、使用訂閱發(fā)布模式
12、React 父組件如何調(diào)用子組件中的方法?
1、如果是在方法組件中調(diào)用子組件(>= react@16.8),可以使用 useRef 和 useImperativeHandle:
const { forwardRef, useRef, useImperativeHandle } = React;
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
getAlert() {
alert("getAlert from Child");
}
}));
return <h1>Hi</h1>;
});
const Parent = () => {
const childRef = useRef();
return (
<div>
<Child ref={childRef} />
<button onClick={() => childRef.current.getAlert()}>Click</button>
</div>
);
};
2、如果是在類組件中調(diào)用子組件(>= react@16.4),可以使用 createRef:
const { Component } = React;
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
return <h1>Hello</h1>;
}
}
13、React有哪些優(yōu)化性能的手段?
類組件中的優(yōu)化手段
1、使用純組件 PureComponent 作為基類。
2、使用 React.memo 高階函數(shù)包裝組件。
3、使用 shouldComponentUpdate 生命周期函數(shù)來自定義渲染邏輯。
方法組件中的優(yōu)化手段
1、使用 useMemo。
2、使用 useCallBack。
其他方式
1、在列表需要頻繁變動(dòng)時(shí),使用唯一 id 作為 key,而不是數(shù)組下標(biāo)。
2、必要時(shí)通過改變 CSS 樣式隱藏顯示組件,而不是通過條件判斷顯示隱藏組件。
3、使用 Suspense 和 lazy 進(jìn)行懶加載,例如:
import React, { lazy, Suspense } from "react";
export default class CallingLazyComponents extends React.Component {
render() {
var ComponentToLazyLoad = null;
if (this.props.name == "Mayank") {
ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
} else if (this.props.name == "Anshul") {
ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
}
return (
<div>
<h1>This is the Base User: {this.state.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<ComponentToLazyLoad />
</Suspense>
</div>
)
}
}
Suspense 用法可以參考官方文檔
相關(guān)閱讀:21個(gè)React性能優(yōu)化技巧
14、為什么 React 元素有一個(gè) $$typeof 屬性?
目的是為了防止 XSS 攻擊。因?yàn)?Synbol 無(wú)法被序列化,所以 React 可以通過有沒有 $$typeof 屬性來斷出當(dāng)前的 element 對(duì)象是從數(shù)據(jù)庫(kù)來的還是自己生成的。
如果沒有 $$typeof 這個(gè)屬性,react 會(huì)拒絕處理該元素。
在 React 的古老版本中,下面的寫法會(huì)出現(xiàn) XSS 攻擊:
// 服務(wù)端允許用戶存儲(chǔ) JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* 把你想的擱著 */'
},
},
// ...
};
let message = { text: expectedTextButGotJSON };
// React 0.13 中有風(fēng)險(xiǎn)
<p>
{message.text}
</p>
15、React 如何區(qū)分 Class組件 和 Function組件?
一般的方式是借助 typeof 和 Function.prototype.toString 來判斷當(dāng)前是不是 class,如下:
function isClass(func) {
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}
但是這個(gè)方式有它的局限性,因?yàn)槿绻昧?babel 等轉(zhuǎn)換工具,將 class 寫法全部轉(zhuǎn)為 function 寫法,上面的判斷就會(huì)失效。
React 區(qū)分 Class組件 和 Function組件的方式很巧妙,由于所有的類組件都要繼承 React.Component,所以只要判斷原型鏈上是否有 React.Component 就可以了:
AComponent.prototype instanceof React.Component
16、HTML 和 React 事件處理有什么區(qū)別?
在 HTML 中事件名必須小寫:
<button onclick='activateLasers()'>
而在 React 中需要遵循駝峰寫法:
<button onClick={activateLasers}>
在 HTML 中可以返回 false 以阻止默認(rèn)的行為:
<a href='#' onclick='console.log("The link was clicked."); return false;' />
而在 React 中必須地明確地調(diào)用 preventDefault():
function handleClick(event) {
event.preventDefault()
console.log('The link was clicked.')
}
17、什么是 suspense 組件?
Suspense 讓組件“等待”某個(gè)異步操作,直到該異步操作結(jié)束即可渲染。在下面例子中,兩個(gè)組件都會(huì)等待異步 API 的返回值:
const resource = fetchProfileData();
function ProfilePage() {
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}
function ProfileDetails() {
// 嘗試讀取用戶信息,盡管該數(shù)據(jù)可能尚未加載
const user = resource.user.read();
return <h1>{user.name}</h1>;
}
function ProfileTimeline() {
// 嘗試讀取博文信息,盡管該部分?jǐn)?shù)據(jù)可能尚未加載
const posts = resource.posts.read();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
);
}
Suspense 也可以用于懶加載,參考下面的代碼:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
18、為什么 JSX 中的組件名要以大寫字母開頭?
因?yàn)?React 要知道當(dāng)前渲染的是組件還是 HTML 元素。
19、redux 是什么?
Redux 是一個(gè)為 JavaScript 應(yīng)用設(shè)計(jì)的,可預(yù)測(cè)的狀態(tài)容器。
它解決了如下問題:
跨層級(jí)組件之間的數(shù)據(jù)傳遞變得很容易
所有對(duì)狀態(tài)的改變都需要 dispatch,使得整個(gè)數(shù)據(jù)的改變可追蹤,方便排查問題。
但是它也有缺點(diǎn):
概念偏多,理解起來不容易
樣板代碼太多
20、react-redux 的實(shí)現(xiàn)原理?
通過 redux 和 react context 配合使用,并借助高階函數(shù),實(shí)現(xiàn)了 react-redux。
參考鏈接:React.js 小書
21、reudx 和 mobx 的區(qū)別?
得益于 Mobx 的 observable,使用 mobx 可以做到精準(zhǔn)更新;對(duì)應(yīng)的 Redux 是用 dispath 進(jìn)行廣播,通過Provider 和 connect 來比對(duì)前后差別控制更新粒度;
相關(guān)閱讀:Redux or MobX: An attempt to dissolve the Confusion
22、redux 異步中間件有什么什么作用?
假如有這樣一個(gè)需求:請(qǐng)求數(shù)據(jù)前要向 Store dispatch 一個(gè) loading 狀態(tài),并帶上一些信息;請(qǐng)求結(jié)束后再向Store dispatch 一個(gè) loaded 狀態(tài)
一些同學(xué)可能會(huì)這樣做:
function App() {
const onClick = () => {
dispatch({ type: 'LOADING', message: 'data is loading' })
fetch('dataurl').then(() => {
dispatch({ type: 'LOADED' })
});
}
return (<div>
<button onClick={onClick}>click</button>
</div>);
}
但是如果有非常多的地方用到這塊邏輯,那應(yīng)該怎么辦?
聰明的同學(xué)會(huì)想到可以將 onClick 里的邏輯抽象出來復(fù)用,如下:
function fetchData(message: string) {
return (dispatch) => {
dispatch({ type: 'LOADING', message })
setTimeout(() => {
dispatch({ type: 'LOADED' })
}, 1000)
}
}
function App() {
const onClick = () => {
fetchData('data is loading')(dispatch)
}
return (<div>
<button onClick={onClick}>click</button>
</div>);
}
很好,但是 fetchData(‘data is loading’)(dispatch) 這種寫法有點(diǎn)奇怪,會(huì)增加開發(fā)者的心智負(fù)擔(dān)。
于是可以借助 rudux 相關(guān)的異步中間件,以 rudux-chunk 為例,將寫法改為如下:
function fetchData(message: string) {
return (dispatch) => {
dispatch({ type: 'LOADING', message })
setTimeout(() => {
dispatch({ type: 'LOADED' })
}, 1000)
}
}
function App() {
const onClick = () => {
- fetchData('data is loading')(dispatch)
+ dispatch(fetchData('data is loading'))
}
return (<div>
<button onClick={onClick}>click</button>
</div>);
}
這樣就更符合認(rèn)知一些了,redux 異步中間件沒有什么奧秘,主要做的就是這樣的事情。
相關(guān)閱讀:Why do we need middleware for async flow in Redux?
23、redux 有哪些異步中間件?
1、redux-thunk
源代碼簡(jiǎn)短優(yōu)雅,上手簡(jiǎn)單
2、redux-saga
借助 JS 的 generator 來處理異步,避免了回調(diào)的問題
3、redux-observable
借助了 RxJS 流的思想以及其各種強(qiáng)大的操作符,來處理異步問題文章來源:http://www.zghlxwxcb.cn/news/detail-697063.html
感謝觀看,有幫助的話點(diǎn)點(diǎn)贊點(diǎn)點(diǎn)關(guān)注謝謝哈。文章來源地址http://www.zghlxwxcb.cn/news/detail-697063.html
到了這里,關(guān)于23個(gè)react常見問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!