React系列(二)之 Hook基礎(chǔ)以及在項(xiàng)目中的運(yùn)用

Hook簡(jiǎn)介

? Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性暑中。

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

function Example() {
  // 聲明一個(gè)新的叫做 “count” 的 state 變量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

注意
React 16.8.0 是第一個(gè)支持 Hook 的版本壹瘟。升級(jí)時(shí),請(qǐng)注意更新所有的 package鳄逾,包括 React DOM稻轨。 React Native 從 0.59 版本開始支持 Hook。

什么是Hook

Hook 是一些可以讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù)雕凹。Hook 不能在 class 組件中使用 —— 這使得你不使用 class 也能使用 React殴俱。

例如,useState 是允許你在 React 函數(shù)組件中添加 state 的 Hook(不推薦把你已有的組件全部重寫枚抵,但是你可以在新組件里開始使用 Hook线欲。)

React 內(nèi)置了一些像 useState 這樣的 Hook。你也可以創(chuàng)建你自己的 Hook 來(lái)復(fù)用不同組件之間的狀態(tài)邏輯汽摹。我們會(huì)先介紹這些內(nèi)置的 Hook询筏。

什么時(shí)候我會(huì)用 Hook?

如果你在編寫函數(shù)組件并意識(shí)到需要向其添加一些 state竖慧,以前的做法是必須將其它轉(zhuǎn)化為 class∠犹祝現(xiàn)在你可以在現(xiàn)有的函數(shù)組件中使用 Hook逆屡。

引入Hook對(duì)于當(dāng)前項(xiàng)目的影響

沒有破壞性改動(dòng)

    > 1. **完全可選的。** 你無(wú)需重寫任何已有代碼就可以在一些組件中嘗試 Hook踱讨。但是如果你不想魏蔗,你不必現(xiàn)在就去學(xué)習(xí)或使用 Hook。
    > 2. **100% 向后兼容的痹筛。** Hook 不包含任何破壞性改動(dòng)莺治。
    > 3. **現(xiàn)在可用。** Hook 已發(fā)布于 v16.8.0帚稠。

Hook 不會(huì)影響你對(duì) React 概念的理解谣旁。 恰恰相反,Hook 為已知的 React 概念提供了更直接的 API:props滋早, state榄审,context,refs 以及生命周期杆麸。稍后我們將看到搁进,Hook 還提供了一種更強(qiáng)大的方式來(lái)組合他們。

動(dòng)機(jī)

Hook 解決了編寫和維護(hù)組件中各種各樣看起來(lái)不相關(guān)的問(wèn)題昔头。無(wú)論你正在學(xué)習(xí) React饼问,或每天使用,或者更愿嘗試另一個(gè)和 React 有相似組件模型的框架揭斧,你都可能對(duì)這些問(wèn)題似曾相識(shí)莱革。

  1. 對(duì)于復(fù)雜組件中的處理優(yōu)勢(shì)

我們經(jīng)常維護(hù)一些組件,組件起初很簡(jiǎn)單讹开,但是逐漸會(huì)被狀態(tài)邏輯和副作用充斥盅视。每個(gè)生命周期常常包含一些不相關(guān)的邏輯。例如萧吠,組件常常在 componentDidMountcomponentDidUpdate 中獲取數(shù)據(jù)左冬。但是桐筏,同一個(gè) componentDidMount 中可能也包含很多其它的邏輯纸型,如設(shè)置事件監(jiān)聽,而之后需在 componentWillUnmount 中清除梅忌。相互關(guān)聯(lián)且需要對(duì)照修改的代碼被進(jìn)行了拆分狰腌,而完全不相關(guān)的代碼卻在同一個(gè)方法中組合在一起。如此很容易產(chǎn)生 bug牧氮,并且導(dǎo)致邏輯不一致琼腔。

在多數(shù)情況下,不可能將組件拆分為更小的粒度踱葛,因?yàn)闋顟B(tài)邏輯無(wú)處不在丹莲。這也給測(cè)試帶來(lái)了一定挑戰(zhàn)光坝。同時(shí),這也是很多人將 React 與狀態(tài)管理庫(kù)結(jié)合使用的原因之一甥材。但是盯另,這往往會(huì)引入了很多抽象概念,需要你在不同的文件之間來(lái)回切換洲赵,使得復(fù)用變得更加困難鸳惯。

