React新特性全解(下)– 一文讀懂Hooks

blank

React新特性全解(下)-- 一文讀懂Hooks

前言

這篇文章主要講Hooks,如果你想了解React 16的其他新特性,請移步React 16新特性全解(上)React 16新特性全解(中)

v16.8 Hooks

Hooks是什麼?

我們知道,functional component在使用的時候有一些限制,比如需要生命週期、state的時候就不能用functional component。而有了Hooks,你就可以在funtional component裡,使用class component的功能:props,state,context,refs,和生命週期函數等等。

雖然Hooks已經有要取代正宮class的趨勢了,但是react目前沒有計劃拋棄class,所以不要慌,你還是可以跟往常一樣使用class。

在真正介紹Hook之前,還是一樣先來了解為什麼要引入Hooks?其實不單單是為了給functional component賦於class component的功能。

還有下面的問題:

1.組件之間很難復用邏輯

之前如果我們需要復用邏輯,常用的兩種方式是render propshigher-order components 。但是這兩種方式都需要你重構代碼,所以比較麻煩。

最重要的是,用這兩種方式的話,在React Devtools裡,會看到很多的嵌套組件。

blank

在這個圖可以看到Header外層套著很多嵌套組件。

2.複雜組件很難理解

在之前的class component裡,我們的生命週期函數里通常放著不相關的代碼,而相關的代碼確要放在不同的生命週期函數里。這樣說可能有點繞,我們來看一個具體的例子。

classAppextendsReact.component{componentDidMount(){window.addEventListener('scroll',()=>{console.log('a')})this.fetchData();}componentDidUpdate(){this.fetchData();}componentWillUnmount(){window.removeEventListener('scroll',()=>{console.log('a')})}render(){return<div>ddd</div>}}

這應該是我們平時會經常寫的代碼。在componentDidMount裡做事件綁定,獲取數據。在componentWillUnMount裡解綁事件。

這樣做的問題是:componentDidMount裝著的代碼都是不相關的,而相關聯的事件綁定以及事件解綁,分散在componentDidMount 跟componentWillUnMount裡。這樣如果組件的邏輯越寫越複雜之後,就會變得很難維護易出bug。

後面講Effect Hook的時候,我會介紹這個問題用Hooks怎麼解決。

3.class比較難學

React團隊發現class是初學者學習React的大障礙。要學習class component,你必須要知道幾點:

  1. this在JS是如何工作的(光是這個就夠繞的)
  2. 記得綁定事件
  3. 了解state,props,state以及從上而下的數據流
  4. functional component跟class component的區別,如何使用它們

如何使用

理解了Hooks誕生的原因,接著來看看要如何使用。

假設我需要實現一個功能,點擊app時候,count數目加一。用class的形式實現就是這樣的:

Class:

classExampleextendsReact.Component{constructor(props){super(props);this.state={count:0};}render(){return(<div><p>Youclicked{this.state.count}times</p><buttononClick={()=>this.setState({count:this.state.count+1})}>Clickme</button></div>);}}

Hooks:

如果需要用Hooks實現,變成

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> ); }

演示地址

在這裡demo中。 useState就是Hook。我們通過它來在function component裡加入state數據。

兩者對比如下:

1.定義state變量

Class:

