Hooks 顧名思義,字面意義上來講就是 React 鉤子的概念。經過一個 case 咱們對 React Hooks 先有一個第一印象。html
假設如今要實現一個計數器的組件。若是使用組件化的方式,咱們須要作的事情相對更多一些,好比說聲明 state,編寫計數器的方法等,並且須要理解的概念可能更多一些,好比 Javascript 的類的概念,this 上下文的指向等。java
示例react
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
state = {
count: 0
}
countUp = () => {
const { count } = this.state;
this.setState({ count: count + 1 });
}
countDown = () => {
const { count } = this.state;
this.setState({ count: count - 1 });
}
render() {
const { count } = this.state;
return (
<div> <button onClick={this.countUp}>+</button> <h1>{count}</h1> <button onClick={this.countDown}>-</button> </div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
使用 React Hooks,咱們能夠這麼寫。git
示例github
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
return (
<div> <button onClick={() => setCount(count + 1)}>+</button> <h1>{count}</h1> <button onClick={() => setCount(count - 1)}>-</button> </div>
)
}
ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
經過上面的例子,顯而易見的是 React Hooks 提供了一種簡潔的、函數式(FP)的程序風格,經過純函數組件和可控的數據流來實現狀態到 UI 的交互(MVVM)。編程
useState 是最基本的 API,它傳入一個初始值,每次函數執行都能拿到新值。redux
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
return (
<div> <button onClick={() => setCount(count + 1)}>+</button> <h1>{count}</h1> <button onClick={() => setCount(count - 1)}>-</button> </div>
)
}
ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
須要注意的是,經過 useState 獲得的狀態 count,在 Counter 組件中的表現爲一個常量,每一次經過 setCount 進行修改後,又從新經過 useState 獲取到一個新的常量。數組
useReducer 和 useState 幾乎是同樣的,須要外置外置 reducer (全局),經過這種方式能夠對多個狀態同時進行控制。仔細端詳起來,其實跟 redux 中的數據流的概念很是接近。app
import { useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function reducer(state, action) {
switch (action.type) {
case 'up':
return { count: state.count + 1 };
case 'down':
return { count: state.count - 1 };
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 1 })
return (
<div> {state.count} <button onClick={() => dispatch({ type: 'up' })}>+</button> <button onClick={() => dispatch({ type: 'down' })}>+</button> </div>
);
}
ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
一個相當重要的 Hooks API,顧名思義,useEffect 是用於處理各類狀態變化形成的反作用,也就是說只有在特定的時刻,纔會執行的邏輯。
import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
function Example() {
const [count, setCount] = useState(0);
// => componentDidMount/componentDidUpdate
useEffect(() => {
// update
document.title = `You clicked ${count} times`;
// => componentWillUnMount
return function cleanup() {
document.title = 'app';
}
}, [count]);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
ReactDOM.render(<Example />, document.getElementById('root')); 複製代碼
useMemo 主要用於渲染過程優化,兩個參數依次是計算函數(一般是組件函數)和依賴狀態列表,當依賴的狀態發生改變時,纔會觸發計算函數的執行。若是沒有指定依賴,則每一次渲染過程都會執行該計算函數。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
複製代碼
import { useState, useMemo } from 'react';
import ReactDOM from 'react-dom';
function Time() {
return <p>{Date.now()}</p>;
}
function Counter() {
const [count, setCount] = useState(0);
const memoizedChildComponent = useMemo((count) => {
return <Time />; }, [count]); return ( <div> <h1>{count}</h1> <button onClick={() => setCount(count + 1)}>+</button> <div>{memoizedChildComponent}</div> </div> ); } ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
context 是在外部 create ,內部 use 的 state,它和全局變量的區別在於,若是多個組件同時 useContext,那麼這些組件都會 rerender,若是多個組件同時 useState 同一個全局變量,則只有觸發 setState 的當前組件 rerender。
import { useState, useContext, createContext } from 'react';
import ReactDOM from 'react-dom';
// 1. 使用 createContext 建立上下文
const UserContext = new createContext();
// 2. 建立 Provider
const UserProvider = props => {
let [username, handleChangeUsername] = useState('');
return (
<UserContext.Provider value={{ username, handleChangeUsername }}>
{props.children}
</UserContext.Provider>
);
};
// 3. 建立 Consumer
const UserConsumer = UserContext.Consumer;
// 4. 使用 Consumer 包裹組件
const Pannel = () => (
<UserConsumer>
{({ username, handleChangeUsername }) => (
<div>
<div>user: {username}</div>
<input onChange={e => handleChangeUsername(e.target.value)} />
</div>
)}
</UserConsumer>
);
const Form = () => <Pannel />;
const App = () => (
<div>
<UserProvider>
<Form />
</UserProvider>
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
複製代碼
import { useState, useContext, createContext } from 'react';
import ReactDOM from 'react-dom';
// 1. 使用 createContext 建立上下文
const UserContext = new createContext();
// 2. 建立 Provider
const UserProvider = props => {
let [username, handleChangeUsername] = useState('');
return (
<UserContext.Provider value={{ username, handleChangeUsername }}>
{props.children}
</UserContext.Provider>
);
};
const Pannel = () => {
const { username, handleChangeUsername } = useContext(UserContext); // 3. 使用 Context
return (
<div>
<div>user: {username}</div>
<input onChange={e => handleChangeUsername(e.target.value)} />
</div>
);
};
const Form = () => <Pannel />;
const App = () => (
<div>
<UserProvider>
<Form />
</UserProvider>
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
複製代碼
useRef 返回一個可變的 ref 對象,其 .current 屬性初始化爲傳遞的參數(initialValue)。返回的對象將持續整個組件的生命週期。事實上 useRef 是一個很是有用的 API,許多狀況下,咱們須要保存一些改變的東西,它會派上大用場的。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
複製代碼
說到狀態共享,最簡單和直接的方式就是經過 props 逐級進行狀態的傳遞,這種方式耦合於組件的父子關係,一旦組件嵌套結構發生變化,就須要從新編寫代碼,維護成本很是昂貴。隨着時間的推移,官方推出了各類方案來解決狀態共享和代碼複用的問題。
高階組件源於函數式編程,因爲 React 中的組件也能夠視爲函數(類),所以天生就能夠經過 HOC 的方式來實現代碼複用。能夠經過屬性代理和反向繼承來實現,HOC 能夠很方便的操控渲染的結果,也能夠對組件的 props / state 進行操做,從而能夠很方便的進行復雜的代碼邏輯複用。
import React from 'react';
import PropTypes from 'prop-types';
// 屬性代理
class Show extends React.Component {
static propTypes = {
children: PropTypes.element,
visible: PropTypes.bool,
};
render() {
const { visible, children } = this.props;
return visible ? children : null;
}
}
// 反向繼承
function Show2(WrappedComponent) {
return class extends WrappedComponent {
render() {
if (this.props.visible === false) {
return null;
} else {
return super.render();
}
}
}
}
function App() {
return (
<Show visible={Math.random() > 0.5}>hello</Show>
);
}
複製代碼
Redux 中的狀態複用是一種典型的 HOC 的實現,咱們能夠經過 compose 來將數據組裝到目標組件中,固然你也能夠經過裝飾器的方式進行處理。
import React from 'react';
import { connect } from 'react-redux';
// use decorator
@connect(state => ({ name: state.user.name }))
class App extends React.Component{
render() {
return <div>hello, {this.props.name}</div>
}
}
// use compose
connect((state) => ({ name: state.user.name }))(App);
複製代碼
顯而易見,renderProps 就是一種將 render 方法做爲 props 傳遞到子組件的方案,相比 HOC 的方案,renderProps 能夠保護原有的組件層次結構。
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
// 與 HOC 不一樣,咱們可使用具備 render prop 的普通組件來共享代碼
class Mouse extends React.Component {
static propTypes = {
render: PropTypes.func.isRequired
}
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div>
);
}
}
function App() {
return (
<div style={{ height: '100%' }}> <Mouse render={({ x, y }) => ( // render prop 給了咱們所須要的 state 來渲染咱們想要的 <h1>The mouse position is ({x}, {y})</h1> )}/> </div> ); } ReactDOM.render(<App/>, document.getElementById('root')); 複製代碼
經過組合 Hooks API 和 React 內置的 Context,從前面的示例能夠看到經過 Hook 讓組件之間的狀態共享更清晰和簡單。
function FunctionalComponent () {
const [state1, setState1] = useState(1);
const [state2, setState2] = useState(2);
const [state3, setState3] = useState(3);
}
複製代碼
{
memoizedState: 'foo',
next: {
memoizedState: 'bar',
next: {
memoizedState: 'bar',
next: null
}
}
}
複製代碼
函數組件天生就是支持 props 的,基本用法上和 class 組件沒有太大的差異。須要注意的兩個區別是:
經過一個示例來理解一下 capture value,咱們能夠經過 useRef 來規避 capture value,由於 useRef 是可變的。
class 組件 | 函數組件 | |
---|---|---|
建立狀態 | this.state = {} | useState, useReducer |
修改狀態 | this.setState() | set function |
更新機制 | 異步更新,屢次修改合併到上一個狀態,產生一個副本 | 同步更新,直接修改成目標狀態 |
狀態管理 | 一個 state 集中式管理多個狀態 | 多個 state,能夠經過 useReducer 進行狀態合併(手動) |
性能 | 高 | 若是 useState 初始化狀態須要經過很是複雜的計算獲得,請使用函數的聲明方式,不然每次渲染都會重複執行 |
useEffect 在每一次渲染都會被調用,稍微包裝一下就能夠做爲這些生命週期使用;
一般咱們優化組件性能時,會優先採用純組件的方式來減小單個組件的渲染次數。
class Button extends React.PureComponent {}
複製代碼
React Hooks 中能夠採用 useMemo 代替,能夠實現僅在某些數據變化時從新渲染組件,等同於自帶了 shallowEqual 的 shouldComponentUpdate。
因爲默認狀況下,每一次修改狀態都會形成從新渲染,能夠經過一個不使用的 set 函數來當成 forceUpdate。
const forceUpdate = () => useState(0)[1];
複製代碼
因爲每個 Hooks API 都是純函數的概念,使用時更關注輸入 (input) 和輸出 (output),所以能夠更好的經過組裝函數的方式,對不一樣特性的基礎 Hooks API 進行組合,創造擁有新特性的 Hooks。
import { useEffect } from 'react';
const useDidMount = fn => useEffect(() => fn && fn(), []);
export default useDidMount;
複製代碼
import { useEffect, useRef } from 'react';
const useDidUpdate = (fn, conditions) => {
const didMoutRef = useRef(false);
useEffect(() => {
if (!didMoutRef.current) {
didMoutRef.current = true;
return;
}
// Cleanup effects when fn returns a function
return fn && fn();
}, conditions);
};
export default useDidUpdate
複製代碼
在講到 useEffect 時已經說起過,其容許返回一個 cleanup 函數,組件在取消掛載時將會執行該 cleanup 函數,所以 useWillUnmount 也能輕鬆實現~
import { useEffect } from 'react';
const useWillUnmount = fn => useEffect(() => () => fn && fn(), []);
export default useWillUnmount;
複製代碼
// lib/onHover.js
import { useState } from 'react';
const useHover = () => {
const [hovered, set] = useState(false);
return {
hovered,
bind: {
onMouseEnter: () => set(true),
onMouseLeave: () => set(false),
},
};
};
export default useHover;
複製代碼
import { useHover } from './lib/onHover.js';
function Hover() {
const { hovered, bind } = useHover();
return (
<div> <div {...bind}> hovered: {String(hovered)} </div> </div>
);
}
複製代碼
// lib/useField.js
import { useState } from 'react';
const useField = (initial) => {
const [value, set] = useState(initial);
return {
value,
set,
reset: () => set(initial),
bind: {
value,
onChange: e => set(e.target.value),
},
};
}
export default useField;
複製代碼
import { useField } from 'lib/useField';
function Input {
const { value, bind } = useField('Type Here...');
return (
<div> input text: {value} <input type="text" {...bind} /> </div> ); } function Select() { const { value, bind } = useField('apple') return ( <div> selected: {value} <select {...bind}> <option value="apple">apple</option> <option value="orange">orange</option> </select> </div> ); } 複製代碼
React Hooks 提供爲狀態管理提供了新的可能性,儘管咱們可能須要額外去維護一些內部的狀態,可是能夠避免經過 renderProps / HOC 等複雜的方式來處理狀態管理的問題。Hooks 帶來的好處以下:
事實上,經過定製各類場景下的自定義 Hooks,能讓咱們的應用程序更方便和簡潔,組件的層次結構也能保證無缺,還有如此使人愉悅的函數式編程風格,Hooks 在 React 16.8.0 版本已經正式發佈穩定版本,如今開始用起來吧!!!