React+Hook

動(dòng)機(jī)

一直在考慮為什么要使用,我們明明使用state使用的好好的掖桦,那會(huì)發(fā)明HOOK的動(dòng)機(jī)是什么呢限府?

  • 在組件中復(fù)用狀態(tài)邏輯很復(fù)雜
    使用Hooks猴鲫,可以從組件中提取有狀態(tài)邏輯,以便可以獨(dú)立測(cè)試并復(fù)用谣殊。Hooks允許在不更改組件層次結(jié)構(gòu)的情況下復(fù)用有狀態(tài)的邏輯。 這樣可以輕松地在許多組件之間共享Hooks牺弄。
  • 復(fù)雜組件變得難以理解
    使用state會(huì)導(dǎo)致相關(guān)的邏輯被切分姻几,一些相關(guān)的代碼卻被分割在不同的函數(shù)文件中,這樣導(dǎo)致代碼的可讀性變?nèi)酰琀ook 將組件中相互關(guān)聯(lián)的部分拆分成更小的函數(shù)(比如設(shè)置訂閱或請(qǐng)求數(shù)據(jù))蛇捌,而并非強(qiáng)制按照生命周期劃分抚恒,也可以使用reducer來(lái)進(jìn)行相關(guān)邏輯的復(fù)用
  • 難以理解的 class
    class使用的時(shí)候,要先了解JavaScript的this使用络拌,而且還要進(jìn)行事件的綁定俭驮,對(duì)于函數(shù)組件與 class 組件的差異也存在分歧,甚至還要區(qū)分兩種組件的使用場(chǎng)景春贸。hook 使用非 class 的情況下可以使用更多的 React 特性混萝。 從概念上講,React 組件一直更像是函數(shù)萍恕。而 Hook 則擁抱了函數(shù)逸嘀,同時(shí)也沒(méi)有犧牲 React 的精神原則。

概覽

hooks其實(shí)就是一種特殊的函數(shù)允粤,這個(gè)函數(shù)有勾住狀態(tài)和生命周期的功能崭倘,但我們希望在組件中使用狀態(tài)的時(shí)候,我們就會(huì)使用hook用來(lái)代替class中的state类垫。Hook 不能在 class 組件中使用司光。

hook和class的對(duì)比

使用class的形式來(lái)寫組件的方法

import React from 'react'
class Person extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            username: 'kim'
        }
    }
    componentDidMount() {
        console.log('組件掛載后做的操作')
    }
    componentWillUnmount() {
        console.log('組件將要卸載')
    }
    componentDidUpdate(prevProps, prevState) {
        // 當(dāng)username發(fā)生改變的時(shí)候進(jìn)行渲染
        if (prevState.username !== this.state.username) {
            console.log('組件更新后的操作')
        }
    }
    render() {
        return (<Input onChange={(event) => this.setState({ username: event.target.value })} />)
    }
}

用hook來(lái)寫函數(shù)組件

import React, { useEffect, useState } from 'react'

export const Person = () => {
    const [name, setName] = useState('');

    useEffect(() => {
        console.log('組件掛載后要做的操作');
        return () => {
            console.log('組件卸載要做的操作')
        }
    }, [])

    useEffect(() => {
        console.log('當(dāng)name組件發(fā)生改變的時(shí)候顯示的樣式')
    }, [name])

    return (<div>
        <p>歡迎 {name}</p>
        <input type="text" placeholder="input a username" onChange={(event) => setName(event.target.value)}></input>
    </div>
    )
}

API

useState
  • 參數(shù):是一個(gè)常量,組件初始化的時(shí)候就會(huì)進(jìn)行定義
  • 參數(shù):是一個(gè)函數(shù)悉患,只有開(kāi)始渲染的時(shí)候函數(shù)才會(huì)執(zhí)行
  • 返回值:長(zhǎng)度為2的數(shù)組残家,第一項(xiàng)是返回的state的值,第二項(xiàng)是改變?cè)搒tate的函數(shù)
    用來(lái)初始化狀態(tài)购撼,在函數(shù)的內(nèi)部調(diào)用跪削,為函數(shù)添加內(nèi)部的state,useState會(huì)返回一對(duì)值迂求,分別是要更新的狀態(tài)和一個(gè)讓你更新他的函數(shù)碾盐,useState唯一的參數(shù)就是初始化的值。
