[TOC]
React 16版本有一個名為 Fiber 的新核心架構。
# 事件機制
# setState
# `React.lazy` 與 `React.Suspense`
> `React.lazy`和 Suspense 技術還不支持服務端渲染。如果你想要在使用服務端渲染的應用中使用,我們推薦[Loadable Components](https://github.com/gregberge/loadable-components)這個庫。它有一個很棒的[服務端渲染打包指南](https://loadable-components.com/docs/server-side-rendering/)。
> `React.lazy`目前只支持默認導出(default exports)
`React.Suspense`也是一種虛擬組件(類似于[Fragment](https://reactjs.org/docs/react-api.html#reactfragment),僅用作類型標識),用法如下:
```
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./Home'));
const Bar = lazy(() => import('./Bar'));
const App = () => (
<Router>
<Suspense fallback={<div>loading</div>}>
<div>
<ul>
<li><Link to="/">Home></Link></li>
<li><Link to="/bar">Bar></Link></li>
</ul>
</div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/bar" component={Bar} />
</Switch>
</Suspense>
</Router>
)
export default App;
```
`Suspense`子樹中只要存在還沒回來的 Lazy 組件,就走`fallback`指定的內容。**這不正是可以提升到任意祖先級的loading嗎?**
http://www.ayqy.net/blog/react-suspense/
# React Context
React的 context 就是一個全局變量,可以從根組件跨級別在 React 的組件中傳遞。React context 的API有兩個版本,React16.x 之前的是 老版本的 context,之后的是新版本的 context。
新版本的 React context 使用了`Provider`和`Customer`模式,和 react-redux 的模式非常像。在頂層的`Provider`中傳入`value`,
在子孫級的`Consumer`中獲取該值,并且能夠傳遞函數,用來修改 context,如下代碼所示:
```
import React, { createContext } from 'react';
// 創建Context的唯一方法
const ThemeContext = createContext()
const SizeContext = createContext()
class App extends React.Component {
state = {
theme: 'red',
size: 'small'
}
render () {
const { theme, size } = this.state
return (
// 使用 Context.Provider 包裹后續組件,value 指定值
<ThemeContext.Provider value={theme}>
{/* 當出現多個Context的時候,只需要將Context.Provider 嵌套即可 */}
<SizeContext.Provider value={size}>
{/* 當Context的Provider值更改時,Consumer 的值必須重新渲染 */}
<button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按鈕</button>
<Middle></Middle>
</SizeContext.Provider>
</ThemeContext.Provider>
)
}
}
class Bottom extends React.Component {
render () {
return (
// Context.Consumer Consumer 消費者使用 Context 得值
// 但子組件不能是其他組件,必須渲染一個函數,函數的參數就是 Context 得值
// 當出現 多個 Consumer 的時候,進行嵌套,每個 Consumer 的子組件必須是一個函數,即可
<ThemeContext.Consumer>
{
theme => (
<SizeContext.Consumer>
{
size => (<h1>ThemeContext 的 值為 {theme}; SizeContext 的值為 {size}</h1>)
}
</SizeContext.Consumer>
)
}
</ThemeContext.Consumer>
)
}
}
class Middle extends React.Component {
render () {
return <Bottom></Bottom>
}
}
export default App;
```
* 當 Provider 提供的值更改時,Consumer 必須重新渲染
* context 不僅僅只是可以傳數值,也可以傳函數。
* 創建 Context 的時候`createContext`可以傳入默認值,當向上找不到 Provider 的時候,就會顯示默認值
> 注意:context 類似于全局變量做法,會讓組件失去獨立性、復用起來更困難,不能濫用、但本身它一定有適合使用的場景,具體看情況使用
## contextType
React 16.6 引入了在不直接使用 Consumer 組件的情況下從上下文消費數據的功能。這有助于減少組件 JSX 中不必要的嵌套,使它們更易于閱讀。
```
import React, { createContext } from 'react';
// 創建Context的唯一方法
const ThemeContext = createContext()
const SizeContext = createContext()
class App extends React.Component {
state = {
theme: 'red',
size: 'small'
}
render () {
const { theme, size } = this.state
return (
// 使用 Context.Provider 包裹后續組件,value 指定值
<ThemeContext.Provider value={theme}>
{/* 當出現多個Context的時候,只需要將Context.Provider 嵌套即可 */}
<SizeContext.Provider value={size}>
{/* 當Context的Provider值更改時,Consumer 的值必須重新渲染 */}
<button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按鈕</button>
<Middle></Middle>
</SizeContext.Provider>
</ThemeContext.Provider>
)
}
}
class Bottom extends React.Component {
// 申明靜態變量、contextType 將 context 直接賦值于 contextType
static contextType = ThemeContext
render () {
// 在 render 函數中 可以直接 訪問 this.context 獲取共享變量、這樣就可以不使用 consumer
const theme = this.context
return (
// Context.Consumer Consumer消費者使用Context得值
// 但子組件不能是其他組件,必須渲染一個函數,函數的參數就是Context得值
// 當出現 多個Consumer的時候,進行嵌套,每個Consumer 的子組件必須是一個函數,即可
<div>
<h1>ThemeContext 的 值為 {theme} </h1>
</div>
)
}
}
class Middle extends React.Component {
render () {
return <Bottom></Bottom>
}
}
export default App;
```
* `contextType`只能在類組件中使用
* 一個組件如果有多個consumer, contextType 只對其中一個有效,所以說,`contextType`只能有一個
## React context的局限性
1. 在組件樹中,如果中間某一個組件 `ShouldComponentUpdate` `returning false` 了,會阻礙 context 的正常傳值,導致子組件無法獲取更新。
2. 組件本身 extends `React.PureComponent` 也會阻礙 context 的更新。
注意點:
1. Context 應該是唯一不可變的
2. 組件只在初始化的時候去獲取 Context
> [context.html#classcontexttype](https://reactjs.org/docs/context.html#classcontexttype)
> [React context基本用法](https://www.cnblogs.com/mengff/p/9511419.html)
# 新的生命周期
需要關注新的[聲明周期](https://zh-hans.reactjs.org/docs/react-component.html):

目前在 16.4 版本中:
* ~~componentWillMount~~,
* ~~componentWillReceiveProps~~,
* ~~componentWillUpdate~~
并未完全刪除這三個生命周期函數,而且新增了
* `UNSAFE_componentWillMount`,
* `UNSAFE_componentWillReceiveProps`,
* `UNSAFE_componentWillUpdate`
三個函數,官方計劃在 17 版本完全刪除這三個函數,只保留 `UNSAVE_` 前綴的三個函數,目的是為了向下兼容,但是對于開發者而言應該盡量避免使用他們,而是使用新增的生命周期函數替代它們
**取而代之的生命周期**
1. [static getDerivedStateFromProps(nextProps, prevState)](https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromprops)
2. [getSnapshotBeforeUpdate(prevProps, prevState)](https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate)
## `getDerivedStateFromProps`
當我們接收到新的`props`,想去修改我們state,可以使用`getDerivedStateFromProps`。
```
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
lastRow: null
}
static getDerivedStateFromProps(nextProps,prevState){
if(nextProps.currentRow !== prevState.lastRow){
return {
isScrollingDown:
nextProps.currentRow > prevState.lastRow,
lastRow: nextProps.currentRow
}
}
return null;
}
}
```
## `getSnapshotBeforeUpdate`
這個函數有一個返回值,會作為第三個參數傳給 `componentDidUpdate`,如果你不想要返回值,請返回 `null`,不寫的話控制臺會有警告
```
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) { //表示之前的props 和之前的 state
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
```
## `componentDidUpdate(prevProps, prevState, snapshot)`
該方法在`getSnapshotBeforeUpdate`方法之后被調用,有三個參數`prevProps,prevState,snapshot`,**表示之前的props,之前的state,和snapshot**。第三個參數是`getSnapshotBeforeUpdate`返回的。
## 參考
[生命周期圖](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/)
[官網 - 組件的生命周期](https://zh-hans.reactjs.org/docs/react-component.html#the-component-lifecycle)
[[譯]如何使用React生命周期方法](https://juejin.im/post/5b59d1c8e51d4519455846e0)
[React 16.6.X版本的更新功能](https://www.jianshu.com/p/406bcc058790)
[盤點 React 16.0 ~ 16.5 主要更新及其應用](https://www.cnblogs.com/sunshq/p/10430728.html)