react面試題總結一波,以備不時之需
React元件的建構函式有什麼作用?它是必須的嗎?
建構函式主要用於兩個目的:
- 通過將物件分配給this.state來初始化本地狀態
- 將事件處理程式方法繫結到例項上
所以,當在React class中需要設定state的初始值或者繫結事件時,需要加上建構函式,官方Demo:
class LikeButton extends React.Component { constructor() { super(); this.state = { liked: false }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({liked: !this.state.liked}); } render() { const text = this.state.liked ? 'liked' : 'haven\'t liked'; return ( <div onClick={this.handleClick}> You {text} this. Click to toggle. </div> ); } } ReactDOM.render( <LikeButton />, document.getElementById('example') );
建構函式用來新建父類的this物件;子類必須在constructor方法中呼叫super方法;否則新建例項時會報錯;因為子類沒有自己的this物件,而是繼承父類的this物件,然後對其進行加工。如果不呼叫super方法;子類就得不到this物件。
注意:
- constructor () 必須配上 super(), 如果要在constructor 內部使用 this.props 就要 傳入props , 否則不用
- JavaScript中的 bind 每次都會返回一個新的函式, 為了效能等考慮, 儘量在constructor中繫結事件
除了在建構函式中繫結 this
,還有其它方式嗎
你可以使用屬性初始值設定項(property initializers)來正確繫結回撥,create-react-app 也是預設支援的。在回撥中你可以使用箭頭函式,但問題是每次元件渲染時都會建立一個新的回撥。
什麼原因會促使你脫離 create-react-app 的依賴
當你想去配置 webpack 或 babel presets。
何為 action
Actions 是一個純 javascript 物件,它們必須有一個 type 屬性表明正在執行的 action 的型別。實質上,action 是將資料從應用程式傳送到 store 的有效載荷。
diff演算法如何比較?
- 只對同級比較,跨層級的dom不會進行復用
- 不同型別節點生成的dom樹不同,此時會直接銷燬老節點及子孫節點,並新建節點
- 可以通過key來對元素diff的過程提供複用的線索
- 單節點diff
- 單點diff有如下幾種情況:
- key和type相同表示可以複用節點
- key不同直接標記刪除節點,然後新建節點
- key相同type不同,標記刪除該節點和兄弟節點,然後新建立節點
元件通訊的方式有哪些
- ⽗元件向⼦元件通訊: ⽗元件可以向⼦元件通過傳 props 的⽅式,向⼦元件進⾏通訊
- ⼦元件向⽗元件通訊: props+回撥的⽅式,⽗元件向⼦元件傳遞props進⾏通訊,此props為作⽤域為⽗元件⾃身的函 數,⼦元件調⽤該函式,將⼦元件想要傳遞的資訊,作為引數,傳遞到⽗元件的作⽤域中
- 兄弟元件通訊: 找到這兩個兄弟節點共同的⽗節點,結合上⾯兩種⽅式由⽗節點轉發資訊進⾏通訊
- 跨層級通訊: Context 設計⽬的是為了共享那些對於⼀個元件樹⽽⾔是“全域性”的資料,例如當前認證的⽤戶、主題或⾸選語⾔,對於跨越多層的全域性資料通過 Context 通訊再適合不過
- 釋出訂閱模式: 釋出者釋出事件,訂閱者監聽事件並做出反應,我們可以通過引⼊event模組進⾏通訊
- 全域性狀態管理⼯具: 藉助Redux或者Mobx等全域性狀態管理⼯具進⾏通訊,這種⼯具會維護⼀個全域性狀態中⼼Store,並根據不同的事件產⽣新的狀態
參考 前端進階面試題詳細解答
什麼是受控元件和非受控元件
-
受控元件:
沒有維持自己的狀態
資料由付元件控制
通過props獲取當前值,然後通過回撥函式通知更改
-
非受控元件
保持這個自己的狀態
資料有DOM控制
refs用於獲取其當前值
React的虛擬DOM和Diff演算法的內部實現
傳統 diff 演算法的時間複雜度是 O(n^3),這在前端 render 中是不可接受的。為了降低時間複雜度,react 的 diff 演算法做了一些妥協,放棄了最優解,最終將時間複雜度降低到了 O(n)。
那麼 react diff 演算法做了哪些妥協呢?,參考如下:
- tree diff:只對比同一層的 dom 節點,忽略 dom 節點的跨層級移動
如下圖,react 只會對相同顏色方框內的 DOM 節點進行比較,即同一個父節點下的所有子節點。當發現節點不存在時,則該節點及其子節點會被完全刪除掉,不會用於進一步的比較。
這樣只需要對樹進行一次遍歷,便能完成整個 DOM 樹的比較。
這就意味著,如果 dom 節點發生了跨層級移動,react 會刪除舊的節點,生成新的節點,而不會複用。
- component diff:如果不是同一型別的元件,會刪除舊的元件,建立新的元件
- element diff:對於同一層級的一組子節點,需要通過唯一 id 進行來區分
- 如果沒有 id 來進行區分,一旦有插入動作,會導致插入位置之後的列表全部重新渲染
- 這也是為什麼渲染列表時為什麼要使用唯一的 key。
React如何獲取元件對應的DOM元素?
可以用ref來獲取某個子節點的例項,然後通過當前class元件例項的一些特定屬性來直接獲取子節點例項。ref有三種實現方法:
-
字串格式:字串格式,這是React16版本之前用得最多的,例如:
<p ref="info">span</p>
-
函式格式:ref對應一個方法,該方法有一個引數,也就是對應的節點例項,例如:
<p ref={ele => this.info = ele}></p>
-
createRef方法:React 16提供的一個API,使用React.createRef()來實現
如何配置 React-Router 實現路由切換
(1)使用<Route>
元件
路由匹配是通過比較 <Route>
的 path 屬性和當前地址的 pathname 來實現的。當一個 <Route>
匹配成功時,它將渲染其內容,當它不匹配時就會渲染 null。沒有路徑的 <Route>
將始終被匹配。
// when location = { pathname: '/about' }
<Route path='/about' component={About}/> // renders <About/>
<Route path='/contact' component={Contact}/> // renders null
<Route component={Always}/> // renders <Always/>
(2)結合使用 <Switch>
元件和 <Route>
元件
<Switch>
用於將 <Route>
分組。
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
<Switch>
不是分組 <Route>
所必須的,但他通常很有用。 一個 <Switch>
會遍歷其所有的子 <Route>
元素,並僅渲染與當前地址匹配的第一個元素。
(3)使用 <Link>、 <NavLink>、<Redirect>
元件
<Link>
元件來在你的應用程式中建立連結。無論你在何處渲染一個<Link>
,都會在應用程式的 HTML 中渲染錨(<a>
)。
<Link to="/">Home</Link>
// <a href='/'>Home</a>
是一種特殊型別的 當它的 to屬性與當前地址匹配時,可以將其定義為"活躍的"。
// location = { pathname: '/react' }
<NavLink to="/react" activeClassName="hurray">
React
</NavLink>
// <a href='/react' className='hurray'>React</a>
當我們想強制導航時,可以渲染一個<Redirect>
,當一個<Redirect>
渲染時,它將使用它的to屬性進行定向。
Redux Thunk 的作用是什麼
Redux thunk 是一個允許你編寫返回一個函式而不是一個 action 的 actions creators 的中介軟體。如果滿足某個條件,thunk 則可以用來延遲 action 的派發(dispatch),這可以處理非同步 action 的派發(dispatch)。
React實現的移動應用中,如果出現卡頓,有哪些可以考慮的優化方案
- 增加
shouldComponentUpdate
鉤子對新舊props
進行比較,如果值相同則阻止更新,避免不必要的渲染,或者使用PureReactComponent
替代Component
,其內部已經封裝了shouldComponentUpdate
的淺比較邏輯 - 對於列表或其他結構相同的節點,為其中的每一項增加唯一
key
屬性,以方便React
的diff
演算法中對該節點的複用,減少節點的建立和刪除操作 -
render
函式中減少類似onClick={() => {doSomething()}}
的寫法,每次呼叫render函式時均會建立一個新的函式,即使內容沒有發生任何變化,也會導致節點沒必要的重渲染,建議將函式儲存在元件的成員物件中,這樣只會建立一次 - 元件的
props
如果需要經過一系列運算後才能拿到最終結果,則可以考慮使用reselect
庫對結果進行快取,如果props值未發生變化,則結果直接從快取中拿,避免高昂的運算代價 -
webpack-bundle-analyzer
分析當前頁面的依賴包,是否存在不合理性,如果存在,找到優化點並進行優化
Diff 的瓶頸以及 React 的應對
由於 diff 操作本身會帶來效能上的損耗,在 React 文件中提到過,即使最先進的演算法中,將前後兩棵樹完全比對的演算法複雜度為O(n3)
,其中 n 為樹中元素的數量。
如果 React 使用了該演算法,那麼僅僅一千個元素的頁面所需要執行的計算量就是十億的量級,這無疑是無法接受的。
為了降低演算法的複雜度,React 的 diff 會預設三個限制:
- 只對同級元素進行 diff 比對。如果一個元素節點在前後兩次更新中跨越了層級,那麼 React 不會嘗試複用它
- 兩個不同型別的元素會產生出不同的樹。如果元素由 div 變成 p,React 會銷燬 div 及其子孫節點,並新建 p 及其子孫節點
- 開發者可以通過 key 來暗示哪些子元素在不同的渲染下能保持穩定
fetch封裝
npm install whatwg-fetch --save // 適配其他瀏覽器
npm install es6-promise
export const handleResponse = (response) => {
if (response.status === 403 || response.status === 401) {
const oauthurl = response.headers.get('locationUrl');
if (!_.isEmpty(oauthUrl)) {
window.location.href = oauthurl;
return;
}
}
if (!response.ok) {
return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage));
}
if (isJson(response)) {
return response.json();
}
if (isText(response)) {
return response.text();
}
return response.blob();
};
const httpRequest = {
request: ({
method, headers, body, path, query,
}) => {
const options = {};
let url = path;
if (method) {
options.method = method;
}
if (headers) {
options.headers = {...options.headers,...headers};
}
if (body) {
options.body = body;
}
if (query) {
const params = Object.keys(query)
.map(k => `${k}=${query[k]}`)
.join('&');
url = url.concat(`?${params}`);
}
return fetch(url, Object.assign({}, options, { credentials: 'same-origin' })).then(handleResponse);
},
};
export default httpRequest;
什麼是上下文Context
Context 通過元件樹提供了一個傳遞資料的方法,從而避免了在每一個層級手動的傳遞 props 屬性。
- 用法:在父元件上定義getChildContext方法,返回一個物件,然後它的子元件就可以通過this.context屬性來獲取
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{
render() {
return (
<div>
<Title/>
</div>
)
}
}
class Title extends Component{
static contextTypes={
color:PropTypes.string
}
render() {
return (
<div style={{color:this.context.color}}>
Title
</div>
)
}
}
class Main extends Component{
render() {
return (
<div>
<Content>
</Content>
</div>
)
}
}
class Content extends Component{
static contextTypes={
color: PropTypes.string,
changeColor:PropTypes.func
}
render() {
return (
<div style={{color:this.context.color}}>
Content
<button onClick={()=>this.context.changeColor('green')}>綠色</button>
<button onClick={()=>this.context.changeColor('orange')}>橙色</button>
</div>
)
}
}
class Page extends Component{
constructor() {
super();
this.state={color:'red'};
}
static childContextTypes={
color: PropTypes.string,
changeColor:PropTypes.func
}
getChildContext() {
return {
color: this.state.color,
changeColor:(color)=>{
this.setState({color})
}
}
}
render() {
return (
<div>
<Header/>
<Main/>
</div>
)
}
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
何為 Children
在JSX表示式中,一個開始標籤(比如<a>
)和一個關閉標籤(比如</a>
)之間的內容會作為一個特殊的屬性props.children
被自動傳遞給包含著它的元件。
這個屬性有許多可用的方法,包括 React.Children.map
,React.Children.forEach
, React.Children.count
, React.Children.only
,React.Children.toArray
。
componentWillReceiveProps呼叫時機
- 已經被廢棄掉
- 當props改變的時候才呼叫,子元件第二次接收到props的時候
React 效能優化
- shouldCompoentUpdate
- pureComponent 自帶shouldCompoentUpdate的淺比較優化
- 結合Immutable.js達到最優
說說你用react有什麼坑點?
1. JSX做表示式判斷時候,需要強轉為boolean型別
如果不使用
!!b
進行強轉資料型別,會在頁面裡面輸出0
。
render() {
const b = 0;
return <div>
{
!!b && <div>這是一段文字</div>
}
</div>
}
2. 儘量不要在 componentWillReviceProps
裡使用 setState,如果一定要使用,那麼需要判斷結束條件,不然會出現無限重渲染,導致頁面崩潰
3. 給元件新增ref時候,儘量不要使用匿名函式,因為當元件更新的時候,匿名函式會被當做新的prop處理,讓ref屬性接受到新函式的時候,react內部會先清空ref,也就是會以null為回撥引數先執行一次ref這個props,然後在以該元件的例項執行一次ref,所以用匿名函式做ref的時候,有的時候去ref賦值後的屬性會取到null
4. 遍歷子節點的時候,不要用 index 作為元件的 key 進行傳入
React Hooks 解決了哪些問題?
React Hooks 主要解決了以下問題:
(1)在元件之間複用狀態邏輯很難
React 沒有提供將可複用性行為“附加”到元件的途徑(例如,把元件連線到 store)解決此類問題可以使用 render props 和 高階元件。但是這類方案需要重新組織元件結構,這可能會很麻煩,並且會使程式碼難以理解。由 providers,consumers,高階元件,render props 等其他抽象層組成的元件會形成“巢狀地獄”。儘管可以在 DevTools 過濾掉它們,但這說明了一個更深層次的問題:React 需要為共享狀態邏輯提供更好的原生途徑。
可以使用 Hook 從元件中提取狀態邏輯,使得這些邏輯可以單獨測試並複用。Hook 使我們在無需修改元件結構的情況下複用狀態邏輯。 這使得在元件間或社群內共享 Hook 變得更便捷。
(2)複雜元件變得難以理解
在元件中,每個生命週期常常包含一些不相關的邏輯。例如,元件常常在 componentDidMount 和 componentDidUpdate 中獲取資料。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設定事件監聽,而之後需在 componentWillUnmount 中清除。相互關聯且需要對照修改的程式碼被進行了拆分,而完全不相關的程式碼卻在同一個方法中組合在一起。如此很容易產生 bug,並且導致邏輯不一致。
在多數情況下,不可能將元件拆分為更小的粒度,因為狀態邏輯無處不在。這也給測試帶來了一定挑戰。同時,這也是很多人將 React 與狀態管理庫結合使用的原因之一。但是,這往往會引入了很多抽象概念,需要你在不同的檔案之間來回切換,使得複用變得更加困難。
為了解決這個問題,Hook 將元件中相互關聯的部分拆分成更小的函式(比如設定訂閱或請求資料),而並非強制按照生命週期劃分。你還可以使用 reducer 來管理元件的內部狀態,使其更加可預測。
(3)難以理解的 class
除了程式碼複用和程式碼管理會遇到困難外,class 是學習 React 的一大屏障。我們必須去理解 JavaScript 中 this 的工作方式,這與其他語言存在巨大差異。還不能忘記繫結事件處理器。沒有穩定的語法提案,這些程式碼非常冗餘。大家可以很好地理解 props,state 和自頂向下的資料流,但對 class 卻一籌莫展。即便在有經驗的 React 開發者之間,對於函式元件與 class 元件的差異也存在分歧,甚至還要區分兩種元件的使用場景。
為了解決這些問題,Hook 使你在非 class 的情況下可以使用更多的 React 特性。 從概念上講,React 元件一直更像是函式。而 Hook 則擁抱了函式,同時也沒有犧牲 React 的精神原則。Hook 提供了問題的解決方案,無需學習複雜的函式式或響應式程式設計技術