"Unlearn what you have learned" -- Yodajavascript
有一天我在逛Medium的時候,忽然發現了一篇介紹React Hooks的文章,我認真看了一遍後,計劃好好了解一下它。html
在學習Hooks以前,官網上說了Hooks是徹底可用的(v16.8.0),並無破壞性變動,並且徹底向後兼容,與其說是一種新API,不如說是React Team他們把React更核心的操做數據與UI的能力挖掘了出來。java
嗯美滋滋~學完應該能夠在工做項目裏用了!開始學習吧!react
其實Hooks主要經常使用的能夠有如下幾個:redux
useState
useEffect
useContext
useMemo
useRef
useReducer
useCallback
列舉的以上這幾個,其實已經算是比較經常使用的,尤爲是前兩個,接下來就會介紹它們部分幾個的使用。數組
useState
這個鉤子其實對應的是咱們以前class Component
裏的this.setState
。緩存
useState
傳參表明默認值,能夠是原始值,也能夠是對象、數組,因此其實表達能力很豐富。useState
調用後返回是一對值,對應當前的值和更新這個值的函數,用數組解構的方式獲取很簡潔。useState
在一個函數組件裏能夠屢次使用。useState
和this.setState
區別之處在於,前者每次更新後state
都是新值,換而言之實際上是不可變數據的概念。然後者使用後,其實更新state
部分的值,引用自己並沒有改變。簡單使用以下示例。bash
import React, { useState } from 'react';
export default function StateHook() {
const [count, useCount] = useState(0);
return (
<> <p>You clicked {count} times</p> <button onClick={() => useCount(count + 1)}>Click me</button> </> ); } 複製代碼
useEffect
這個鉤子勢必是咱們經常使用的。架構
componentDidMount
和componentDidUpdate
的這兩個生命週期鉤子組合的效果。那麼它的調用時機大概是每次渲染結束後,因此不會阻塞組件渲染。useEffect
通常用於實現設置數據請求、監聽器等有反作用的功能,傳入的第一個參數函數A1用於設置反作用,而是傳入的這個函數能夠返回一個函數A2用於取消函數A1的反作用。這兩個函數的React調用它們時機分別在於,註冊反作用的函數A1在當次渲染結束後當即執行,取消反作用的函數A2在下次渲染開始以前當即執行。再次強調,這麼設計的理由仍是爲了避免阻塞組件渲染。useEffect
第二個參數用於設置反作用的依賴數組。什麼意思?思惟靈活的同窗已經想到了,若是每次渲染都執行反作用,有可能形成性能浪費,那麼能夠經過告訴React,這個鉤子依賴某些props
或者states
,在這些依賴不發生改變時,這個反作用不會再重複執行。在如下的例子中,能夠傳空數組,告訴React該反作用什麼也不依賴,那麼它只會在第一次渲染時執行一次(可是通常不推薦這麼作)。若是不傳第二個參數,則意味着每次渲染都必然執行一次,此時應當注意內存泄露。useEffect
後,一個反作用的註冊監聽與對應的取消註冊邏輯所有放在了一塊兒,對比與以往的分別在componentDidMount
、componentDidUpdate
、componentWillUnmount
裏分散同一反作用的邏輯。useEffect
的使用更有吸引力和說服力了。import React, { useState, useEffect } from 'react';
export default function EffectHook({ dep }) {
const [width, setWidth] = useState(window.innerWidth);
function handleWindowResize() {
const w = window.innerWidth;
setWidth(w);
}
useEffect(() => {
window.addEventListener('resize', handleWindowResize);
return () => {
window.removeEventListener('resize', handleWindowResize);
};
},
// deps
[]
);
return (
<> <p>window.innerWidth: {width}</p> </> ); } 複製代碼
這個鉤子仍是和原有的Context.Provider
、Context.Consumer
同樣的理解便可。用法示例以下,理解方便,再也不贅述。app
import React, { useContext } from 'react';
export const souliz = {
name: 'souliz',
description: 'A normal human named by his cat.'
};
export const UserContext = React.createContext(souliz);
export default function ContextHook() {
const context = useContext(UserContext);
return (
<>
<p>UserContext name: {context.name}</p>
<p>UserContext description: {context.description}</p>
</>
);
}
複製代碼
有時候咱們會遇到一個極耗性能的函數方法,但因爲依賴了函數組件裏一些狀態值,又不得不放在其中。那麼若是咱們每次渲染都去重複調用的發,組件的渲染必然會十分卡頓。
所以寫了如下示例驗證,一個計算斐波那契的函數(衆所周知的慢),讀者能夠拷貝這段代碼,註釋useMemo
那一行,使用直接計算來看,點擊按鈕觸發組件從新渲染,會發現很卡頓(固然了),那麼此時useMemo
做用就發揮出來了,其實理解上仍是和原有的React.memo
同樣,可用於緩存一下計算緩慢的函數,若是依賴沒有發生改變,則重複使用舊值。前提必然是這個函數是一個純函數,不然必然會引起問題。
(useCallback
其實也和useMemo
道理相似,不過它解決的問題其實若是依賴不改變,使用舊的函數引用,在useEffect
的依賴是函數時,可使用useCallback
的特性來避免重複觸發反作用的發生,所以再也不贅述useCallback
)
import React, { useState, useMemo } from 'react';
let fib = n => (n > 1 ? fib(n - 1) + fib(n - 2) : n);
let renders = 0;
export default function MemoHook() {
const defaultInput = 37;
const [input, setInput] = useState(defaultInput);
const [time, setTime] = useState(0);
const value = useMemo(() => fib(input), [input]);
// 來來來,看看不使用Memo的後果就是卡頓
// const value = fib(input);
return (
<>
<p>fib value is {value}</p>
<input
type="number"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={() => setTime(time + 1)}>Trigger render {time}</button>
<footer>render times: {renders++}</footer>
</>
);
}
複製代碼
useRef
這個鉤子須要更通用的理解方式,不一樣於咱們以前使用的React.createRef()
,這個鉤子用於建立的是一個引用對象,那麼能夠用於突破useState
所帶來的侷限。什麼意思呢?useState
每次渲染都是新的值,也就是下面示例中,若是我點擊3次按鈕,分別更新了值觸發5次組件從新渲染,那麼經過延時5秒後獲取current值如示例二,若是須要在某些操做中獲取組件最新的某些state
是最新的值的時候,useRef
能夠派上大用場。
import React, { useRef, useEffect, useState } from 'react';
export default function RefHook() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
latestCount.current = count;
useEffect(() => {
setTimeout(() => {
console.log(`Ref: You clicked ${latestCount.current} times`);
console.log(`state: You clicked ${count} times`);
}, 5000);
});
return (
<> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </> ); } 複製代碼
Ref: You clicked 3 times
state: You clicked 1 times
Ref: You clicked 3 times
state: You clicked 2 times
Ref: You clicked 3 times
state: You clicked 3 times
複製代碼
相信同窗們都使用過redux,React Team考慮到這種使用方式常見,因而設計出來了這麼一個鉤子。這樣的話其實解決了咱們常見寫redux的多文件跳躍編寫的煩惱,並且十分易於理解。(固然還有比較高級的用法)。如下代碼示例。
import React, { useState, useReducer } from 'react';
const defaultTodos = [
{
id: 1,
text: 'Todo 1',
completed: false
},
{
id: 2,
text: 'Todo 2',
completed: false
}
];
function todosReducer(state, action) {
switch (action.type) {
case 'add':
return [
...state,
{
id: Date.now(),
text: action.text,
completed: false
}
];
case 'complete':
return state.map(todo => {
if (todo.id === action.id) {
todo.completed = true;
}
return todo;
});
default:
return state;
}
}
export default function ReducerHook() {
const [todos, dispatch] = useReducer(todosReducer, defaultTodos);
const [value, setValue] = useState('');
function handleTextChange(e) {
setValue(e.target.value);
}
function handleAddTodo() {
if (value === '') {
return;
}
dispatch({
type: 'add',
text: value
});
setValue('');
}
function handleCompleteTodo(id) {
dispatch({
type: 'complete',
id
});
}
return (
<>
<section>
<input
type="text"
onChange={handleTextChange}
value={value}
/>
<button onClick={handleAddTodo}>Add Todo</button>
</section>
<ul className="todos">
{todos.map(todo => (
<ol id={todo.id} key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</span>
<input
type="checkbox"
disabled={todo.completed}
onClick={() => handleCompleteTodo(todo.id)}
/>
</ol>
))}
</ul>
</>
);
}
複製代碼
其實useReducer
的原理大概也能夠這麼來實現。
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
複製代碼
相信學完這些Hooks的使用後,許多同窗都是心裏充滿了不少疑惑的同時也想要嘗試看看怎麼使用到實際項目了。
固然如今React官方的建議是:
class Component
這些原有API的。eslint-plugin-react-hooks
,用於檢測對於Hooks的不正當使用。(據說create-react-app很快將會加上這個配置)傳統組件的開發有如下幾個侷限:
寫到這裏,文章篇幅已經很長了。一篇文章是說不完Hooks的。學習Hooks的最推薦的實際上是看官網文檔以及Dan Abramov的博文,以及多多動手實踐。
謝謝你們閱讀~~