前言

這篇文章主要介紹v16.4~ v16.6的特性,如果你關注Hooks,可以直接看React 16 新特性全解(下)

目錄

  • v16.4
  1. 新增指針事件
  2. fix生命週期函數
  • v16.5
  1. 提供新的調試工具
  • v16.6
  1. memo
  2. lazy
  3. suspense
  4. 簡化 contextType
  5. 增加 getDerivedStateFromError

16.4

新增指針事件

新增了對對指針設備(例如滑鼠,觸控筆,或者手指觸摸)觸發的dom事件:

  • onPointerDown
  • onPointerMove
  • onPointerEnter
  • onPointerLeave
  • onPointerOver
  • onPointerOut

等等。

但是這個事件僅支持那些支持指針事件的瀏覽器,比如目前最新版本的Chrome,Firefox,Edge IE瀏覽器)。但是如果你的應用程序真的依賴這些事件,可以使用第三方的polyfill。因為React團隊對了避免增大react的bundle size,所以沒有放進去。

demo時刻

這裡展示的是一個滑鼠的拖拽功能,可以自己try try。

fix生命週期函數 - getDerivedStateFromProps

React 16.0 ~ React 16.3 :

生命週期的圖如下:

這裡我們可以看到getDerivedStateFromProps這個生命週期函數有個問題就是在Updating階段的時候,無論使用setState還是forceUpdate,都不調用getDerivedStateFromProps。

喂,這明顯有問題啊?! 那我在updating階段都沒辦法監聽到props的改變來搞事情了。

React團隊還是很快意識到了這個問題的。所以在這個版本,他們fix了這個問題,新的圖長這樣:

很簡單,就是fix了之前Updating階段用setState,forceUpdate調用不到getDerivedStateFromProps這個問題。

v16.4.2

這個小版本的發布主要是為了fix一個服務端的XSS漏洞,我本來不想講,但是看大家那麼好學,還是提一下把。

呃,別告訴我你還不知道什麼是XSS漏洞??,拜託趕緊現在立刻馬上去學一下,這個必會。一個簡單的例子就是用戶輸入了你不想讓他輸入的內容,尤其帶有JS html標籤的,給你的網站帶來了麻煩。

這個XSS具體場景是這樣的:

let props = {};
// 注意:這裡props的屬性依靠用戶輸入
props[userProvidedData] = "hello";
let element = <div {...props} />;
let html = ReactDOMServer.renderToString(element);

此時,用戶輸入:

let userProvidedData = ></div><script>alert("hi")</script>;

那麼最終生成的html就會變成:

<div ></div><script>alert("hi")</script>

這就是我們常見的XSS攻擊。

但是現實中我們dom元素屬性需要依賴用戶輸入的場景非常的少,所以對於大部分應用來說沒有影響,最重要的是意味著對大部分開發者都沒有影響,這樣我們就不用擔心要半夜起來改代碼,還是可以的。

v16.5 React Profiler

這個版本提供了對新的Profiler DevTools插件的支持。這個插件就厲害了,可以通過收集每個組件的渲染耗時,來幫助我們找到ReactApp的渲染瓶頸,並且整個界面更加清爽清晰,簡直是開發者的福利。

話不多說,下面來講解下如何使用:

  1. 首先安裝React DevTools插件

如果你的React版本已經升到16.5以上,那麼你的DevTools的界面會變成這樣: 打開第二個tab。

2.點擊中間這個button開始記錄

3.接著操作你想要記錄的操作,完事之後點stop

4.看結果圖

在講解之前,先普及一些知識。知道Fiber的同學應該都瞭解,現在React渲染過程分為兩個階段:

一、render階段

這個階段主要是對比,有那些DOM節點需要更新。

二、commit階段

將第一階段收集的信息更新到真實節點,完成之後會調用componentDidMount跟componentDidUpdate。

接著看結果圖。

圖示一的地方顯示的是每一次commit的耗時,其中黑色表示當前選中的commit,可以左右移動來選擇,其中柱子越高說明這次的commit耗時約多。

圖示二表示的是這次commit發生在第幾S,它的render階段耗時多少。

圖示三表示的是這次commit裏每一個組件的耗時。由圖中我們可以看到Router耗時最多,達到18.4ms。 而耗時主要來源於Nav 跟 Route組件。 如果你去點擊每個組件,還可以在右邊看到這個組件此時的state跟props,那就可以很清晰的知道這個組件此刻在渲染什麼。

小技巧

再說最後一個小技巧,你可以選中某個組件,然後點擊右上角兩次相鄰的commit,這樣你就知道是哪個 state的改變引發了這次的re-render。 比如下面的例子,就是scrollOffset的變化導致整個App的變化。

以上就是一些基本使用,關於實際例子的演示,還挺多的,可以單獨寫一篇文章了。 有時間的話在給大家介紹。