為了解決這個(gè)問(wèn)題,Hook 將組件中相互關(guān)聯(lián)的部分拆分成更小的函數(shù)(比如設(shè)置訂閱或請(qǐng)求數(shù)據(jù))叠萍,而并非強(qiáng)制按照生命周期劃分芝发。你還可以使用 reducer 來(lái)管理組件的內(nèi)部狀態(tài),使其更加可預(yù)測(cè)苛谷。

  1. 對(duì)于現(xiàn)在正在使用的class組件的替代

    除了代碼復(fù)用和代碼管理會(huì)遇到困難外辅鲸,我們還發(fā)現(xiàn) class 是學(xué)習(xí) React 的一大屏障刀崖。你必須去理解 JavaScript 中 this 的工作方式赚爵,這與其他語(yǔ)言存在巨大差異。還不能忘記綁定事件處理器析藕。沒有穩(wěn)定的語(yǔ)法提案赫蛇,這些代碼非常冗余绵患。大家可以很好地理解 props,state 和自頂向下的數(shù)據(jù)流悟耘,但對(duì) class 卻一籌莫展落蝙。即便在有經(jīng)驗(yàn)的 React 開發(fā)者之間,對(duì)于函數(shù)組件與 class 組件的差異也存在分歧暂幼,甚至還要區(qū)分兩種組件的使用場(chǎng)景筏勒。

    當(dāng)然class也不會(huì)從React中移除。

useState

先看一個(gè)經(jīng)典的計(jì)數(shù)器例子:

import React, { useState } from 'react';