// 聲明多個(gè) state 變量揩局!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
//  聲明一個(gè)叫 "count" 的 state 變量,初始值為0毫玖,后續(xù)通過(guò)setCount改變它能讓視圖重新渲染
export const Count = () => {
    const [count, setCount] = useState(0);
    // initiState 只會(huì)在組件初渲染的時(shí)候起作用,后續(xù)的渲染會(huì)被忽略不計(jì)
    const [value,setValue] = setValue(()=>{
        const initialCount = someExpensiveComputation(props);
        return initialState;
    })

    return (<div>
        <p> 你點(diǎn)擊了{(lán)count}次</p>
        <button onClick={() => setCount(count + 1)}> Click me</button>
    </div>)
}
useEffect
  • 參數(shù):第一個(gè)是含有副作用的命令式的代碼凌盯,
  • 參數(shù):第二個(gè)參數(shù)是一個(gè)數(shù)組付枫,數(shù)組中的值用來(lái)控制第一個(gè)參數(shù)中的函數(shù)是否執(zhí)行,當(dāng)?shù)诙€(gè)參數(shù)不傳的時(shí)候驰怎,是每一次有數(shù)據(jù)更新的時(shí)候阐滩,執(zhí)行第一個(gè)參數(shù)中的函數(shù),相當(dāng)于class組件中的componentDidMount和componentDidupdate的生命周期县忌。
export const Count = () => {
    const [count, setCount] = useState(0);
    // 功能類似componentDidMount and componentDidUpdate
    useEffect(() => {
        document.title = `You clicked ${count} times`;
    });

    // 只有count改變時(shí)才會(huì)執(zhí)行
    useEffect(() => {
        document.title = `You clicked ${count} times`;
    }, [count]);

    // 組件掛載時(shí)只執(zhí)行一次
    useEffect(() => {
        console.log("只執(zhí)行一次掂榔,類似componentDidMount")
    }, []);

    return (<div>
        <p> 你點(diǎn)擊了{(lán)count}次</p>
        <button onClick={() => setCount(count + 1)}> Click me</button>
    </div>)
}

可以在組件渲染后實(shí)現(xiàn)各種不同的副作用继效。有些副作用可能需要清除,比如說(shuō)全局設(shè)定鼠標(biāo)監(jiān)聽(tīng)事件装获。

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});
useContext

當(dāng)hook接受到一個(gè)context的對(duì)象的時(shí)候并返回context的當(dāng)前值瑞信,當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 <MyContext.Provider> 的 value prop 決定。

// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)穴豫。
const ThemeContext = React.createContext(themes.light);

const App = () => {
    // 在root中傳入這個(gè)值凡简,使用context進(jìn)行包裹,便于后續(xù)取值
    return (
        <ThemeContext.Provider value={themes.dark}>
            <ToolBar />
        </ThemeContext.Provider>
    )
}

// 中間層不用傳遞參數(shù)
const ToolBar = () => {
    return (
        <ThemedButton />
    )
}

//  在使用層直接進(jìn)行使用就可以了
const ThemedButton = () => {
    const theme = useContext(ThemeContext)
    return (
        <button style={{ background: theme.background, color: theme.foreground }}>
            I am styled by theme context!
        </button>
    )
}

Context 主要應(yīng)用場(chǎng)景在于很多不同層級(jí)的組件需要訪問(wèn)同樣一些的數(shù)據(jù)精肃。請(qǐng)謹(jǐn)慎使用秤涩,因?yàn)檫@會(huì)使得組件的復(fù)用性變差。

我們看一下上面的代碼以class的形式編寫肋杖,會(huì)是什么樣子的:

// Context 可以讓我們無(wú)須明確地傳遍每一個(gè)組件溉仑,就能將值深入傳遞進(jìn)組件樹(shù)。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)状植。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一個(gè) Provider 來(lái)將當(dāng)前的 theme 傳遞給以下的組件樹(shù)浊竟。
    // 無(wú)論多深,任何組件都能讀取這個(gè)值津畸。
    // 在這個(gè)例子中振定,我們將 “dark” 作為當(dāng)前的值傳遞下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間的組件再也不必指明往下傳遞 theme 了肉拓。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 讀取當(dāng)前的 theme context后频。
  // React 會(huì)往上找到最近的 theme Provider,然后使用它的值暖途。
  // 在這個(gè)例子中卑惜,當(dāng)前的 theme 值為 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

useReducer
  • 第一個(gè)參數(shù)是reducer純函數(shù)
  • 第二個(gè)參數(shù)是初始的state
  • 第三個(gè)參數(shù)可以修改初始state驻售,將初始 state 設(shè)置為 init(initialArg)

是useState的一種替代場(chǎng)景露久,如果我們都是使用useState就會(huì)散落到程序的各個(gè)地方,我們通過(guò)代碼比較一下二者的差別:

我們使用setState來(lái)完成這一段代碼