v16.6

memo

React 15:如果你想阻止組件的重複渲染,在class component裏可以使用PureComponent, shouldComponentUpdate來幫助你。但是如果你是function component,對不起,沒有這個功能, 只能每次都重新渲染。

React 16:為了全面擁抱function component,React團隊寫了memo來幫助function component實現這個阻止重複渲染的功能。

demo

在這個demo可以看到,如果沒有memo,每次點擊button控制檯都會列印render表示重新渲染。但是加了 memo之後,就不會了。

lazy、suspense

lazy需要跟Suspence配合使用,所以這裡放在一起介紹。

lazy實際上是幫助我們實現代碼分割的功能,使用過webpack的同學都知道,webpack也有這個功能。 那為什麼他們都要做這個功能呢?

其實是這樣的:由於有些內容,我們並不一定要在首屏展示,所以這些資源我們沒有必要一開始就要去獲取,那麼這些資源就可以動態獲取。這樣的話,相當於把不需要首屏展示的代碼分割出來,減少首屏代碼的體積,提升性能。

demo

在這個demo中,有3個tab。但是我們的首屏只需要先展示一個,所以其他的可以動態引入。 這裡以動態引入B為例:

<!--import B from "./B";-->

// 需要用到的時候才載入進來,當然還有預載入更好
const B = lazy(() => import("./B"));

但是這樣的話,一開始就可以點擊 B,會報錯。 因為需要當組件還在載入渲染的時候,需要一個place holder防止組件還沒載入完畢的時候可以有東西顯示給用戶。

這時候Suspence得作用就出來了。 Suspence 很像Error Boundary,不同的是Error Boundary是用來捕獲錯誤,顯示相應的callback組件。而Suspence是用來捕獲還沒有載入好的組件,並暫停渲染,顯示相應的callback。

我們給B加上Suspense 就搞定了。

<Suspense fallback={<div>Loading...</div>}>
<TabPanel>
<B />
</TabPanel>
</Suspense>

跟Error Boundary一樣,Suspence也有一個放置位置的問題,是整個包裹在App外,還是隻是給單獨的組件包裹?

我的選擇與Error Boundary依舊一致,應該將其包裹在子組件外面,因為這樣當某個組件沒有載入好的時候,不會影響到整個App都顯示一個loading的標識。

注意:

  1. SSR不支持lazy這個特性。
  2. Lazy 必須搭配Suspence使用,否則會報錯

進一步優化:

這裡我們在進一步思考一個點,目前我們的B組件是需要用到的時候才載入。萬一這個組件需要獲取數據,使得他顯示比較慢,就會顯示loading,導致我們用戶體驗比較差呢。所以我們可否在瀏覽器閑著的時候預載入這些即將要用到資源?

答案是可以的,React團隊也在做這件事情。 但是這個API目前為止還沒有Ready,大家先知道這個事情好了~

簡化static contextType

簡化獲取context的方式,之前需要用一個在外層包裹一個<Consumer>,如下:

// Theme context, default to light theme
const ThemeContext = React.createContext(light);

// Signed-in user context
const UserContext = React.createContext({
name: Guest,
});

class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;

// App component that provides initial context values
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}

function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}

// A component may consume multiple contexts
// 同時如果是function component 用Consumer
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}

現在可以直接通過this.context獲取。

class MyClass extends React.Component {
static contextType = MyContext;
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;

新增static getDerivedStateFromError

v16.3這個版本里,React 除了Error Boundaries來捕獲錯誤,裡面主要是使用了componentDidCatch來捕獲 錯誤。但是它是在錯誤已經發生之後並且render函數被調用之後,才會被調用。 也就是說如果一個組件出現的錯誤,在調用 componentDidCatch之前只能返回null給用戶。

而 getDerivedStateFromError 可以在render函數之嵌捕獲到錯誤,所以它更適合寫用來顯示fallback UI的邏輯。

注意事項: componentDidCatch,getDerivedStateFromError都無法捕獲服務端的錯誤,但是React團隊正在努力支持SSR。

改進前的ErrorBoundary:

class ErrorBoundary extends React.Component {
state = { hasError: false };

componentDidCatch(error, info) {
this.setState({ hasError: false })
logErrorToMyService(error, info);
}

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

return this.props.children;
}

改進後的ErrorBoundary(推薦寫法):

class ErrorBoundary extends React.Component {
state = { hasError: false };

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
// 更新state所以下次render可以立刻顯示fallback UI
return { hasError: true };
}

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

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

return this.props.children;
}

以上就是v16.4 ~ v16.6的全部內容,考慮到大家應該比較累了,Hooks又是比較大的特性,所以還是分到下一篇吧~

本文對你有幫助的話,點個贊吧~

推薦閱讀:

相關文章