經過對比class組件來理解React的hooks特性

前言

hooks是react16.8新增的特性。關於爲何要新增hooks,是由於class的組件會存在如下的一些問題。javascript

  1. 在組件之間複用狀態邏輯很難
  2. 複雜組件變得難以理解
  3. 難以理解的 class

這些點就不詳細贅述了,這篇文章的重點是介紹hooks。html

useState

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];前端

useState是用來取代class組件中的setState。useState接受一個值做爲state的初始值,返回一個數組,數組第一個值是state,第二個值是一個用於修改state的函數。useState能夠屢次使用。栗子以下:vue

import React, { useState } from 'react';

const App = function() {
    const [count, setCount] = useState(0);
    // 另外一個沒有使用的state
    const [other, setOther] = useState('hello');
    
    return (
      <button onClick={() => setCount(count+1)}>{ count }</button>
    );  
}

它對應的class組件的代碼以下:java

import React, { Component } from 'react';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0,
            other: 'hello'
        };
    }

    setCount(count: number) {
        this.setState({
            count
        });
    }

    setOther(other: string) {
        this.setState({
            other
        });
    }

    render() {
        return (
            <button onClick={() => this.setCount(this.state.count + 1)}>
                {this.state.count}
            </button>
        );
    }
}

useReducer

useReducer和useState的用處是同樣的,不一樣的是useReducer處理的是更加複雜的場景,例如三級聯動選擇框,須要同時對多個state進行聯動處理。能夠看作是一個小的redux,使用方式和redux也基本一致。react

const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return {count: state.count + 1};
        case 'decrement':
            return {count: state.count - 1};
        default:
            return state;
    }
}

const App = function() {
    const [state, dispatch] = useReducer(reducer, { count: 0 });

    return (
        <>
            { state.count }
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        </>
    );
}

useEffect

function useEffect(effect: EffectCallback, deps?: DependencyList): void;
type EffectCallback = () => (void | (() => void | undefined));redux

Effect Hook 可讓你在函數組件中執行反作用操做。何爲反作用操做呢?例如:請求接口、操做dom、定時器等等。咱們可使用useEffect模擬React中的一些生命週期函數。但須要注意的是:請不要把這種對應劃上等號。api

從他的定義中就能夠知道,useEffect有兩個參數,第一個參數是一個回調函數(能夠返回一個函數或者不返回內容),第二個參數是依賴項。數組

模擬componentDidMount:dom

// 在didMount中請求一個接口
useEffect(() => {
    fetch('http://xxx.com/api/list');
}, []);

模擬componentDidUpdate

// 不傳入依賴時,會在每次渲染以後執行
useEffect(() => {
    console.log('update');
});

模擬componentWillUnmount: useEffect的回調函數返回一個函數。

// 向document添加一個click事件,並在下次從新渲染前將其卸載。
useEffect(() => {
    const handler = () => { console.log('click'); }
    document.addEventListener('click', handler);

    return () => {
        document.removeEventListener('click', handler);
    }
}, []);

模擬shouldComponentUpdate: 這個我以爲應該和vue的watch更加相近。

// 在第一個加載和count發生變化時纔會觸發
useEffect(() => {
    // ...
}, [count]);

useMemo

若是你對vue比較熟悉的話,useMemo能夠看作是computed,能夠看作是須要手動添加依賴的計算屬性,在依賴的值不發生改變時,返回的值是不變的。

const App = function() {
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');

    const name = useMemo(() => {
        return firstName + ' ' + lastName
    }, [firstName, lastName]);

    return (
        <>
            姓:<input value={firstName} onChange={(e) => setFirstName(e.target.value)} />
            名:<input value={lastName} onChange={(e) => setLastName(e.target.value)} />
            姓名:{ name }
        </>
    );
}

useCallback

咱們知道,函數組件中props或者state改變時,都會從新執行當前函數,那麼若是咱們直接在函數組件中定義處理函數的話,屬性更改會觸發修改,那麼每次修改都會致使處理函數的從新定義,這樣會形成極大的性能耗損。

useCallback即是用來處理這個問題,在依賴項不改變的狀況下,函數不會從新定義。

const App = function() {
    const change = useCallback((e) => {
        console.log(e);
    }, []);

    return (
        <>
            <input onChange={change} />
        </>
    );
}

useRef

ref的做用我想不少前端小夥伴應該並不陌生,能夠用來操做dom或者實例。而useRef除了用於操做DOM以外,在一些其餘的方面也頗有用,例如咱們須要把一個定時器的值全局保存,但又不但願這個值的變化觸發render,就像是咱們在使用class組件時的this.timer = setInterval(...)

// 例如咱們設置了定時獲取數據的interval,在點擊某個按鈕以後就中止定時器
const App = () => {
    const inputEl = useRef(null); // 這個用來獲取dom,綁定到input上以後,就能夠經過inputEl.current進行訪問
    const timer = useRef(null);

    useEffect(() => {
        timer.current = setInterval(...);
        return () => {
            clearInterval(timer.current);
        }
    }, []);

    return (
        <div>
            <button onClick={() => clearInterval(timer.current)}>Stop</button>
            <input ref={inputEl} />
        </div>
    );
}

useContext

從字面上也能夠看出來,useContext就是爲了方便使用context(通常用於祖孫組件的數據通訊)的。須要注意的是調用了 useContext 的組件總會在 context 值變化時從新渲染。

const themes = {
    light: {
        color: '#fff',
        background: '#f12'
    }
};

const ThemeContext = createContext(themes.light);

const Child = () => {
    const theme = useContext(ThemeContext);

    return (
        <div style={{color: theme.color, background: theme.background}}>Child</div>
    );
}

const App = () => {
    return (
        <div>
            <ThemeContext.Provider value={themes.light}>
                <Child />
            </ThemeContext.Provider>
        </div>
    );
}

自定義Hook

自定義hook通常用來HOC作比較,他們都是用來對組件的邏輯進行復用。hook與HOC不一樣的是:

  1. 因爲HOC機制的緣由(包裹一層組件)會改變原組件的HTML結構,hook則不會;
  2. 使用過HOC的小夥伴應該都體驗過使用了多個HOC以後,徹底不知道某個prop是來自於哪裏,也就咱們常說的props來源不明。不方便調試;
  3. HOC可能會覆蓋固有的props。

自定義hooks或使用hooks時須要注意的是:

  1. 自定義hook必須以use開頭,這有利於react對自定義hook的規則進行檢查;
  2. 不要在循環,條件或嵌套函數中調用 Hook;
  3. 不要在普通的 JavaScript 函數中調用 Hook。

栗子:在多個組件中須要實時獲取鼠標的位置

// 定義hook
const useMousePosition = () => {
    const [location, setLocation] = useState({x: 0, y: 0});

    useEffect(() => {
        function move(e) {
            setLocation({ x: e.screenX, y: e.screenY });
        }

        document.addEventListener('mousemove', move);

        return () => {
            document.removeEventListener('mousemove', move);
        };
    }, []);

    return location;
}

// 使用hook
const App = () => {
    const position = useMousePosition();

    return (<div>{ position.x } { position.y }</div>);
}

能夠看到,咱們的就對代碼進行了複用,也避免了上述關於HOC的問題。

小結

hooks的引入讓咱們很方便的使用函數組件來編寫代碼,不過關於OOP和FP的爭議也一直存在,因此是否使用hooks也須要童鞋們好好考量。

參考:React Hook API 索引

相關文章
相關標籤/搜索