const LoginPage = () => {
    const [name, setName] = useState(''); // 用戶名
    const [err, setError] = useState(''); //錯(cuò)誤信息
    const [pwd, setPwd] = useState(''); // 密碼
    const [isLoading, setIsLoading] = useState(false); // 發(fā)送請(qǐng)求之后數(shù)據(jù)是否已經(jīng)返回
    const [isLogged, setIsLogged] = useState(false) // 是否已經(jīng)登陸

    const login = (e) => {
        e.preventDefault();
        setError('');
        setIsLoading(true);
        login({ name, pwd }).then(() => {
            setIsLoggedIn(true); // 標(biāo)示登陸成功
            setIsLoading(false); // 標(biāo)示loading完成
        }).catch((error) => {
            setError(error.message);
            setName('');
            setPwd('');
            setIsLoading(false);
        })
    }
    return(<div>jsx的界面</div>)
}

我們發(fā)現(xiàn)setState散落到方法的各個(gè)地方欺栗,導(dǎo)致代碼的已讀性大大的減弱了毫痕,我們?cè)倏匆幌?我們使用useReducer來(lái)進(jìn)行這個(gè)操作:

const loginReducer = (state, action) => {
    switch (action.type) {
        case 'login':
            return {
                ...state,
                isLoading: false,
                error: ''
            }
        case 'success':
            return {
                ...state,
                isLoggedIn: true,
                isLoading: false,
            }
        case 'error':
            return {
                ...state,
                error: action.payload.error,
                name: '',
                pwd: '',
                isLoading: false
            }
        default:
            return state;
    }
}

function LoginPage() {
    const [state, dispatch] = useReducer(loginReducer, initState);
    const { name, pwd, isLoading, error, isLoggedIn } = state;
    const login = (event) => {
        event.preventDefault();
        dispatch({ type: 'login' });
        login({ name, pwd })
            .then(() => {
                dispatch({ type: 'success' });
            })
            .catch((error) => {
                dispatch({
                    type: 'error',
                    payload: { error: error.message }
                });
            });
    }
    return (
        <div></div>
        //  返回頁(yè)面JSX Element
    )
}
  • 我們發(fā)現(xiàn)使用reducer寫的代碼,雖然代碼長(zhǎng)度變長(zhǎng)了迟几,但是代碼的可讀性大大的增加了消请,我們可以更加清晰的看到這么寫代碼的意圖
  • LoginPage不需要關(guān)心如何處理這幾種行為,那是loginReducer需要關(guān)心的类腮,表現(xiàn)和業(yè)務(wù)分離臊泰。
  • 所有的state處理都集中到了一起,使得我們對(duì)state的變化更有掌控力蚜枢,同時(shí)也更容易復(fù)用state邏輯變化代碼因宇,比如在其他函數(shù)中也需要觸發(fā)登錄error狀態(tài)七婴,只需要dispatch({ type: 'error' })。

總結(jié)一下使用state的場(chǎng)景:

  • 如果你的state是一個(gè)數(shù)組或者對(duì)象
  • 如果你的state變化很復(fù)雜察滑,經(jīng)常一個(gè)操作需要修改很多state
  • 如果你希望構(gòu)建自動(dòng)化測(cè)試用例來(lái)保證程序的穩(wěn)定性
  • 如果你需要在深層子組件里面去修改一些狀態(tài)(關(guān)于這點(diǎn)我們下篇文章會(huì)詳細(xì)介紹)
  • 如果你用應(yīng)用程序比較大,希望UI和業(yè)務(wù)能夠分開(kāi)維護(hù)
useCallback
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末修肠,一起剝皮案震驚了整個(gè)濱河市贺辰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嵌施,老刑警劉巖饲化,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吗伤,居然都是意外死亡吃靠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門足淆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巢块,“玉大人,你說(shuō)我怎么就攤上這事巧号∽迳荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵丹鸿,是天一觀的道長(zhǎng)越走。 經(jīng)常有香客問(wèn)我,道長(zhǎng)靠欢,這世上最難降的妖魔是什么廊敌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮门怪,結(jié)果婚禮上骡澈,老公的妹妹穿的比我還像新娘。我一直安慰自己薪缆,他們只是感情好秧廉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著拣帽,像睡著了一般疼电。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上减拭,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天蔽豺,我揣著相機(jī)與錄音,去河邊找鬼拧粪。 笑死修陡,一個(gè)胖子當(dāng)著我的面吹牛沧侥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魄鸦,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宴杀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拾因?” 一聲冷哼從身側(cè)響起旺罢,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绢记,沒(méi)想到半個(gè)月后扁达,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蠢熄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年跪解,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片签孔。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叉讥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骏啰,到底是詐尸還是另有隱情节吮,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布判耕,位于F島的核電站透绩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏壁熄。R本人自食惡果不足惜帚豪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望草丧。 院中可真熱鬧狸臣,春花似錦、人聲如沸昌执。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)懂拾。三九已至煤禽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岖赋,已是汗流浹背檬果。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人选脊。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓杭抠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恳啥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偏灿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容