深入理解 React JS 元件 9 個略難的 React JS 面試問題

me
林彥成
2021-05-21 | 4 min.
文章目錄
  1. 1. 元件中週期有哪些? 執行順序如何?
    1. 1.1. getDerivedStateFromProps() 這個週期的用途?
  2. 2. 元件渲染的三個狀態
  3. 3. 什麼是 Reconciliation?
  4. 4. 什麼是 HOC (Higher-Order Components)?
  5. 5. React 中的 Portals 是做什麼用的?
  6. 6. React v16 錯誤邊界 (Error Boundaries) 是什麼?
  7. 7. React 中要怎麼實作 Props 相關檢查?
  8. 8. 如何在頁面載入時自動 focus 在輸入框上?

因為最近想準備面試,所以整理了些覺得算是比較進階的 React 必備知識,底下是 9 個問題和回答:

元件中週期有哪些? 執行順序如何?

人有生老病死,元件從出現到消失主要也分三部分 Mount -> Updating -> Unmounting:

  1. Mount: 已經出現在瀏覽器的 DOM 上

    1. constructor()
    2. static getDerivedStateFromProps()
    3. render()
    4. componentDidMount()
  2. Updating: Props 或 State 變化後引發的元件更新

    1. getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate()
    5. componentDidUpdate()
  3. Unmounting: 從瀏覽器的 DOM 中移除

    1. componentWillUnmount()

getDerivedStateFromProps() 這個週期的用途?

getDerivedStateFromProps() 會在元件初始化後但尚未 re-rendered 前執行。會回傳一個物件去更新狀態或是也可以選擇回傳 null 代表這次的 props 改變不用更新元件狀態,搭配 componentDidUpdate() 一起使用來取代被棄用的componentWillReceiveProps()

1
2
3
4
5
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}

元件渲染的三個狀態

元件從 Props 或 State 變化後引發改變的過程,主要分為兩大階段 Render 和 Commit,會先進行 Render 的計算後才會真的 Commit 結果到真正的 DOM 上面,Commit 前會有個 Pre-commit。

  1. Render: 在這個階段 React 能自行暫停、取消、重新這個過程
  2. Pre-commit: 文件上有出現但甚少使用的功能,有一個週期是 getSnapshotBeforeUpdate(),可以看成是一個做決定前的再次狀態確認
  3. Commit: 套用改變到瀏覽器的 DOM 上,而這是肉眼可見,也是我們較常操作的週期
    1. componentDidMount(): Mount 成功,出現在 DOM 上面
    2. componentDidUpdate(): Props 或 State 改變
    3. componentWillUnmount(): 從 DOM 上移除

什麼是 Reconciliation?

當元件 props 或是 state 改變後,React 會透過 render 的演算法來決定最終要更新到 DOM 上面的改變,這個過程就是 reconciliation。

如果是想自己手動提升效能則可以運用條件減少狀態改變:

1
2
3
4
5
6
7
8
9
10
11
getUserAddress = (user) => {
const latestAddress = user.address;
this.setState((state) => {
// 地址沒變就不用換畫面
if (state.address === latestAddress) {
return null;
} else {
return { title: latestAddress };
}
});
};

什麼是 HOC (Higher-Order Components)?

常寫 JavaScript 的開發者就會知道,function 可以 return 任何東西,而 return 一個新的 function 的 function 我們就稱作 HOF (Higher-Order Function),底下是範例:

1
2
3
4
5
6
7
8
9
var xHOF = function (x) {
return function (y) {
return x * y;
};
};
var xFive = xHOF(5);
var xTen = xHOF(10);
xFive(2); // 10
xTen(2); // 20

一個 HOC 就是一個函式 (function),接收到元件後,產生出另一個新的元件,在卡通影片中,可以想像成金剛戰士透過手表進行變身,或是數碼寶貝透過共振主人的無限可能進化成更強的模樣。一個 HOC 有底下四個特點:

  • Pure component: 沒有修改原來行為只有加強功能
  • Code reuse: 讓邏輯能夠重複使用
  • Render hijacking: 在真的元件 render 前,處理 render 前需要處理的事情,像是資料防呆或是將操作記錄發到後台
  • Manipulation: State 和 Props 統一控制,元件載入狀態動畫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function HOC(WrappedComponent) {
return class Test extends Component {
render() {
const newProps = {
title: "New Header",
footer: false,
showFeatureX: false,
showFeatureY: true,
};

return <WrappedComponent {...this.props} {...newProps} />;
}
};
}

