這篇文章主要介紹了React Hooks的一些實踐用法和場景,遵循我我的一向的思(tao)路(是什麼-爲何-怎麼作)html
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.vue
簡單來講,上面這段官腔大概翻(xia)譯(shuo)就是告訴咱們class可以作到的老子用hooks基本能夠作到,放棄抵抗吧,少年!react
其實按照我本身的見解:React Hooks是在函數式組件中的一類以use爲開頭命名的函數。 這類函數在React內部會被特殊對待,因此也稱爲鉤子函數。編程
Hooks只能用於Function Component, 其實這麼說不嚴謹,我更喜歡的說法是建議只在於Function Component使用Hooksredux
React 約定,鉤子一概使用use前綴命名,便於識別,這沒什麼可說的,要被特殊對待,就要服從必定的規則api
Hooks做爲鉤子,存在與每一個組件相關聯的「存儲器單元」的內部列表。 它們只是咱們能夠放置一些數據的JavaScript對象。 當你像使用useState()同樣調用Hook時,它會讀取當前單元格(或在第一次渲染時初始化它),而後將指針移動到下一個單元格。 這是多個useState()調用每一個get獨立本地狀態的方式數組
解決爲何要使用hooks的問題,我決定從hooks解決了class組件的哪些痛點和hooks更符合react的組件模型兩個方面講述。性能優化
class組件它香,可是暴露的問題也很多。Redux 的做者 Dan Abramov總結了幾個痛點:bash
- Huge components that are hard to refactor and test.
- Duplicated logic between different components and lifecycle methods.
- Complex patterns like render props and higher-order components.
第一點:難以重構和測試的巨大組件。 若是讓你在一個代碼行數300+的組件里加一個新功能,你不慌嗎?你嘗試過註釋一行代碼,結果就跑不了或者邏輯錯亂嗎?若是須要引入redux或者定時器等那就更慌了~~網絡
第二點:不一樣組件和生命週期方法之間的邏輯重複。 這個難度不亞於蜀道難——難於上青天!固然對於簡單的邏輯可能經過HOC和render props來解決。可是這兩種解決辦法有兩個比較致命的缺點,就是模式複雜和嵌套。
第三點:複雜的模式,好比render props和 HOC。 不得不說我在學習render props的時候不由發問只有在render屬性傳入函數纔是render props嗎?好像我再任意屬性(如children)傳入函數也能實現同樣的效果; 一開始使用HOC的時候打開React Develops Tools一看,Unknown是什麼玩意~看着一層層的嵌套,我也是無能爲力。
以上這三點均可以經過Hooks來解決(瘋狂吹捧~)
咱們知道,react強調單向數據流和數據驅動視圖,說白了就是組件和自上而下的數據流能夠幫助咱們將UI分割,像搭積木同樣實現頁面UI。這裏更增強調組合而不是嵌套,class並不能很完美地詮釋這個模型,可是hooks配合函數式組件卻能夠!函數式組件的純UI性配合Hooks提供的狀態和反作用能夠將組件隔離成邏輯可複用的獨立單元,邏輯分明的積木他不香嗎!
別問,問就是文檔,若是不行的話,請熟讀並背誦文檔...
可是(萬事萬物最怕But), 既然是實踐,就得僞裝實踐過,下面就說說本人的簡單實踐和想法吧。
// in class component
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'Hello',
age: '18',
rest: {},
}
}
...
}
// in function component
function Demo(props) {
const initialState = {
name: 'Hello',
age: '18',
rest: {},
}
const [state, setState] = React.useState(initialState)
...
}
複製代碼
// 這麼實現很粗糙,能夠配合useRef和useCallback,但即便這樣也不徹底等價於componentDidMount
function useDidMount(handler){
React.useEffect(()=>{
handler && handler()
}, [])
}
複製代碼
// count更新到1就不動了
function Counter() {
const [count, setCount] = React.useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
...
}
複製代碼
其實,在class component環境下思考問題更像是在特定的時間點作特定的事情,例如咱們會在constructor中初始化state,會在組件掛載後(DidMount)請求數據等,會在組件更新後(DidUpdate)處理狀態變化的邏輯,會在組件卸載前(willUnmount)清除一些反作用
然而在hooks+function component環境下思考問題應該更趨向於特定的功能邏輯,以功能爲一個單元去思考問題會有一種豁然開朗的感受。例如改變document的title、網絡請求、定時器... 對於hooks,只是爲了實現特定功能的工具而已
你會發現大部分你想實現的特定功能都是有反作用(effect)的,能夠負責任的說useEffect是最干擾你心智模型的Hooks, 他的心智模型更接近於實現狀態同步,而不是響應生命週期事件。還有一個可能會影響你的就是每一次渲染都有它本身的資源,具體表現爲如下幾點
- 每一次渲染都有它本身的Props 和 State:當咱們更新狀態的時候,React會從新渲染組件。每一次渲染都能拿到獨立的狀態值,這個狀態值是函數中的一個常量(也就是會說,在任意一次渲染中,props和state是始終保持不變的)
- 每一次渲染都有它本身的事件處理函數:和props和state同樣,它們都屬於一次特定的渲染,即使是異步處理函數也只能拿到那一次特定渲染的狀態值
- 每個組件內的函數(包括事件處理函數,effects,定時器或者API調用等等)會捕獲某次渲染中定義的props和state(建議在分析問題時,將每次的渲染的props和state都常量化)
// 實現計數功能
const [count, setCount] = React.useState(0);
setCount(count => count + 1)
// 展現用戶信息
const initialUser = {
name: 'Hello',
age: '18',
}
const [user, setUser] = React.useState(initialUser)
複製代碼
// 修改上面count更新到1就不動了,方法1
function Counter() {
const [count, setCount] = React.useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);
...
}
// 修改上面count更新到1就不動了,方法2( 與方法1的區別在哪裏 )
function Counter() {
const [count, setCount] = React.useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
...
}
複製代碼
關於useEffect, 牆裂推薦Dan Abramov的A Complete Guide to useEffect,一篇支稱整篇文章架構的深度好文!
/** 修改需求:每秒不是加多少能夠由用戶決定,能夠看做不是+1,而是+step*/
// 方法1
function Counter() {
const [count, setCount] = React.useState(0);
const [step, setStep] = React.useState(1);
useEffect(() => {
let id = setInterval(() => {
setCount(count => count + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
...
}
// 方法2( 與方法1的區別在哪裏 )
const initialState = {
count: 0,
step: 1,
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'tick') {
return { ...state, count: count + step };
} else if (action.type === 'step') {
return { ...state, step: action.step };
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
...
}
複製代碼
說這個以前,先說一說若是你要在FP裏面使用函數,你要先要思考有替代方案嗎?
方案1: 若是這個函數沒有使用組件內的任何值,把它提到組件外面去定義
方案2:若是這個函數只是在某個effect裏面用到,把它定義到effect裏面
若是沒有替代方案,就是useCallback出場的時候了。
// 場景1:依賴組件的query
function Search() {
const [query, setQuery] = React.useState('hello');
const getFetchUrl = React.useCallback(() => {
return `xxxx?query=${query}`;
}, [query]);
useEffect(() => {
const url = getFetchUrl();
}, [getFetchUrl]);
...
}
// 場景2:做爲props
function Search() {
const [query, setQuery] = React.useState('hello');
const getFetchUrl = React.useCallback(() => {
return `xxxx?query=${query}`;
}, [query]);
return <MySearch getFetchUrl={getFetchUrl} />
}
function MySearch({ getFetchUrl }) {
useEffect(() => {
const url = getFetchUrl();
}, [getFetchUrl]);
...
}
複製代碼
// 存儲不變的引用類型
const { current: stableArray } = React.useRef( [1, 2, 3] )
<Comp arr={stableArray} />
// 存儲dom引用
const inputEl = useRef(null);
<input ref={inputEl} type="text" />
// 存儲函數回調
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}
複製代碼
// 此栗子來自文檔
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
複製代碼
// 此栗子來自文檔
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
複製代碼
說是彩蛋,實際上是補充說明~~
hooks除了要以use開頭,還有一條很很很很重要的規則,就是hooks只容許在react函數的頂層被調用(這裏牆裂推薦Hooks必備神器eslint-plugin-react-hooks)
考慮到出於研(gang)究(jing)精神的你可能會問,爲何不能這麼用,我偏要的話呢?若是我是hooks開發者,我會堅決果斷地說出門右轉,有請下一位開發者!固然若是你想知道爲何這麼約定地話,仍是值得探討一下的。其實這個規則就是保證了組件內的全部hooks能夠按照順序被調用。那麼爲何順序這麼重要呢,不能夠給每個hooks加一個惟一的標識,這樣不就能夠隨心所欲了嗎?我之前一直都這麼想過直到Dan給了我答案,簡單點說就是爲了hooks最大的閃光點——custom-hooks
給個人感受就是custom-hooks是一個真正詮釋了React的編程模型的組合的魅力。你能夠不看好它,但它確實有過人之處,至少它呈現出思想讓我越想越上頭~~以致於vue3.0也借鑑了他的經驗,推出了Vue Hooks。反手推薦一下react conf 2018的custom-hooks。
// 修改頁面標題
function useDocumentTitle(title) {
useEffect (() => {
document.title = title;
}, [title]);
}
// 使用表單的input
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
return {
value,
onChange: handleChange
};
}
複製代碼
最後拋出兩個討論的小問題。
React Hooks沒有缺點嗎?
- 確定是有的,給我最直觀的感覺就是使人又愛又恨的閉包
- 不斷地重複渲染會帶來必定的性能問題,須要人爲的優化
上面說了寫了不少的setInterval的代碼,能夠考慮封裝成一個custom-hooks?
- 能夠考慮封裝成useInterva,關於封裝仍是牆裂推薦Dan的 Making setInterval Declarative with React Hooks
- 若是有一堆特定的功能hooks,是否是徹底能夠經過組裝各類hooks完成業務邏輯的開發,例如網絡請求、綁定事件監聽等
本人能力有限,若是有哪裏說得不對的地方,歡迎批評指正!
真的真的最後,怕你錯過,再次安利Dan Abramov的A Complete Guide to useEffect,一篇支稱整篇文章架構的深度好文!