hook!

在react conf 2018上,react發佈了一個新的提案hook。穩定的正式版可能要等一兩個月以後才能出來,目前能夠在v16.7.0-alpha上試用到rfc上各類提問。css

那麼這個hook究竟是個什麼呢,官方的定義是這樣的html

Hooks are a new feature proposal that lets you use state and other React features without writing a class.react

這是一個比class更直觀的新寫法,在這個寫法中react組件都是純函數,沒有生命週期函數,但能夠像class同樣擁有state,能夠由effect觸發生命週期更新,提供一種新的思路來寫react。(雖然官方再三聲明咱們絕對沒有要拿掉class的意思,但hook將來的目標是覆蓋全部class的應用場景)git

其實在看demo演示的時候我是十分抗拒的,沒有生命週期函數的react是個什麼黑魔法,雖然代碼變得乾淨了很多,但寫法實在是發生了很大的轉變,有種脫離掌控的不安全感,我甚至有點懷疑我能不能好好debug。github

演示的最後dan的結束語是這樣的redux

hook表明了咱們對react將來的願景,也是咱們用來推進react前進的方法。所以咱們不會作大幅的重寫,咱們會讓舊的class模式和新的hook模式共存,因此咱們能夠一塊兒慢慢的接納這個新的react。api

我接觸react已經四年了,第一次接觸它的時候,我第一個想問的是,爲何要用jsx。第二個想問的是,爲何要用這個logo,畢竟咱們又不是叫atom,也不是什麼物理引擎。如今我想到了了一個解釋,原子的類型和屬性決定了事物的外觀和表現,react也是同樣的,你能夠把界面劃分爲一個個獨立的組件,這些組件(component)的類型(type)和屬性(props)決定了最終界面的外觀和表現。諷刺的是,原子一直被認爲是不可分的,因此當科學家第一次發現原子的時候認爲這就是最小的單元,直到後來在原子中發現了電子,實際上電子的運動更能決定原子能作什麼。hook也是同樣的,我不認爲hook是一個新的react特性,相反的,我認爲hook能讓我更直觀的瞭解react的基本特性像是state、context、生命週期。hook能更直觀的表明react,它解釋了組件內部是如何工做的,我認爲它被遺落了四年,當你看到react的logo,能夠看到電子一直環繞在那裏,hook也是,它一直在這裏。數組

因而我決定乾了這杯安利。安全

試了幾個比較基本的api寫了幾個demo,代碼在 github.com/lllbahol/re…, 徹底的api還請參考官方文檔 reactjs.org/docs/hooks-…bash

api

基本的hook有三個

  • useState(至關於state)
  • useEffect(至關於componentDidUpdate, componentDidMount, componentWillUnmount)
  • useContext(至關於Context api)

useState

const [state, setState] = useState(initialState);

import { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製代碼

在這裏react組件就是一個簡單的function

  • useState(initialState)也是一個函數,定義了一個state,初始值爲initialState,返回值是一個數組,0爲state的值,1爲setState的方法。

  • 當state發生變化時,函數組件刷新。

  • 能夠useState屢次來定義多個state,react會根據調用順序來判斷。

你必定也寫過一個龐大的class, 有一堆handler函數,由於要setState因此不能挪到組件外面去,而後render函數就被擠出了頁面,每次想看render都要把頁面滾到底下。

如今由於useState是函數,因此它能夠被挪到組件外面,連帶handler一塊兒,下面是一個具體一點的表單例子。

import React, { useState } from 'react';

// 表單組件,有name, phone兩個輸入框。
export default () => {
  const name = useSetValue('hello');
  const phone = useSetValue('120');
  return (
    <React.Fragment>
      <Item {...name} />
      <br />
      <Item {...phone} />
    </React.Fragment>
  );
}

// controlled input component
const Item = ({ value, setValue }) => (
  <React.Fragment>
    <label>{value}</label>
    <br />
    <input value={value} onChange={setValue} />
  </React.Fragment>
);

// 能夠將state連同handler function一塊兒挪到組件外面。
// 甚至能夠export出去,讓其餘組件也能使用這個state邏輯
const useSetValue = (initvalue) => {
  const [value, setValue] = useState(initvalue);
  const handleChange = (e) => {
    setValue(e.target.value);
  }
  return {
    value,
    setValue: handleChange,
  };
}

複製代碼

useEffect

這個api可讓你在函數組件中使用反作用(use side effects),常見的會產生反作用的方式有獲取數據,更新dom,綁定事件監聽等,render只負責渲染,通常會等到dom加載好以後再去調用這些反作用方法。

useEffect(didUpdate/didMount);

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
複製代碼

useEffect能夠接受兩個參數

  • 第一個參數爲一個effect函數,effect函數在每次組件render以後被調用,至關於componentDidUpdate和componentDidMount兩個生命週期之和。effect函數能夠返回一個clear effect函數,會在下一次的effect函數執行以前執行,原來componentWillUnmount裏執行的東西均可以交給它。調用順序是:render(dom加載完成) => prevClearUseEffect => useEffect

  • 第二個參數是一個數組,只有當數組傳入的值發生變化時,effect纔會執行。

上面的寫法若是用class實現的話應該是下面這樣的。咱們按時間前後將一個會產生反作用的函數的第1次調用、第2-n次調用、卸載分紅3截,實際上它們老是一一對應出現的,應該是一個總體。

componentDidMount() {
  this.subscription = props.source.subscribe();
}

componentDidUpdate() {
  this.subscription = props.source.subscribe();
}

componentWillUnmount () {
  subscription.unsubscribe();
}
複製代碼

具體案例能夠看一個輪播組件的demo

import React, { useState, useEffect } from 'react';
import './index.css';

const IMG_NUM = 3;

export default () => {
  const [index, setIndex] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  useEffect(() => {
    // 每次組件刷新時觸發effect, 至關cDM cDU
    if (isPlaying) {
      const timeout = setTimeout(() => {
        // 改變state, 刷新組件
        handleNext();
      }, 2000);
      // 返回清除effect的回調函數, 在每次effect調用完以後,若是有則執行
      return () => clearTimeout(timeout);
    }
    // 若是不想每次render以後都調一次effect, 可使用第二個參數做爲篩選條件
  }, [index, isPlaying]);

  const handleNext = () => {
    setIndex((index + 1) % IMG_NUM);
  }
  const handlePrev = () => {
    setIndex((index - 1 + IMG_NUM) % IMG_NUM);
  }
  const handlePause = () => {
    setIsPlaying(!isPlaying);
  };
  return (
    <div>
      <div className="img">{index}</div>
      <button onClick={handlePrev}>prev</button>
      <button onClick={handlePause}>pause</button>
      <button onClick={handleNext}>next</button>
    </div>
  )
}
複製代碼

useContext

const context = useContext(Context);

若是對react比較熟悉的話,應該用過Context這個api,用於在組件之間傳遞數據。useContext接受一個context對象(React.createContext生成),返回context.Consumer中得到的值。

export const Context = React.createContext(null);

function Parent() {
  const someValue = 'haha';
  return (
    <Context.Provider value={someValue}>
      <DeepTree>
       	<DeepChild />
      </DeepTree>
    </Context.Provider>
  );
}
複製代碼
function DeepChild() {
  const someValue = useContext(Context);
  return (<div>{someValue}</div>)
}

複製代碼

16.7以前的Consumer寫法是render props

function DeepChild() {
  return (
    <Context.Consumer>
      {
        (someValue) => <div>{someValue}</div>
      }
    </Context.Consumer>
  )
}
複製代碼

彷佛還能忍受,可是可是,爲了不沒必要要的刷新通常推薦用多個Context來傳遞刷新週期不一樣的數據,所以按原來的render-props寫法很容易陷入多重嵌套地獄(wrapper-hell),頗有可能你真正的渲染代碼在十幾個縮進後面纔開始出現。繼代碼上下滾問題以後咱們又出現了代碼左右滾問題。

<Consumer1>
  {
    (value1) => (
      <Consumer2>
        {
          (value2) => (
            ...
          )
        }
      </Consumer2>
    )
  }
</Consumer1>

// 我怎麼尚未被同事打死🤦

複製代碼

useReducer

還有一堆高級hook

其中有一個useReducer

就是你們熟悉的那個redux裏的reducer,來段模板代碼讓你們回憶一下。

const mapStateToProps = createStructuredSelector({
	...
});

const mapDispatchToProps = (dispatch) => ({
  ...
});

const withReducer = injectReducer({ ... });

const withConnect = connect(mapStateToProps, mapDispatchToProps);

export default compose(withReducer, withConnect)(Component);
複製代碼

以上的這些,使用了useReducer以後都沒有了。

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, {count: initialCount});
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'reset'})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
複製代碼

我還用useReducer實現了一個todo的demo,代碼分了好幾個文件就不放上來了 github.com/lllbahol/re…

爲何要用hook

除了上面提到的,還有官方羅列出來的一些時常會在寫class時遇到的麻煩

  • class組件間不能複用與state關聯的代碼,hook能夠作到這一點。
  • 複雜而龐大的class組件很難被理解,hook可以讓你把組件拆成更小的獨立單元
  • 理解class是一件困難的事,不管是對人仍是對開發工具而言都是這樣。好比class裏面的this指向的是組件,在箭頭函數寫法出來以前,咱們不得不手動綁定this到調用函數的對象上。

總的來講

用react也很久了,工程越寫越複雜,組件間的數據傳遞是一個很大的問題,從傳統的傳回調函數,到跨多層多組件共享數據的時候使用redux,後來嫌模板代碼太多又本身封了一層render-props結果掉進wrapper嵌套地獄的坑裏,Context出來的時候開心了一下子而後發現依然在坑裏。寫是能寫的,就是恐懼,每寫一層,個人代碼就又縮進了三個tab,離被同事打死又前進三步。

useContext,useReducer的用法讓我想到了高階,不一樣的是能夠直接用變量接住而不是掛在props上,所以不用考慮props名衝突問題,但能達到高階一層層包裹數據的效果。

從現有的文檔來看,新的api很是的多,一些是咱們熟悉的用法一些則是徹底新的東西,且暫時還沒能覆蓋全部生命週期場景(好比getDeriveStateFromProps),但不着急,能夠一步一步來。

hook正式版發佈以後我還會來更新一次這個文檔,在工程里正式使用一段時間以後會再更新一次,先奶一口。

參考

  1. www.youtube.com/watch?v=dpw… 官方介紹hook的視頻
  2. reactjs.org/docs/hooks-… 官方文檔
  3. reactjs.org/docs/hooks-… 一些常見問題的官方解答
相關文章
相關標籤/搜索