有 staticMethod 的話要自己複製:

1
2
3
4
5
6
7
function enhance(WrappedComponent) {
class Enhance extends React.Component {
/*...*/
}
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}

React 中的 Portals 是做什麼用的?

正常 react 所有的渲染都只掛載在某個 Parent 節點下,如果需要跳脫這個節點就需要 Portals,第一個參數可以放任何可被渲染的 child,第二個參數則是真實的 DOM element。

ReactDOM.createPortal(child, container)

React v16 錯誤邊界 (Error Boundaries) 是什麼?

錯誤邊界其實也只是個元件,錯誤邊界的概念像是一道防火門,讓當渲染發生錯誤時,能夠被關在 child component 中並顯示客製化的錯誤 UI,透過 componentDidCatch(error, info)static getDerivedStateFromError() 這兩個新增的周期來協助記錄相關錯誤。

This lifecycle method is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.

The signature of the lifecycle method is as follows,

static getDerivedStateFromError(error)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>{"Something went wrong."}</h1>;
}
return this.props.children;
}
}

當撰寫完成後,使用上也非常簡單,只要把原來的元件包起來就好了,底下是範例

1
2
3
<ErrorBoundary>
<MyBugComponent />
</ErrorBoundary>

錯誤邊界在底下四種情形不會 catch 到錯誤

  • 在 Event handlers 中,只能夠自己寫 try catch
  • setTimeout 或 requestAnimationFrame 的 callbacks
  • 伺服器渲染的過程
  • ErrorBoundary 沒寫好自己發生的錯誤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
this.handleClick = this.handleClick.bind(this);
}

// 在 Event handlers 中,只能夠自己寫 try catch
handleClick() {
try {
// Do something that could throw
} catch (error) {
this.setState({ error });
}
}

render() {
if (this.state.error) {
return <h1>Caught an error.</h1>;
}
return <button onClick={this.handleClick}>Click Me</button>;
}
}

React 中要怎麼實作 Props 相關檢查?

雖然 JavaScript 是弱型別的語言,但 React 內建 PropTypes check 機制,當 APP 處在開發模式下,React 會自動確認是否每個 props 都是如所設定的格式,若不正確 React 就會在命令列中產生警告的訊息,這個機制由於效能考量在 Production 環境中會被停用。

React 定義了底下十種型別:

  1. PropTypes.number
  2. PropTypes.string
  3. PropTypes.array
  4. PropTypes.object
  5. PropTypes.func
  6. PropTypes.node
  7. PropTypes.element
  8. PropTypes.bool
  9. PropTypes.symbol
  10. PropTypes.any

Props 中可分為必填和非必填,必填的後面則加上 isRequired 像是 PropTypes.number.isRequired,如果陣列中的物件需要定義則可以搭配使用 React.PropTypes.shape()React.PropTypes.arrayOf(),如果 Props 會是變動的,也可以使用 React.PropTypes.oneOfType()

1
2
3
4
5
6
7
8
9
10
11
12
ReactComponent.propTypes = {
arrayWithShape: React.PropTypes.arrayOf(
React.PropTypes.shape({
name: React.PropTypes.string.isRequired,
weight: React.PropTypes.number.isRequired,
})
).isRequired,
};

ReactComponent.PropTypes = {
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

如何在頁面載入時自動 focus 在輸入框上?

只需要幫 input 加上參照 ref 就可以在 componentDidMount() 後針對 input 進行操作。不過這種 inline function 的寫法在元件更新的時候因為底層的邏輯會被執行兩次,所以不被推薦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class App extends React.Component {
componentDidMount() {
this.nameInput.focus();
}

render() {
return (
<div>
<input defaultValue={"Won't focus"} />
<input
ref={(input) => (this.nameInput = input)}
defaultValue={"Will focus"}
/>
</div>
);
}
}

ReactDOM.render(<App />, document.getElementById("app"));

比較推薦的寫法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class UserForm extends Component {
handleSubmit = () => {
console.log("Input Value is: ", this.input.value);
};

setSearchInput = (input) => {
this.input = input;
};

render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.setSearchInput} /> // Access DOM input in
handle submit
<button type="submit">Submit</button>
</form>
);
}
}

喜歡這篇文章,請幫忙拍拍手喔 🤣

share