function Example() {
  // 聲明一個(gè)叫 “count” 的 state 變量旺嬉。
  const [count, setCount] = useState(0);
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    const [count3, setCount3] = useState(0);
let num = 0
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

在這里管行,useState 就是一個(gè) Hook 。通過(guò)在函數(shù)組件里調(diào)用它來(lái)給組件添加一些內(nèi)部 state邪媳。React 會(huì)在重復(fù)渲染時(shí)保留這個(gè) state捐顷。useState 會(huì)返回一對(duì)值:當(dāng)前狀態(tài)和一個(gè)讓你更新它的函數(shù),你可以在事件處理函數(shù)中或其他一些地方調(diào)用這個(gè)函數(shù)雨效。它類似 class 組件的 this.setState迅涮,但是它不會(huì)把新的 state 和舊的 state 進(jìn)行合并。

useState 唯一的參數(shù)就是初始 state徽龟。在上面的例子中叮姑,我們的計(jì)數(shù)器是從零開始的,所以初始 state 就是 0据悔。值得注意的是传透,不同于 this.state耘沼,這里的 state 不一定要是一個(gè)對(duì)象 —— 如果你有需要,它也可以是朱盐。這個(gè)初始 state 參數(shù)只有在第一次渲染時(shí)會(huì)被用到耕拷。

聲明多個(gè)State變量

可以在一個(gè)組件內(nèi)多次使用State Hook:

function ExampleWithManyStates() {
  // 聲明多個(gè) state 變量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

數(shù)組解構(gòu)的語(yǔ)法讓我們?cè)谡{(diào)用 useState 時(shí)可以給 state 變量取不同的名字托享。當(dāng)然骚烧,這些名字并不是 useState API 的一部分。React 假設(shè)當(dāng)你多次調(diào)用 useState 的時(shí)候闰围,你能保證每次渲染時(shí)它們的調(diào)用順序是不變的赃绊。后面我們會(huì)再次解釋它是如何工作的以及在什么場(chǎng)景下使用。
useState的使用

還是上面那個(gè)例子羡榴,下面我們寫一個(gè)的class的示例來(lái)對(duì)比一下:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

state 初始值為 { count: 0 } 碧查,當(dāng)用戶點(diǎn)擊按鈕后,我們通過(guò)調(diào)用 this.setState() 來(lái)增加 state.count校仑。
Hook和函數(shù)組件

React的函數(shù)組件:

const Example = (props) => {
  // 你可以在這使用 Hook
  return <div />;
}

或者:

function Example(props) {
  // 你可以在這使用 Hook
  return <div />;
}

之前可能把它們叫做“無(wú)狀態(tài)組件”忠售。但現(xiàn)在我們?yōu)樗鼈円肓耸褂?React state 的能力,所以我們更喜歡叫它”函數(shù)組件”迄沫。

Hook 在 class 內(nèi)部是起作用的稻扬。但你可以使用它們來(lái)取代 class 。

聲明State變量

在 class示例 中羊瘩,我們通過(guò)在構(gòu)造函數(shù)中設(shè)置 this.state{ count: 0 } 來(lái)初始化 count state 為 0.

在函數(shù)組件中泰佳,是沒有this,所以不能分配或讀取this.state尘吗。我們直接在組件中調(diào)用useState Hook:

import React, { useState } from 'react';

function Example() {
  // 聲明一個(gè)叫 “count” 的 state 變量
  const [count, setCount] = useState(0);

調(diào)用useState方法的時(shí)候做了什么逝她?

它定義一個(gè) “state 變量”。我們的變量叫 count睬捶, 但是我們可以叫他任何名字黔宛,比如 banana。這是一種在函數(shù)調(diào)用時(shí)保存變量的方式 —— useState 是一種新方法擒贸,它與 class 里面的 this.state 提供的功能完全相同臀晃。一般來(lái)說(shuō),在函數(shù)退出后變量就就會(huì)”消失”酗宋,而 state 中的變量會(huì)被 React 保留积仗。

useState 需要哪些參數(shù)疆拘?

useState() 方法里面唯一的參數(shù)就是初始 state蜕猫。不同于 class 的是,我們可以按照需要使用數(shù)字或字符串對(duì)其進(jìn)行賦值哎迄,而不一定是對(duì)象回右。在示例中隆圆,只需使用數(shù)字來(lái)記錄用戶點(diǎn)擊次數(shù),所以我們傳了 0 作為變量的初始 state翔烁。(如果我們想要在 state 中存儲(chǔ)兩個(gè)不同的變量渺氧,只需調(diào)用 useState() 兩次即可。)

seState 方法的返回值是什么蹬屹?

返回值為:當(dāng)前 state 以及更新 state 的函數(shù)侣背。這就是我們寫 const [count, setCount] = useState() 的原因。這與 class 里面 this.state.countthis.setState 類似慨默,唯一區(qū)別就是你需要成對(duì)的獲取它們贩耐。

讀取State

當(dāng)我們想在 class 中顯示當(dāng)前的 count,我們讀取 this.state.count

<p>You clicked {this.state.count} times</p>

在函數(shù)中厦取,我們可以直接用 count:

<p>You clicked {count} times</p>

更新State
在 class 中潮太,我們需要調(diào)用 this.setState() 來(lái)更新 count 值:

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
  </button>

在函數(shù)中,我們已經(jīng)有了 setCountcount 變量虾攻,所以我們不需要 this:

<button onClick={() => setCount(count + 1)}>
    Click me
  </button>

useEffect

之前可能已經(jīng)在 React 組件中執(zhí)行過(guò)數(shù)據(jù)獲取铡买、訂閱或者手動(dòng)修改過(guò) DOM。我們統(tǒng)一把這些操作稱為“副作用”霎箍,或者簡(jiǎn)稱為“作用”奇钞。

useEffect 就是一個(gè) Effect Hook,給函數(shù)組件增加了操作副作用的能力漂坏。它跟 class 組件中的 componentDidMount蛇券、componentDidUpdatecomponentWillUnmount 具有相同的用途,只不過(guò)被合并成了一個(gè) API樊拓。

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

function Example() {
  const [count, setCount] = useState(0);

  // 相當(dāng)于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用瀏覽器的 API 更新頁(yè)面標(biāo)題
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

當(dāng)你調(diào)用 useEffect 時(shí)纠亚,就是在告訴 React 在完成對(duì) DOM 的更改后運(yùn)行你的“副作用”函數(shù)。由于副作用函數(shù)是在組件內(nèi)聲明的筋夏,所以它們可以訪問(wèn)到組件的 props 和 state蒂胞。默認(rèn)情況下,React 會(huì)在每次渲染后調(diào)用副作用函數(shù) —— 包括第一次渲染的時(shí)候条篷。

數(shù)據(jù)獲取骗随,設(shè)置訂閱以及手動(dòng)更改 React 組件中的 DOM 都屬于副作用。不管你知不知道這些操作赴叹,或是“副作用”這個(gè)名字鸿染,應(yīng)該都在組件中使用過(guò)它們。

提示

如果你熟悉 React class 的生命周期函數(shù)乞巧,你可以把 useEffect Hook 看做 componentDidMount涨椒,componentDidUpdatecomponentWillUnmount 這三個(gè)函數(shù)的組合。

在 React 組件中有兩種常見副作用操作:需要清除的和不需要清除的。我們來(lái)更仔細(xì)地看一下他們之間的區(qū)別蚕冬。

無(wú)需清除的 effect

有時(shí)候免猾,我們只想在 React 更新 DOM 之后運(yùn)行一些額外的代碼。比如發(fā)送網(wǎng)絡(luò)請(qǐng)求囤热,手動(dòng)變更 DOM猎提,記錄日志,這些都是常見的無(wú)需清除的操作旁蔼。因?yàn)槲覀冊(cè)趫?zhí)行完這些操作之后锨苏,就可以忽略他們了。讓我們對(duì)比一下使用 class 和 Hook 都是怎么實(shí)現(xiàn)這些副作用的

class示例:

在 React 的 class 組件中棺聊,render 函數(shù)是不應(yīng)該有任何副作用的蚓炬。一般來(lái)說(shuō),在這里執(zhí)行操作太早了躺屁,我們基本上都希望在 React 更新 DOM 之后才執(zhí)行我們的操作肯夏。

這就是為什么在 React class 中,我們把副作用操作放到 componentDidMountcomponentDidUpdate 函數(shù)中犀暑⊙被鳎回到示例中,這是一個(gè) React 計(jì)數(shù)器的 class 組件耐亏。它在 React 對(duì) DOM 進(jìn)行操作之后徊都,立即更新了 document 的 title 屬性

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

注意,在這個(gè) class 中广辰,我們需要在兩個(gè)生命周期函數(shù)中編寫重復(fù)的代碼暇矫。

這是因?yàn)楹芏嗲闆r下,我們希望在組件加載和更新時(shí)執(zhí)行同樣的操作择吊。從概念上說(shuō)李根,我們希望它在每次渲染之后執(zhí)行 —— 但 React 的 class 組件沒有提供這樣的方法。即使我們提取出一個(gè)方法几睛,我們還是要在兩個(gè)地方調(diào)用它房轿。

如何使用 useEffect 執(zhí)行相同的操作。

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

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect 做了什么所森?

通過(guò)使用這個(gè) Hook囱持,你可以告訴 React 組件需要在渲染后執(zhí)行某些操作。React 會(huì)保存你傳遞的函數(shù)(我們將它稱之為 “effect”)焕济,并且在執(zhí)行 DOM 更新之后調(diào)用它纷妆。在這個(gè) effect 中,我們?cè)O(shè)置了 document 的 title 屬性晴弃,不過(guò)我們也可以執(zhí)行數(shù)據(jù)獲取或調(diào)用其他命令式的 API掩幢。

為什么在組件內(nèi)部調(diào)用 useEffect逊拍?

useEffect 放在組件內(nèi)部讓我們可以在 effect 中直接訪問(wèn) count state 變量(或其他 props)。我們不需要特殊的 API 來(lái)讀取它 —— 它已經(jīng)保存在函數(shù)作用域中粒蜈。Hook 使用了 JavaScript 的閉包機(jī)制,而不用在 JavaScript 已經(jīng)提供了解決方案的情況下旗国,還引入特定的 React API

useEffect 會(huì)在每次渲染后都執(zhí)行嗎枯怖?

是的,默認(rèn)情況下能曾,它在第一次渲染之后每次更新之后都會(huì)執(zhí)行度硝。(我們稍后會(huì)談到如何控制它。)你可能會(huì)更容易接受 effect 發(fā)生在“渲染之后”這種概念寿冕,不用再去考慮“掛載”還是“更新”蕊程。React 保證了每次運(yùn)行 effect 的同時(shí),DOM 都已經(jīng)更新完畢驼唱。

提示

componentDidMountcomponentDidUpdate 不同藻茂,使用 useEffect 調(diào)度的 effect 不會(huì)阻塞瀏覽器更新屏幕,這讓你的應(yīng)用看起來(lái)響應(yīng)更快玫恳。大多數(shù)情況下辨赐,effect 不需要同步地執(zhí)行。在個(gè)別情況下(例如測(cè)量布局)京办,有單獨(dú)的 useLayoutEffect Hook 供你使用掀序,其 API 與 useEffect 相同。

通過(guò)跳過(guò)effect進(jìn)行性能優(yōu)化

在 class 組件中惭婿,我們可以通過(guò)在 componentDidUpdate 中添加對(duì) prevPropsprevState 的比較邏輯解決:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

這是很常見的需求不恭,所以它被內(nèi)置到了 useEffect 的 Hook API 中。如果某些特定值在兩次重渲染之間沒有發(fā)生變化财饥,你可以通知 React 跳過(guò)對(duì) effect 的調(diào)用换吧,只要傳遞數(shù)組作為 useEffect 的第二個(gè)可選參數(shù)即可:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時(shí)更新

上面這個(gè)示例中,我們傳入 [count] 作為第二個(gè)參數(shù)钥星。這個(gè)參數(shù)是什么作用呢式散?如果 count 的值是 5,而且我們的組件重渲染的時(shí)候 count 還是等于 5打颤,React 將對(duì)前一次渲染的 [5] 和后一次渲染的 [5] 進(jìn)行比較暴拄。因?yàn)閿?shù)組中的所有元素都是相等的(5 === 5),React 會(huì)跳過(guò)這個(gè) effect编饺,這就實(shí)現(xiàn)了性能的優(yōu)化乖篷。

Hook的使用規(guī)則

Hook 就是 JavaScript 函數(shù),但是使用它們會(huì)有兩個(gè)額外的規(guī)則:

  • 只能在函數(shù)最外層調(diào)用 Hook透且。不要在循環(huán)撕蔼、條件判斷或者子函數(shù)中調(diào)用豁鲤。
  • 只能在 React 的函數(shù)組件中調(diào)用 Hook。不要在其他 JavaScript 函數(shù)中調(diào)用鲸沮。

使用hook獲取數(shù)據(jù)

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

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

這里我們使用 useEffect 的 effect hook 來(lái)獲取數(shù)據(jù)琳骡。并且使用 useState 中的 setData 來(lái)更新組件狀態(tài)。

但是如上代碼運(yùn)行的時(shí)候讼溺,你會(huì)發(fā)現(xiàn)一個(gè)特別煩人的循環(huán)問(wèn)題楣号。effect hook 的觸發(fā)不僅僅是在組件第一次加載的時(shí)候,還有在每一次更新的時(shí)候也會(huì)觸發(fā)怒坯。由于我們?cè)讷@取到數(shù)據(jù)后就進(jìn)行設(shè)置了組件狀態(tài)炫狱,然后又觸發(fā)了 effect hook。所以就會(huì)出現(xiàn)死循環(huán)剔猿。很顯然视译,這是一個(gè) bug!我們只想在組件第一次加載的時(shí)候獲取數(shù)據(jù) 归敬,這也就是為什么你可以提供一個(gè)空數(shù)組作為 useEffect 的第二個(gè)參數(shù)以避免在組件更新的時(shí)候也觸它酷含。當(dāng)然,這樣的話汪茧,也就是在組件加載的時(shí)候觸發(fā)第美。

解決方法: 可以在useEffect(()=>{}, [])來(lái)解決。

還有一個(gè)陷阱是會(huì)報(bào)一個(gè)Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.. ``警告

代碼里面陆爽,我們使用 async/await 去獲取第三方的 API 的接口數(shù)據(jù)什往,根據(jù)文檔,每一個(gè) async 都會(huì)返回一個(gè) promise:async 函數(shù)聲明定義了一個(gè)異步函數(shù)慌闭,它返回一個(gè) AsyncFunction 對(duì)象别威。異步函數(shù)是通過(guò)事件循環(huán)異步操作的函數(shù),使用隱式的 Promise 返回結(jié)果然而驴剔,effect hook 不應(yīng)該返回任何內(nèi)容省古,或者清除功能

解決方法

import React, { useState, useEffect } from 'react';
  import axios from 'axios';
  
  function App() {
    const [data, setData] = useState({ hits: [] });
  
    useEffect(() => {
      const fetchData = async () => {
        const result = await axios(
          'https://hn.algolia.com/api/v1/search?query=redux',
        );
  
        setData(result.data);
      };
  
      fetchData();
    }, []);
  
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    );
  }
  
  export default App;

如何手動(dòng)觸發(fā)hook
**

function App() {
    const [data, setData] = useState({ hits: [] });
    const [query, setQuery] = useState('redux');
  
    useEffect(() => {
      const fetchData = async () => {
        const result = await axios(
          `http://hn.algolia.com/api/v1/search?query=${query}`,
        );
  
        setData(result.data);
      };
  
      fetchData();
    }, []);
  
    return (
      ...
    );
  }
  
  export default App;

當(dāng)你嘗試輸入字段鍵入內(nèi)容的時(shí)候,他是不會(huì)再去觸發(fā)請(qǐng)求的丧失。因?yàn)槟闾峁┑氖且粋€(gè)空數(shù)組作為useEffect的第二個(gè)參數(shù)是一個(gè)空數(shù)組豺妓,所以effect hook 的觸發(fā)不依賴任何變量,因此只在組件第一次加載的時(shí)候觸發(fā)布讹。所以這里我們希望當(dāng) query 這個(gè)字段一改變的時(shí)候就觸發(fā)搜索

query作為第二個(gè)參數(shù)傳遞給了 effect hook琳拭,這樣的話,每當(dāng) query 改變的時(shí)候就會(huì)觸發(fā)搜索描验。

但是白嘁,這樣就會(huì)出現(xiàn)了另一個(gè)問(wèn)題:每一次的query 的字段變動(dòng)都會(huì)觸發(fā)搜索。如何提供一個(gè)按鈕來(lái)觸發(fā)請(qǐng)求呢膘流?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${search}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [search]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="button" onClick={() => setSearch(query)}>
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

搜索的狀態(tài)設(shè)置為組件的初始化狀態(tài)絮缅,組件加載的時(shí)候就要觸發(fā)搜索鲁沥,類似的查詢和搜索狀態(tài)易造成混淆,為什么不把實(shí)際的 URL 設(shè)置為狀態(tài)而不是搜索狀態(tài)呢耕魄?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

求贊

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末画恰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吸奴,更是在濱河造成了極大的恐慌允扇,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奄抽,死亡現(xiàn)場(chǎng)離奇詭異蔼两,居然都是意外死亡甩鳄,警方通過(guò)查閱死者的電腦和手機(jī)逞度,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妙啃,“玉大人档泽,你說(shuō)我怎么就攤上這事∫靖埃” “怎么了馆匿?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)燥滑。 經(jīng)常有香客問(wèn)我渐北,道長(zhǎng),這世上最難降的妖魔是什么铭拧? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任赃蛛,我火速辦了婚禮,結(jié)果婚禮上搀菩,老公的妹妹穿的比我還像新娘呕臂。我一直安慰自己,他們只是感情好肪跋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布歧蒋。 她就那樣靜靜地躺著,像睡著了一般州既。 火紅的嫁衣襯著肌膚如雪谜洽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天吴叶,我揣著相機(jī)與錄音褥琐,去河邊找鬼。 笑死晤郑,一個(gè)胖子當(dāng)著我的面吹牛敌呈,可吹牛的內(nèi)容都是我干的贸宏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼磕洪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吭练!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起析显,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鲫咽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后谷异,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體分尸,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年歹嘹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了箩绍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尺上,死狀恐怖材蛛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怎抛,我是刑警寧澤卑吭,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站马绝,受9級(jí)特大地震影響豆赏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜富稻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一掷邦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唉窃,春花似錦耙饰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蔓涧,卻和暖如春件已,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背元暴。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工篷扩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茉盏。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓鉴未,卻偏偏與公主長(zhǎng)得像枢冤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铜秆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344