在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
基本的hook有三個
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,
};
}
複製代碼
這個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>
)
}
複製代碼
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>
// 我怎麼尚未被同事打死🤦
複製代碼
還有一堆高級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…
除了上面提到的,還有官方羅列出來的一些時常會在寫class時遇到的麻煩
用react也很久了,工程越寫越複雜,組件間的數據傳遞是一個很大的問題,從傳統的傳回調函數,到跨多層多組件共享數據的時候使用redux,後來嫌模板代碼太多又本身封了一層render-props結果掉進wrapper嵌套地獄的坑裏,Context出來的時候開心了一下子而後發現依然在坑裏。寫是能寫的,就是恐懼,每寫一層,個人代碼就又縮進了三個tab,離被同事打死又前進三步。
useContext,useReducer的用法讓我想到了高階,不一樣的是能夠直接用變量接住而不是掛在props上,所以不用考慮props名衝突問題,但能達到高階一層層包裹數據的效果。
從現有的文檔來看,新的api很是的多,一些是咱們熟悉的用法一些則是徹底新的東西,且暫時還沒能覆蓋全部生命週期場景(好比getDeriveStateFromProps),但不着急,能夠一步一步來。
hook正式版發佈以後我還會來更新一次這個文檔,在工程里正式使用一段時間以後會再更新一次,先奶一口。