Stateless Function Component:html
const App = (props) => (
<div>Hello, {props.name}</div>
)
複製代碼
Class Component:react
class App extends React.Component {
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
複製代碼
上面是兩個最簡單的 function component 和 class component 的對比,首先從行數上來看,3 << 7。git
再看 babel 編譯成 es2015 後的代碼:github
Function Component:web
"use strict";
var App = function App(props) {
return React.createElement("div", null, "Hello, ", props.name);
};
複製代碼
Class Component:npm
去除了一堆 babel helper 函數數組
"use strict";
var App =
/*#__PURE__*/
function (_React$Component) {
_inherits(App, _React$Component);
function App() {
_classCallCheck(this, App);
return _possibleConstructorReturn(this, _getPrototypeOf(App).apply(this, arguments));
}
_createClass(App, [{
key: "render",
value: function render() {
return React.createElement("div", null, "Hello, ", this.props.name);
}
}]);
return App;
}(React.Component);
複製代碼
Function Component 僅僅是一個普通的 JS 函數,Class Component 由於 ES2015 不支持 class
的緣由,會編譯出不少和 class 相關的代碼。性能優化
同時由於 Function Component 的特殊性,React 底層或許能夠作 更多的性能優化。bash
總的來講,如下點:babel
bind(this)
在 React Class Component 中,咱們必定寫過不少這樣的代碼
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: 'rccoder',
age: 22
},
this.updateName = this.updateName.bind(this);
this.updateAge = this.updateAge.bind(this);
}
render() {
<div onClick={this.updateName}
</div>
}
}
複製代碼
固然這個錯不在 React,而在於 JavaScript 的 this 指向問題,簡單看這樣的代碼:
class Animate {
constructor(name) {
this.name = name;
}
getName() {
console.log(this);
console.log(this.name)
}
}
const T = new Animate('cat');
T.getName(); // `this` is Animate Instance called Cat
var P = T.getName;
P(); // `this` is undefined
複製代碼
這個例子和上面的 React 一模一樣,在沒有 bind 的狀況下這樣寫會 致使了 this 是 global this,即 undefined。
解決它比較好的辦法就是在 contructor 裏面 bind this。
在新版本的 ES 中,有 Public Class Fields Syntax 能夠解決這個問題,即:
class Animate {
constructor(name) {
this.name = name;
}
getName = () => {
console.log(this);
console.log(this.name)
}
}
const T = new Animate('cat');
T.getName(); // `this` is Animate Instance called Cat
var P = T.getName;
P(); // `this` is Animate Instance called Cat
複製代碼
箭頭函數不會建立本身的 this,只會依照詞法從本身的做用域鏈的上一層繼承 this,從而會讓這裏的 this 指向剛好和咱們要的一致。
即便 public class fileds syntax 藉助 arrow function 能夠勉強解決這種問題,但 this 指向的問題依舊讓人 「恐慌」。
React 有很是多的生命週期,在 React 的版本更新中,有新的生命週期進來,也有一些生命週期官方已經漸漸開始認爲是 UNSAFE
。目前被標識爲 UNSAFE
的有:
新引入了
getDerivedStateFromProps
和 getSnapshotBeforeUpdate
均是返回一個處理後的對象給 componentDidUpdate
,全部須要操做的邏輯都放在 componentDidUpdate
裏面。
原則上:
getDerivedStateFromProps
+ componentDidUpdate
能夠替代 componentWillReceiveProps
的全部正常功能;getSnapshotBeforeUpdate
+ componentDidUpdate
能夠替代 componentWillUpdate
的全部功能。具體的 緣由 和 遷移指南 能夠參考 React 的官方博客:Update on Async Rendering,有比較詳實的手把手指南。
最後,你應該依舊是一樣的感受,Class Component 有如此多的生命週期,顯得是如此的複雜。
說了上面一堆看似和題目無關的話題,其實就是爲了讓你以爲 「Function Component 大法好」,而後再開心的看下文。
終於來到了和 Hooks 相關的部分,首先咱們看下 什麼是 Hooks:
首先來看下咱們熟知的 WebHook:
Webhooks allow you to build or set up GitHub Apps which subscribe to certain events on GitHub.com. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL. Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. You're only limited by your imagination.
—— GitHub WebHook 介紹
核心是:When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL
那 React Hooks 又是什麼呢?
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
看上去和 WebHook 區別不小,實際上也不小,怎麼解釋呢?
React Hook 在 Function Component 上開了一些 Hook,經過內置的一些 Hook 可讓 Function Component 擁有本身的 state 和 生命週期,同時能夠在 Hook 中操做它。此外經過組合內置的 Hook + 本身的業務邏輯 就能夠生成新的 Custom Hook,以方便優雅複用一些業務邏輯。
不少時候,視圖表現不一樣的組件都指望擁有一部分相同的邏輯,好比:強制登陸、注入一些值等。這個時候咱們常常會使用 HOC、renderProps 來包裝這層邏輯,總的來講都會加入一層 wrapper,使組件的層級發生了變化,隨着業務邏輯複雜度的增長,都會產生 wrapper 地獄的問題。
隨着業務邏輯複雜度的增長,咱們的組件常常會在一個生命週期中幹多見事,好比:在 componentDidMount 中請求數據、發送埋點等。總之就是在一個生命週期中會寫入多個徹底不相關的代碼,進而形成各類成本的隱形增長。
假如由於這些問題再把組件繼續抽象,不只工做量比較繁雜,同時也會遇到 wrapper 地獄和調試閱讀更加困難的問題。
從人的角度上講,class component 須要關心 this 指向等,大多常常在使用 function component 仍是 class component 上感到困惑;從機器的角度上講,class component 編譯體積過大,熱重載不穩定
上述提到的三個問題,Hooks 某種意義上都作了本身的解答,那是如何解答的呢?
目前 Hooks 有: State Hook、Effect Hook、Context Hook、以及 Custom Hook。
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
複製代碼
useState
是 State Hook 的 API。入參是 initialState
,返回一個 數組,第一值是 state,第二個值是改變 state 的函數。
若是 initialState
的提升須要消耗大量的計算力,同時不指望這些計算阻塞後面要乾的事情的話。initialState
能夠是個函數,會在 render 前調用達到 Lazy Calc 的效果。
useState(() => {
// ... Calc
return initialState;
})
複製代碼
同時爲了不沒必要要的性能開銷,在設置 State 的時候若是兩個值是相等的,則也不會觸發 rerender。(判斷兩個值相等使用的是 Object.is)
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
return () => {
... // Similar to componentWillUnMount。Named as clear up effect
}
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
複製代碼
useEffect
至關於 class Component 中 componentDidMount
、 componentDidUpdate
、 componentWillUnmount
三個生命週期的大綜合,在組件掛載、更新、卸載的時候都會執行 effect 裏面的函數。用 「after render」 理解是最好的。
值的注意的是,Effect Hook 中的內容不會像 componentDidMount
、componentDidUpdate
同樣阻塞渲染。若是不指望這種表現,但是用來 API 表現同樣的 useLayoutEffect
。(常見的計算器快速增長問題)
在一個 Function Component 裏,和 useState 同樣能夠可使用屢次 useEffect,這樣在組織業務邏輯的時候,就能夠按照業務邏輯去劃分代碼片斷了(而不是 Class Component 中只能按照生命週期去劃分代碼片斷)。
Effect Hook 的執行實際是 「after render」,爲了不每一個 render 都執行全部的 Effect Hook,useEffect
提供了第二個入參(是個數組),組件 rerender 後數組中的值發生了變化後纔會執行該 Effect Hook,若是傳的是個空數組,則只會在組件第一次 Mount 後和 Unmount 前調用。這層優化理論上是能夠在編譯時去作的,React Team 後期可能會作掉這層。
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li>
);
}
複製代碼
useFriendStatus
就是一個典型的 Custom Hook,他利用 useState 和 useEffect 封裝了 訂閱朋友列表,設置朋友狀態,組件卸載時取消訂閱 的系列操做,最後返回一個表示是否在線的 state;在使用的時候,就能夠像內置 Hook 同樣使用,享用封裝的系列邏輯。
在 Hooks 內部,即便在一個 Function Component 中,每一個 Hooks 調用都有本身的隔離空間,能保證不一樣的調用之間互不干擾。
useFriendStatus
以 use
開頭是 React Hooks 的約定,這樣的話方便標識他是一個 Hook,同時 eslint 插件也會去識別這種寫法,以防產生沒必要要的麻煩。
同時若是 useXXX
的 initialState
是個變量,而後這個變量發生變化的時候,該 Hook 會自動取消以前的消息訂閱,從新進行 Hooks 的掛載。也就是說,在上面的例子中,若是 props.friend.id 發生變化,useFriendStatus 這個 Hooks 會從新掛載,進而 online 狀態也會正常顯示。
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
複製代碼
useContext 的入參是某個 Provider 提供的 context,若是 context 發生變化的話,返回值也會當即發生變化。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼
若是 State 的變化有比較複雜的狀態流轉,可使用 useReducer 讓他更加 Redux 化,以便讓這層邏輯更加清晰。同時 Reduce Hook 也提供 Lazy Calc 的功能,有需求的時候能夠去使用它。
除此以外,內置的 Hooks 還有 useCallback
, useMemo
,useRef
,useImperativeHandle
, useLayoutEffect
,useDebugValue
。能夠去 這裏 瞭解更多的用法。
該例子是 Dan 在 React Conf 上的例子,算是很是有表明性的了:
視頻地址:React Today and Tomorrow and 90% Cleaner React With Hooks
同時這裏有一些 Hooks,看看實現和所解決的問題能更深的去了解 Hooks 的魅力:react hooks codesandbox
useEffect 能夠覆蓋 componentDidMount,ComponentDidUpdate 和 componentWillUnmount 三個生命週期的操做,但從某種意義上說,實現 componentWillUnMount 的操做是有點讓人窒息的,若是是一個沒看過文檔的人,絕對不知道要這麼操做。
useEffect(() => {
// cDM or cDU
return () => {
// cWU
}
})
複製代碼
React Hook 在內部實現上是使用 xxx,由於使用 React Hook 有兩個限制條件
React Team 爲此增長了 eslint 插件:eslint-plugin-react-hooks,算是變通的去解決問題吧。
這裏的數組,從某種意義上說叫 tuple 會更加容器理解一些,惋惜 JavaScript 中目前還沒這種概念。
同時,若是返回的是個 Object 又會怎麼樣呢?
let { state: name, setState: setName } = useState('Name');
let { state: surname, setState: setSurname } = useState('Surname');
複製代碼
shouldComponentUpdate
使用 useMemo
便可,參考:How to memoize calculations?。
同時 Hooks 的寫法避免的 Class Component 建立時大量建立示例和綁定事件處理的開銷,外加用 Hooks 的寫法能夠避免 HOC 或者 renderProps 產生深層嵌套,對 React 來講處理起來會更加輕鬆。
getSnapshotBeforeUpdate
和 componentDidCatch
目前覆蓋不到
若是有其餘問題,不妨去 React Hooks FQA 看看,大機率裏面涵蓋了你想知道的問題。
原文地址:github.com/rccoder/blo… (去這交流更方便哦~)