classExampleextendsReact.Component{constructor(props){super(props);this.state={count:0};}

Hooks:

importReact,{useState}from'react';functionExample(){// 通过useState这个Hooks定义state变量:count。并且通过useState给count赋初始值0,只在初始化时候使用一次
const[count,setCount]=useState(0);

在function component裡,我們是沒有this的。所以沒辦法向Class那樣用this來創建state,這時候Hooks閃亮登場。

通過useState這個hooks我們可以定義count這個state變量。由Hooks定義的state變量不一定要是object,可以是string、number。傳入的內容相當於給變量賦初始值。

function ExampleWithManyStates() { // Declare multiple state variables! const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); // ... }

2.渲染state

Class:

<p>Youclicked{this.state.count}times</p>

Hooks:

<p>Youclicked{count}times</p>

可以不需要用this,直接使用count

3.更新state

Class:

<buttononClick={()=>this.setState({count:this.state.count+1})}>Clickme</button>

Hooks:

<buttononClick={()=>setCount(count+1)}>Clickme</button>

我們可以看到const [count, setCount] = useState(0); useState返回兩個參數,一個是當前state的值,還有一個其實是一個函數,用來改變state的值,就是setCount。

類似setState,但是不同的是,它不會將舊的state跟新的state合併在一起,而是覆蓋式的重寫state的值。

說完了functional component裡面如何使用state之後,我們再來看如何用Effect Hook來取代生命週期。

一般我們都會在生命週期componentDidMount, componentDidUpdate與componentWillUnmount中做一些副作用的操作,例如:獲取數據,訂閱,手動改變DOM。而在hooks裡,這些生命週期函數都被統一成一個方法useEffect。

下面我們來舉個例子:

importReact,{useState,useEffect}from'react';functionExample(){const[count,setCount]=useState(0);// Similar to componentDidMount and componentDidUpdate:useEffect(()=>{// Update the document title using the browser APIdocument.title=`You clicked${count}times`;});return(<div><p>Youclicked{count}times</p><buttononClick={()=>setCount(count+1)}>Clickme</button></div>);}

在這個例子中,我們在useEffect 裡完成了副作用的操作。 demo演示

Effects Hooks 就在functional component裡, 所以它可以直接訪問props跟state。 React在每次render之後都會調用effect,包括第一次render。

但是這裡還遺留兩個問題

1、我們在開篇說到,class component有個問題就是生命週期函數里的代碼都是不相關的,而相關的代碼確要被打散在不同的生命週期函數里。這個問題用Hooks的話就可以解決。

比如綁定、解綁事件,在使用class的時候,在componentDidMount裡監聽了一個事件,之後需要在componentWillMount裡給它解綁。

用Hook只需要在useEffect一個函數就可以做到。它可以通過返回一個函數來專門做清除的工作,代碼如下:

importReact,{useState,useEffect}from'react';functionFriendStatus(props){const[isOnline,setIsOnline]=useState(null);functionhandleStatusChange(status){setIsOnline(status.isOnline);}useEffect(()=>{ChatAPI.subscribeToFriendStatus(props.friend.id,handleStatusChange);//在這裡返回一個函數來做這件事return()=>{ChatAPI.unsubscribeFromFriendStatus(props.friend.id,handleStatusChange);};});if(isOnline===null){return'Loading...';}returnisOnline?'Online':'Offline';}

在這個case中,unsubscribeFromFriendStatus不僅僅會在組件unmount的時候調用,同時在重新渲染的時候也會調用。

如果你只想useEffect只在mount與unmount時候調用,需要這樣傳一個[] 作為第二個參數。

useEffect(()=>{ChatAPI.subscribeToFriendStatus(props.friend.id,handleStatusChange);return()=>{ChatAPI.unsubscribeFromFriendStatus(props.friend.id,handleStatusChange);};},[]);

2、有時候我們並不想每次state的改變,都去調用useEffect。在class裡,會這樣做

componentDidUpdate(prevProps,prevState){if(prevState.count!==this.state.count){document.title=`You clicked ${this.state.count} times`;}}

在Hooks,可以通過給useEffect傳入第二個參數,即它關注的state變量,來做到這件事。

例如:當count改變時候,才去調用useEffect。

useEffect(()=>{document.title=`You clicked ${count} times`;},[count]);// Only re-run the effect if count changes

注意事項:

  1. Hooks只可以在頂層使用。也就是說它不能寫在循環體,條件渲染,或者嵌套function裡
  2. 只可以在React的function組件裡使用Hooks。

說了那麼多,總結以下,其實Hooks就是幫助我們在function component裡直接使用原來class component才有的特性。

以上,我們只接觸到了兩種hooks,還有更多比如useContext, useReducer, useCallback ,感興趣的同學可以自己看下~

最後是使用Hooks的一些建議:

一些建議

  1. 是否需要用hoooks重構以前的代碼?

No,react團隊不推薦用hooks重新寫一遍。推薦做法是新的組件可以直接使用,然後需要改老組件代碼的時候在順便改就行了。

2.支持Hooks的工具

React DevTools對hooks已經支持。同時強烈推薦安裝hooks的eslint校驗庫eslint-plugin-react-hooks

Reac團隊下一步計劃

因為現在React Hooks還不能完全cover所有class的功能,雖然他們已經很相近了。目前為止Hooks不支持兩個不常使用的API getSnapshotBeforeUpdate 跟componentDidCatch,但是React團隊正在努力,未來會支持的。

參考文檔:

1.

What do you think?

Written by marketer

blank

React 16 新特性全解(中)

blank

設計系統Ops(協同)