一合砂、React介紹
溫馨提醒:想要獲取更好的觀看效果收厨,可以點(diǎn)擊查看本篇文章的原文檔(React-Hook快速入門(一) (notion.so)
react是基本的頁(yè)面渲染庫(kù),基于不同的平臺(tái)有
- react-dom: 瀏覽器
- react-native: app環(huán)境
- react-vr: vr平臺(tái)
二鼠次、為什么要使用React-Hook
在React-Hook誕生之前,React通常使用class作為組件。而function只能作為受控組件轻局,它本身是沒有自己的狀態(tài)(state),只能通過接受props來被動(dòng)渲染。引入React-Hook后样刷,函數(shù)組件可以通過useState來?yè)碛凶约旱臓顟B(tài)嗽交;useEffect整合了各類生命周期,使得代碼邏輯更清晰颂斜;自定義hook使得代碼更容易復(fù)用夫壁。以下是官網(wǎng)對(duì)于React-Hook的介紹。
簡(jiǎn)而言之沃疮,hook主要解決了以下的一些問題:
- 大型組件很難拆分和重構(gòu)盒让,也很難測(cè)試。
- 業(yè)務(wù)邏輯分散在組件的各個(gè)方法之中司蔬,導(dǎo)致重復(fù)邏輯或關(guān)聯(lián)邏輯邑茄。
- 組件類引入了復(fù)雜的編程模式,比如 render props 和高階組件俊啼。
核心思想:組件的最佳寫法應(yīng)該是函數(shù)肺缕,而不是類。React Hooks 的意思是,組件盡量寫成純函數(shù)同木,如果需要外部功能和副作用浮梢,就用鉤子把外部代碼"鉤"進(jìn)來。
三彤路、useState
基本用法
const [state, setState] = useState(initValue)
useState使React函數(shù)組件擁有了狀態(tài)秕硝。
- 括號(hào)里的initValue是state的初始值。
- 數(shù)組解構(gòu)的第一個(gè)參數(shù)是最新的state值洲尊,每次state的值得改變將觸發(fā)頁(yè)面重新渲染远豺。
- 數(shù)組解構(gòu)的第二個(gè)參數(shù)是state的更新函數(shù),通過給setState(newState)傳遞參數(shù)newState來改變狀態(tài)值(state),并引發(fā)頁(yè)面的重新渲染坞嘀。
import React, { useState } from 'react';
const 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>
);
}
setState是同步還是異步躯护?
[圖片上傳失敗...(image-8c34b3-1621553852262)]
https://codesandbox.io/s/agitated-flower-9lhi2?file=/src/App.tsx:0-570
在執(zhí)行增加num(state值)的前后打印num,發(fā)現(xiàn)打印的結(jié)果均是0而不是最新值1丽涩,似乎setState是一個(gè)異步操作棺滞?
import { useState } from "react";
import "./styles.css";
export default function App() {
const [num, setNum] = useState(0);
const addNum = () => {
console.log("執(zhí)行setNum之前", num);
setNum(num + 1)
console.log("執(zhí)行setNum之后", num);
};
return (
<div className="App">
<h1>setState是同步還是異步的?</h1>
<p>數(shù)字:{num}</p>
<button onClick={addNum}>增加</button>
</div>
);
}
接著將代碼更改如下,在點(diǎn)擊增加按鈕的回調(diào)函數(shù)中一次性調(diào)用兩次setNum内狸,結(jié)果發(fā)現(xiàn)頁(yè)面上顯示的數(shù)字是1而不是2检眯。這是什么情況呢?
import { useState } from "react";
import "./styles.css";
export default function App() {
const [num, setNum] = useState(0);
const addNum = () => {
console.log("執(zhí)行setNum之前", num);
setNum(num + 1)
console.log("執(zhí)行setNum之后", num);
};
return (
<div className="App">
<h1>setState是同步還是異步的昆淡?</h1>
<p>數(shù)字:{num}</p>
<button onClick={addNum}>增加</button>
</div>
);
}
這是因?yàn)樵趫?zhí)行了setNum函數(shù)后锰瘸,React并不會(huì)立馬去更新num。這樣在連續(xù)第二次調(diào)用setState時(shí)昂灵,num值仍然為0避凝,因此連續(xù)調(diào)用兩次setNum后最新結(jié)果是1。React這樣做的目的是眨补,連續(xù)多次的調(diào)用setState會(huì)合并state管削,而不是立馬去更新state,這樣就不會(huì)導(dǎo)致頁(yè)面在短暫時(shí)間進(jìn)行多次渲染撑螺,從而節(jié)省了頁(yè)面開銷含思。
那么我想連續(xù)調(diào)用兩次,而且最終結(jié)果顯示為2甘晤,該怎么實(shí)現(xiàn)含潘?這個(gè)時(shí)候可以使用函數(shù)式更新
函數(shù)式更新
通過在useState傳入一個(gè)函數(shù),該函數(shù)的參數(shù)state即為更新之前的state值线婚,通過在這個(gè)函數(shù)中執(zhí)行兩次num + 1的操作遏弱,最后返回的state將會(huì)比先前值+2,這樣保證了狀態(tài)的一致性塞弊,也驗(yàn)證了setState本身是一個(gè)同步函數(shù)漱逸,只是它的狀態(tài)更新機(jī)制像一個(gè)異步函數(shù)泪姨!
import { useState } from "react";
import "./styles.css";
export default function App() {
const [num, setNum] = useState(0);
const addNum = () => {
console.log("執(zhí)行setNum之前", num);
setNum((state) => {
state += 1;
state += 1
console.log("執(zhí)行setNum的時(shí)候", state);
return state;
});
console.log("執(zhí)行setNum之后", num);
};
return (
<div className="App">
<h1>setState是同步還是異步的?</h1>
<p>數(shù)字:{num}</p>
<button onClick={addNum}>增加</button>
</div>
);
}
惰性初始化
useState可以存字符串饰抒、數(shù)值等基本類型肮砾,也可以存數(shù)組、字符串等引用類型循集。那么可不可以存函數(shù)呢唇敞?
https://codesandbox.io/s/usestateduoxingchushihua-jthil?file=/src/App.tsx
通過callback存入一個(gè)函數(shù)蔗草,在點(diǎn)擊更改函數(shù)
按鈕時(shí)咒彤,變更存儲(chǔ)的函數(shù);在點(diǎn)擊執(zhí)行函數(shù)按鈕時(shí)咒精,調(diào)用存入的函數(shù)镶柱。然而結(jié)果并不是像我們期望的那樣。在初始化state和點(diǎn)擊更改函數(shù)
按鈕時(shí)模叙,都自動(dòng)執(zhí)行了存入的函數(shù)歇拆!原因是React的useState有著惰性初始化的特性
import { useState } from "react";
import "./styles.css";
export default function App() {
const [callback, setCallback] = useState(() => {alert('init')})
return (
<div className="App">
<h1>useState惰性初始化</h1>
<button onClick={() => {setCallback(() => {alert('change')})}}>更改函數(shù)</button>
<button onClick={callback}>執(zhí)行函數(shù)</button>
</div>
);
}
傳入函數(shù)給useState,React并不會(huì)認(rèn)為你要存的是一個(gè)函數(shù)范咨。相反他會(huì)認(rèn)為這是一個(gè)非常消耗性能的計(jì)算state的操作故觅,他會(huì)立即去執(zhí)行還函數(shù),并將該函數(shù)的返回值作為新的state渠啊。
那么useState如何存一個(gè)函數(shù)呢输吏?
其實(shí)很簡(jiǎn)單,只要將上述代碼修改如下即可替蛉。
import { useState } from "react";
import "./styles.css";
export default function App() {
const [callback, setCallback] = useState(() => () => {alert('init')})
return (
<div className="App">
<h1>useState惰性初始化</h1>
<button onClick={() => {setCallback(() => () => {alert('change')})}}>更改函數(shù)</button>
<button onClick={callback}>執(zhí)行函數(shù)</button>
</div>
);
}
四贯溅、useEffect
基本用法
useEffect(() => {
// do some effect function
}, [dependence]);
在DOM更新完畢之后執(zhí)行副作用函數(shù),可以取代class的生命周期函數(shù)躲查。
- 當(dāng)沒有依賴項(xiàng)它浅,會(huì)在組件每次更新后執(zhí)行
- 依賴項(xiàng)為空數(shù)組:會(huì)在組件掛載和卸載時(shí)執(zhí)行
- 依賴項(xiàng)為變量時(shí),會(huì)在這些變量改變后才執(zhí)行
清除effect
通常镣煮,組件卸載時(shí)需要清除 effect 創(chuàng)建的諸如訂閱或計(jì)時(shí)器 ID 等資源姐霍。要實(shí)現(xiàn)這一點(diǎn),useEffect 函數(shù)需返回一個(gè)清除函數(shù)典唇。
https://codesandbox.io/s/wispy-frog-bb3wp?file=/src/App.tsx
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
export default function Test() {
const [num, setNum] = useState<number>(0);
useEffect(() => {
console.log("在return之后執(zhí)行");
return () => {
console.log("return先執(zhí)行");
};
}, [num]);
return (
<div>
<h2>Test測(cè)試頁(yè)面</h2>
<Link to="/other">前往Other</Link>
<p>數(shù)字:{num}</p>
<button
onClick={() => {
setNum(num + 1);
}}
>
增加
</button>
</div>
);
}
- React會(huì)在第一次渲染時(shí)執(zhí)行useEffect中的函數(shù)镊折,韓是不會(huì)執(zhí)行return。
- effect 在之后的每次渲染的時(shí)候都會(huì)執(zhí)行蚓聘。此時(shí)先執(zhí)行return函數(shù)腌乡,再執(zhí)行effect中的副作用。
- React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作(即執(zhí)行return)
依賴項(xiàng)的注意事項(xiàng)
一般選擇什么作為useEffect的依賴項(xiàng)夜牡?選擇依賴項(xiàng)時(shí)需要注意些什么呢与纽?
https://codesandbox.io/s/trusting-cherry-7i2wt?file=/src/App.tsx
分別選擇常量侣签、狀態(tài)對(duì)象和普通對(duì)象作為依賴項(xiàng)〖庇兀可以發(fā)現(xiàn)影所,選擇常量時(shí),除了第一次渲染以外僚碎,之后每次頁(yè)面渲染都不會(huì)去調(diào)用useEffect猴娩;選擇狀態(tài)對(duì)象時(shí),每次該狀態(tài)改變時(shí)(即頁(yè)面重新渲染后)勺阐,都會(huì)去調(diào)用useEffect卷中;而選擇一般對(duì)象時(shí),會(huì)陷入無限循環(huán)的去調(diào)用useEffect渊抽。
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [num, setNum] = useState(0);
const [text, setText] = useState<string[]>([]);
const constValue = "小花";
const object = { name: "小花" };
useEffect(() => {
console.log("頁(yè)面重新渲染了");
setText(["小花", "小明", "囂張"]);
}, [constValue]);
return (
<div className="App">
<h1>選擇依賴項(xiàng)的注意事項(xiàng)</h1>
<p>數(shù)字:{num}</p>
<button
onClick={() => {
setNum(num + 1);
}}
>
增加
</button>
{text.map((item) => (
<div>{item}</div>
))}
</div>
);
}
- 絕不可以使用非狀態(tài)的對(duì)象作為依賴蟆豫,因?yàn)槊看谓M件更新后,該對(duì)象的地址都會(huì)發(fā)生改變懒闷,最終導(dǎo)致不停地的調(diào)用useEffect十减。
- 可以使用常量和狀態(tài)對(duì)象作為依賴,因?yàn)闋顟B(tài)對(duì)象在組建更新后并不會(huì)改變愤估,除非調(diào)用setState改變帮辟。
五、自定義hook
Hook 使用規(guī)則
- 只能在函數(shù)最外層調(diào)用 Hook玩焰。不要在循環(huán)由驹、條件判斷或者子函數(shù)中調(diào)用。
- 只能在 React 的函數(shù)組件中調(diào)用 Hook震捣。不要在其他 JavaScript 函數(shù)中調(diào)用荔棉。(還有一個(gè)地方可以調(diào)用 Hook —— 就是自定義的 Hook 中,我們稍后會(huì)學(xué)習(xí)到蒿赢。)
為什么只能在函數(shù)最外層調(diào)用hook润樱?
自定義一個(gè)useArray hook
遵循React的規(guī)則,使用React提供的基礎(chǔ)hook羡棵,自定義一個(gè)hook壹若。該hook封裝了對(duì)數(shù)組的操作,在其他地方可以方便的調(diào)用這個(gè)hook來完成對(duì)數(shù)組的操作
點(diǎn)擊查看useArray的實(shí)現(xiàn)
https://codesandbox.io/s/fervent-hodgkin-g0ipv?file=/src/App.tsx
注意事項(xiàng)
- 自定義hook一定要使用use開頭的命名規(guī)范皂冰,否則會(huì)直接報(bào)錯(cuò)
- 不要在回調(diào)函數(shù)中調(diào)用hook店展,應(yīng)該把需要用到的回調(diào)方法通過hook返回,然后在需要用到的函數(shù)組件最外層獲取該方法秃流,這樣就可以在回調(diào)函數(shù)中調(diào)用該方法赂蕴。