這篇文章主要介紹v16.4~ v16.6的特性,如果你關注Hooks,可以直接看React 16 新特性全解(下)
新增指針事件
新增了對對指針設備(例如滑鼠,觸控筆,或者手指觸摸)觸發的dom事件:
等等。
但是這個事件僅支持那些支持指針事件的瀏覽器,比如目前最新版本的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這個問題。
這個小版本的發布主要是為了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元素屬性需要依賴用戶輸入的場景非常的少,所以對於大部分應用來說沒有影響,最重要的是意味著對大部分開發者都沒有影響,這樣我們就不用擔心要半夜起來改代碼,還是可以的。
這個版本提供了對新的Profiler DevTools插件的支持。這個插件就厲害了,可以通過收集每個組件的渲染耗時,來幫助我們找到ReactApp的渲染瓶頸,並且整個界面更加清爽清晰,簡直是開發者的福利。
話不多說,下面來講解下如何使用:
如果你的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的變化。
以上就是一些基本使用,關於實際例子的演示,還挺多的,可以單獨寫一篇文章了。 有時間的話在給大家介紹。
React 15:如果你想阻止組件的重複渲染,在class component裏可以使用PureComponent, shouldComponentUpdate來幫助你。但是如果你是function component,對不起,沒有這個功能, 只能每次都重新渲染。
React 16:為了全面擁抱function component,React團隊寫了memo來幫助function component實現這個阻止重複渲染的功能。
demo
在這個demo可以看到,如果沒有memo,每次點擊button控制檯都會列印render表示重新渲染。但是加了 memo之後,就不會了。
lazy需要跟Suspence配合使用,所以這裡放在一起介紹。
lazy實際上是幫助我們實現代碼分割的功能,使用過webpack的同學都知道,webpack也有這個功能。 那為什麼他們都要做這個功能呢?
其實是這樣的:由於有些內容,我們並不一定要在首屏展示,所以這些資源我們沒有必要一開始就要去獲取,那麼這些資源就可以動態獲取。這樣的話,相當於把不需要首屏展示的代碼分割出來,減少首屏代碼的體積,提升性能。
在這個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的標識。
注意:
進一步優化:
這裡我們在進一步思考一個點,目前我們的B組件是需要用到的時候才載入。萬一這個組件需要獲取數據,使得他顯示比較慢,就會顯示loading,導致我們用戶體驗比較差呢。所以我們可否在瀏覽器閑著的時候預載入這些即將要用到資源?
答案是可以的,React團隊也在做這件事情。 但是這個API目前為止還沒有Ready,大家先知道這個事情好了~
簡化獲取context的方式,之前需要用一個在外層包裹一個<Consumer>,如下:
<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;
v16.3這個版本里,React 除了Error Boundaries來捕獲錯誤,裡面主要是使用了componentDidCatch來捕獲 錯誤。但是它是在錯誤已經發生之後並且render函數被調用之後,才會被調用。 也就是說如果一個組件出現的錯誤,在調用 componentDidCatch之前只能返回null給用戶。
注意事項: 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(推薦寫法):
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); }
以上就是v16.4 ~ v16.6的全部內容,考慮到大家應該比較累了,Hooks又是比較大的特性,所以還是分到下一篇吧~
本文對你有幫助的話,點個贊吧~
推薦閱讀: