聽說(shuō)你還不懂ReactHook?

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

創(chuàng)新互聯(lián)公司是一家專業(yè)提供洮南企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)、html5、小程序制作等業(yè)務(wù)。10年已為洮南眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計(jì)公司優(yōu)惠進(jìn)行中。

從官網(wǎng)的這句話中,我們可以明確的知道,Hook增加了函數(shù)式組件中state的使用,在之前函數(shù)式組件是無(wú)法擁有自己的狀態(tài),只能通過(guò)props以及context來(lái)渲染自己的UI,而在業(yè)務(wù)邏輯中,有些場(chǎng)景必須要使用到state,那么我們就只能將函數(shù)式組件定義為class組件。而現(xiàn)在通過(guò)Hook,我們可以輕松的在函數(shù)式組件中維護(hù)我們的狀態(tài),不需要更改為class組件。

React Hooks要解決的問題是狀態(tài)共享,這里的狀態(tài)共享是指只共享狀態(tài)邏輯復(fù)用,并不是指數(shù)據(jù)之間的共享。我們知道在React Hooks之前,解決狀態(tài)邏輯復(fù)用問題,我們通常使用higher-order components和render-props,那么既然已經(jīng)有了這兩種解決方案,為什么React開發(fā)者還要引入React Hook?對(duì)于higher-order components和render-props,React Hook的優(yōu)勢(shì)在哪?

React Hook例子

我們先來(lái)看一下React官方給出的React Hook的demo

import { useState } from 'React';
function Example() {
 // Declare a new state variable, which we'll call "count"
 const [count, setCount] = useState(0);
 return (
 <div>
 <p>You clicked {count} times</p>
 <button onClick={() => setCount(count + 1)}>
 Click me
 </button>
 </div>
 );
}
復(fù)制代碼

我們?cè)賮?lái)看看不用React Hook的話,如何實(shí)現(xiàn)

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>
 );
 }
}
復(fù)制代碼

可以看到,在React Hook中,class Example組件變成了函數(shù)式組件,但是這個(gè)函數(shù)式組件卻擁有的自己的狀態(tài),同時(shí)還可以更新自身的狀態(tài)。這一切都得益于useState這個(gè)Hook,useState 會(huì)返回一對(duì)值:當(dāng)前狀態(tài)和一個(gè)讓你更新它的函數(shù),你可以在事件處理函數(shù)中或其他一些地方調(diào)用這個(gè)函數(shù)。它類似 class 組件的 this.setState,但是它不會(huì)把新的 state 和舊的 state 進(jìn)行合并

React復(fù)用狀態(tài)邏輯的解決方案

Hook是另一種復(fù)用狀態(tài)邏輯的解決方案,React開發(fā)者一直以來(lái)對(duì)狀態(tài)邏輯的復(fù)用方案不斷提出以及改進(jìn),從Mixin到高階組件到Render Props 到現(xiàn)在的Hook,我們先來(lái)簡(jiǎn)單了解一下以前的解決方案

Mixin模式

聽說(shuō)你還不懂React Hook?

在React最早期,提出了根據(jù)Mixin模式來(lái)復(fù)用組件之間的邏輯。在Javascript中,我們可以將Mixin繼承看作是通過(guò)擴(kuò)展收集功能的一種途徑.我們定義的每一個(gè)新的對(duì)象都有一個(gè)原型,從中它可以繼承更多的屬性.原型可以從其他對(duì)象繼承而來(lái),但是更重要的是,能夠?yàn)槿我鈹?shù)量的對(duì)象定義屬性.我們可以利用這一事實(shí)來(lái)促進(jìn)功能重用。

React中的mixin主要是用于在完全不相關(guān)的兩個(gè)組件中,有一套基本相似的功能,我們就可以將其提取出來(lái),通過(guò)mixin的方式注入,從而實(shí)現(xiàn)代碼的復(fù)用。例如,在不同的組件中,組件需要每隔一段時(shí)間更新一次,我們可以通過(guò)創(chuàng)建setInterval()函數(shù)來(lái)實(shí)現(xiàn)這個(gè)功能,同時(shí)在組件銷毀的時(shí)候,我們需要卸載此函數(shù)。因此可以創(chuàng)建一個(gè)簡(jiǎn)單的 mixin,提供一個(gè)簡(jiǎn)單的 setInterval() 函數(shù),它會(huì)在組件被銷毀時(shí)被自動(dòng)清理。

var SetIntervalMixin = {
 componentWillMount: function() {
 this.intervals = [];
 },
 setInterval: function() {
 this.intervals.push(setInterval.apply(null, arguments));
 },
 componentWillUnmount: function() {
 this.intervals.forEach(clearInterval);
 }
};
var createReactClass = require('create-React-class');
var TickTock = createReactClass({
 mixins: [SetIntervalMixin], // 使用 mixin
 getInitialState: function() {
 return {seconds: 0};
 },
 componentDidMount: function() {
 this.setInterval(this.tick, 1000); // 調(diào)用 mixin 上的方法
 },
 tick: function() {
 this.setState({seconds: this.state.seconds + 1});
 },
 render: function() {
 return (
 <p>
 React has been running for {this.state.seconds} seconds.
 </p>
 );
 }
});
ReactDOM.render(
 <TickTock />,
 document.getElementById('example')
);
復(fù)制代碼

mixin的缺點(diǎn)

  1. 不同mixin可能會(huì)相互依賴,耦合性太強(qiáng),導(dǎo)致后期維護(hù)成本過(guò)高

  2. mixin中的命名可能會(huì)沖突,無(wú)法使用同一命名的mixin

  3. mixin即使開始很簡(jiǎn)單,它們會(huì)隨著業(yè)務(wù)場(chǎng)景增多,時(shí)間的推移產(chǎn)生滾雪球式的復(fù)雜化

具體缺點(diǎn)可以看此鏈接Mixins是一種禍害

因?yàn)閙ixin的這些缺點(diǎn)存在,在React中已經(jīng)不建議使用mixin模式來(lái)復(fù)用代碼,React全面推薦使用高階組件來(lái)替代mixin模式,同時(shí)ES6本身是不包含任何 mixin 支持。因此,當(dāng)你在 React 中使用 ES6 class 時(shí),將不支持 mixins 。

聽說(shuō)你還不懂React Hook?

高階組件

高階組件(HOC)是 React 中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC 自身不是 React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式

高級(jí)組件并不是React提供的API,而是React的一種運(yùn)用技巧,高階組件可以看做是裝飾者模式(Decorator Pattern)在React的實(shí)現(xiàn)。裝飾者模式: 動(dòng)態(tài)將職責(zé)附加到對(duì)象上,若要擴(kuò)展功能,裝飾者提供了比繼承更具彈性的代替方案.

具體而言,高階組件是參數(shù)為組件,返回值為新組件的函數(shù)。

組件是將 props 轉(zhuǎn)換為 UI,而高階組件是將組件轉(zhuǎn)換為另一個(gè)組件

我們可以通過(guò)高階組件動(dòng)態(tài)給其他組件增加日志打印功能,而不影響原先組件的功能

function logProps(WrappedComponent) {
 return class extends React.Component {
 componentWillReceiveProps(nextProps) {
 console.log('Current props: ', this.props);
 console.log('Next props: ', nextProps);
 }
 render() {
 return <WrappedComponent {...this.props} />;
 }
 }
}
復(fù)制代碼

Render Propss

術(shù)語(yǔ) “Render Props” 是指一種在 React 組件之間使用一個(gè)值為函數(shù)的 prop 共享代碼的簡(jiǎn)單技術(shù)

具有 Render Props 的組件接受一個(gè)函數(shù),該函數(shù)返回一個(gè) React 元素并調(diào)用它而不是實(shí)現(xiàn)自己的渲染邏輯

以下我們提供了一個(gè)帶有prop的<Mouse>組件,它能夠動(dòng)態(tài)決定什么需要渲染,這樣就能對(duì)<Mouse>組件的邏輯以及狀態(tài)復(fù)用,而不用改變它的渲染結(jié)構(gòu)。

class Cat extends React.Component {
 render() {
 const mouse = this.props.mouse;
 return (
 <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
 );
 }
}
class Mouse extends React.Component {
 constructor(props) {
 super(props);
 this.handleMouseMove = this.handleMouseMove.bind(this);
 this.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>
 );
 }
}
class MouseTracker extends React.Component {
 render() {
 return (
 <div>
 <h2>移動(dòng)鼠標(biāo)!</h2>
 <Mouse render={mouse => (
 
 )}/>
 </div>
 );
 }
}
復(fù)制代碼

然而通常我們說(shuō)的Render Props 是因?yàn)槟J讲疟环Q為 Render Props ,又不是因?yàn)橐欢ㄒ胷ender對(duì)prop進(jìn)行命名。我們也可以這樣來(lái)表示

<Mouse>
 {mouse => (
 <Cat mouse={mouse} />
 )}
</Mouse>
復(fù)制代碼

React Hook動(dòng)機(jī)

React Hook是官網(wǎng)提出的又一種全新的解決方案,在了解React Hook之前,我們先看一下React Hook提出的動(dòng)機(jī)

  1. 在組件之間復(fù)用狀態(tài)邏輯很難

  2. 復(fù)雜組件變得難以理解

  3. 難以理解的 class

下面說(shuō)說(shuō)我對(duì)這三個(gè)動(dòng)機(jī)的理解:

在組件之間復(fù)用狀態(tài)邏輯很難,在之前,我們通過(guò)高階組件(Higher-Order Components)和渲染屬性(Render Propss)來(lái)解決狀態(tài)邏輯復(fù)用困難的問題。很多庫(kù)都使用這些模式來(lái)復(fù)用狀態(tài)邏輯,比如我們常用redux、React Router。高階組件、渲染屬性都是通過(guò)組合來(lái)一層層的嵌套共用組件,這會(huì)大大增加我們代碼的層級(jí)關(guān)系,導(dǎo)致層級(jí)的嵌套過(guò)于夸張。從React的devtool我們可以清楚的看到,使用這兩種模式導(dǎo)致的層級(jí)嵌套程度

聽說(shuō)你還不懂React Hook?

復(fù)雜組件變得難以理解,在不斷變化的業(yè)務(wù)需求中,組件逐漸會(huì)被狀態(tài)邏輯以及副作用充斥,每個(gè)生命周期常常會(huì)包含一些不相關(guān)的邏輯。我們寫代碼通常都依據(jù)函數(shù)的單一原則,一個(gè)函數(shù)一般只處理一件事,但在生命周期鉤子函數(shù)中通常會(huì)同時(shí)做很多事情。比如,在我們需要在componentDidMount中發(fā)起ajax請(qǐng)求獲取數(shù)據(jù),同時(shí)有時(shí)候也會(huì)把事件綁定寫在此生命周期中,甚至有時(shí)候需要在componentWillReceiveProps中對(duì)數(shù)據(jù)進(jìn)行跟componentDidMount一樣的處理。

相互關(guān)聯(lián)且需要對(duì)照修改的代碼被進(jìn)行了拆分,而完全不相關(guān)的代碼卻在同一個(gè)方法中組合在一起。如此很容易產(chǎn)生 bug,并且導(dǎo)致邏輯不一致。

難以理解的class,個(gè)人覺得使用class組件這種還是可以的,只要了解了class的this指向綁定問題,其實(shí)上手的難度不大。大家要理解,這并不是 React 特有的行為;這其實(shí)與 JavaScript 函數(shù)工作原理有關(guān)。所以只要了解好JS函數(shù)工作原理,其實(shí)this綁定都不是事。只是有時(shí)候?yàn)榱吮WCthis的指向正確,我們通常會(huì)寫很多代碼來(lái)綁定this,如果忘記綁定的話,就有會(huì)各種bug。綁定this方法:

1.this.handleClick = this.handleClick.bind(this);
2.<button onClick={(e) => this.handleClick(e)}>
 Click me
 </button>
復(fù)制代碼

于是為了解決以上問題,React Hook就被提出來(lái)了

state Hook使用

我們回到剛剛的代碼中,看一下如何在函數(shù)式組件中定義state

import React, { useState } from 'React';
const [count, setCount] = useState(0);
復(fù)制代碼
  1. useState做了啥

  2. 我們可以看到,在此函數(shù)中,我們通過(guò)useState定義了一個(gè)'state變量',它與 class 里面的 this.state 提供的功能完全相同.相當(dāng)于以下代碼

class Example extends React.Component {
 constructor(props) {
 super(props);
 this.state = {
 count: 0
 };
 }
復(fù)制代碼
  1. useState參數(shù)

  2. 在代碼中,我們傳入了0作為useState的參數(shù),這個(gè)參數(shù)的數(shù)值會(huì)被當(dāng)成count初始值。當(dāng)然此參數(shù)不限于傳遞數(shù)字以及字符串,可以傳入一個(gè)對(duì)象當(dāng)成初始的state。如果state需要儲(chǔ)存多個(gè)變量的值,那么調(diào)用多次useState即可

  3. useState返回值

  4. 返回值為:當(dāng)前 state 以及更新 state 的函數(shù),這與 class 里面 this.state.count 和 this.setState 類似,唯一區(qū)別就是你需要成對(duì)的獲取它們??吹絒count, setCount]很容易就能明白這是ES6的解構(gòu)數(shù)組的寫法。相當(dāng)于以下代碼

let _useState = useState(0);// 返回一個(gè)有兩個(gè)元素的數(shù)組
let count = _useState[0];// 數(shù)組里的第一個(gè)值
let setCount = _useState[1];// 數(shù)組里的第二個(gè)值
復(fù)制代碼

讀取狀態(tài)值

只需要使用變量即可

以前寫法

<p>You clicked {this.state.count} times</p>
復(fù)制代碼

現(xiàn)在寫法

<p>You clicked {count} times</p>
復(fù)制代碼

更新狀態(tài)

通過(guò)setCount函數(shù)更新

以前寫法

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
 Click me
 </button>
復(fù)制代碼

現(xiàn)在寫法

 <button onClick={() => setCount(count + 1)}>
 Click me
 </button>
復(fù)制代碼

這里setCount接收的參數(shù)是修改過(guò)的新狀態(tài)值

聲明多個(gè)state變量

我們可以在一個(gè)組件中多次使用state Hook來(lái)聲明多個(gè)state變量

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

React 假設(shè)當(dāng)你多次調(diào)用 useState 的時(shí)候,你能保證每次渲染時(shí)它們的調(diào)用順序是不變的

為什么React要規(guī)定每次渲染它們時(shí)的調(diào)用順序不變呢,這個(gè)是一個(gè)理解Hook至關(guān)重要的問題

Hook 規(guī)則

Hook 本質(zhì)就是 JavaScript 函數(shù),但是在使用它時(shí)需要遵循兩條規(guī)則。并且React要求強(qiáng)制執(zhí)行這兩條規(guī)則,不然就會(huì)出現(xiàn)異常的bug

  1. 只在最頂層使用 Hook

不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook, 確??偸窃谀愕?React 函數(shù)的最頂層調(diào)用他們

  1. 只在 React 函數(shù)中調(diào)用 Hook

不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook

這兩條規(guī)則出現(xiàn)的原因是,我們可以在單個(gè)組件中使用多個(gè)State Hook 或 Effect Hook,React 靠的是 Hook 調(diào)用的順序來(lái)知道哪個(gè) state 對(duì)應(yīng)哪個(gè)useState

function Form() {
 const [name1, setName1] = useState('Arzh2');
 const [name2, setName2] = useState('Arzh3');
 const [name3, setName3] = useState('Arzh4');
 // ...
}
// ------------
// 首次渲染
// ------------
useState('Arzh2') // 1. 使用 'Arzh2' 初始化變量名為 name1 的 state
useState('Arzh3') // 2. 使用 'Arzh3' 初始化變量名為 name2 的 state
useEffect('Arzh4') 	// 3. 使用 'Arzh4' 初始化變量名為 name3 的 state
// -------------
// 二次渲染
// -------------
useState('Arzh2') // 1. 讀取變量名為 name1 的 state(參數(shù)被忽略)
useState('Arzh3') // 2. 讀取變量名為 name2 的 state(參數(shù)被忽略)
useEffect('Arzh4') // 3. 讀取變量名為 name3 的 state(參數(shù)被忽略)
復(fù)制代碼

如果我們違反React的規(guī)則,使用條件渲染

if (name !== '') {
 const [name2, setName2] = useState('Arzh3');
}
復(fù)制代碼

假設(shè)第一次(name !== '')為true的時(shí)候,執(zhí)行此Hook,第二次渲染(name !== '')為false時(shí),不執(zhí)行此Hook,那么Hook的調(diào)用順序就會(huì)發(fā)生變化,產(chǎn)生bug

useState('Arzh2') // 1. 讀取變量名為 name1 的 state
//useState('Arzh3') // 2. Hook被忽略
useEffect('Arzh4') // 3. 讀取變量名為 name2(之前為name3) 的 state
復(fù)制代碼

React 不知道第二個(gè) useState 的 Hook 應(yīng)該返回什么。React 會(huì)以為在該組件中第二個(gè) Hook 的調(diào)用像上次的渲染一樣,對(duì)應(yīng)的是 arzh3 的 useState,但并非如此。所以這就是為什么React強(qiáng)制要求Hook使用必須遵循這兩個(gè)規(guī)則,同時(shí)我們可以使用 eslint-plugin-React-Hooks來(lái)強(qiáng)制約束

Effect Hook使用

我們?cè)谏厦娴拇a中增加Effect Hook的使用,在函數(shù)式組件中增加副作用,修改網(wǎng)頁(yè)的標(biāo)題

 useEffect(() => {
 document.title = `You clicked ${count} times`;
 });
復(fù)制代碼

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

也就是我們完全可以通過(guò)useEffect來(lái)替代這三個(gè)生命鉤子函數(shù)

我們來(lái)了解一下通常需要副作用的場(chǎng)景,比如發(fā)送請(qǐng)求,手動(dòng)變更dom,記錄日志等。通常我們都會(huì)在第一次dom渲染完成以及后續(xù)dom重新更新時(shí),去調(diào)用我們的副作用操作。我們可以看一下以前生命周期的實(shí)現(xiàn)

 componentDidMount() {
 document.title = `You clicked ${this.state.count} times`;
 }
 componentDidUpdate() {
 document.title = `You clicked ${this.state.count} times`;
 }
復(fù)制代碼

這也就是我們上面提到的React Hook動(dòng)機(jī)的第二個(gè)問題來(lái)源之一,需要在第一次渲染以及后續(xù)的渲染中調(diào)用相同的代碼

Effect在默認(rèn)情況下,會(huì)在第一次渲染之后和每次更新之后都會(huì)執(zhí)行,這也就讓我們不需要再去考慮是componentDidMount還是componentDidUpdate時(shí)執(zhí)行,只需要明白Effect在組件渲染后執(zhí)行即可

清除副作用

有時(shí)候?qū)τ谝恍└弊饔?,我們是需要去清除的,比如我們有個(gè)需求需要輪詢向服務(wù)器請(qǐng)求最新狀態(tài),那么我們就需要在卸載的時(shí)候,清理掉輪詢的操作。

 componentDidMount() {
 this.pollingNewStatus()
 }
 componentWillUnmount() {
 this.unPollingNewStatus()
 }
復(fù)制代碼

我們可以使用Effect來(lái)清除這些副作用,只需要在Effect中返回一個(gè)函數(shù)即可

 useEffect(() => {
 pollingNewStatus()
 //告訴React在每次渲染之前都先執(zhí)行cleanup()
 return function cleanup() {
 unPollingNewStatus()
 };
 });
復(fù)制代碼

有個(gè)明顯的區(qū)別在于useEffect其實(shí)是每次渲染之前都會(huì)去執(zhí)行cleanup(),而componentWillUnmount只會(huì)執(zhí)行一次。

Effect性能優(yōu)化

useEffect其實(shí)是每次更新都會(huì)執(zhí)行,在某些情況下會(huì)導(dǎo)致性能問題。那么我們可以通過(guò)跳過(guò) Effect 進(jìn)行性能優(yōu)化。在class組件中,我們可以通過(guò)在 componentDidUpdate 中添加對(duì) prevProps 或 prevState 的比較邏輯解決

componentDidUpdate(prevProps, prevState) {
 if (prevState.count !== this.state.count) {
 document.title = `You clicked ${this.state.count} times`;
 }
}
復(fù)制代碼

在Effect中,我們可以通過(guò)增加Effect的第二個(gè)參數(shù)即可,如果沒有變化,則跳過(guò)更新

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

聽說(shuō)你還不懂React Hook?

網(wǎng)頁(yè)標(biāo)題:聽說(shuō)你還不懂ReactHook?
標(biāo)題URL:http://muchs.cn/article36/isjcsg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、服務(wù)器托管、外貿(mào)建站定制開發(fā)、響應(yīng)式網(wǎng)站靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁(yè)設(shè)計(jì)公司