組件生命周期
組件的生命周期有助于理解組件的運行方式坏晦,完成更復雜的組件功能、分析組件錯誤原因等
組件的生命周期: 組件從被創(chuàng)建到掛載到頁面中運行爱榔,再到組件不在時卸載的過程
生命周期的每個階段總是伴隨著一些方法調(diào)用翁狐,這些方法就是生命周期的鉤子函數(shù)
構造函數(shù)的作用:為開發(fā)人員在不同階段操作組件提供了實際
創(chuàng)建時(掛載階段)
class App extends React.Component {
constructor() {
super()
console.log(1)
}
componentDidMount() {
console.log(3)
}
render() {
console.log(2)
return (
<div>
啊哈
</div>
)
}
}
更新時
執(zhí)行時機:setState()办铡、 forceUpdate()双抽、 組件接收到新的props
說明:以上三者任意一種變化百框,組件就會重新渲染
卸載時
執(zhí)行時機:組件從頁面中消失
作用:用來做清理操作
新版完整生命鉤子函數(shù)
getDerivedStateFromProps()
-
getDerivedStateFromProps
會在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時都會被調(diào)用牍汹。它應返回一個對象來更新 state铐维,如果返回 null 則不更新任何內(nèi)容 - 不管原因是什么,都會在每次渲染前觸發(fā)此方法
shouldComponentUpdate()
- 根據(jù)
shouldComponentUpdate()
的返回值慎菲,判斷 React 組件的輸出是否受當前 state 或 props 更改的影響嫁蛇。默認行為是 state 每次發(fā)生變化組件都會重新渲染 - 當 props 或 state 發(fā)生變化時,
shouldComponentUpdate()
會在渲染執(zhí)行之前被調(diào)用露该。返回值默認為 true
getSnapshotBeforeUpdate()
-
getSnapshotBeforeUpdate()
在最近一次渲染輸出(提交到 DOM 節(jié)點)之前調(diào)用睬棚。它使得組件能在發(fā)生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)有决。此生命周期的任何返回值將作為參數(shù)傳遞給componentDidUpdate()
- 此用法并不常見闸拿,但它可能出現(xiàn)在 UI 處理中空盼,如需要以特殊方式處理滾動位置的聊天線程等
render-props模式
- 思考:如果兩個組件中的部分功能相似或相同书幕,該如何處理?
- 處理方式:復用相似的功能
- 復用什么揽趾?
- state
- 操作state的方法
- 兩種方式:
- render props模式
- 高階組件(HOC)
- 注意: 這兩種方式不是新的API台汇,而是利用React自身特點的編碼技巧,演化而成的固定模式
// 子組件
class Hello extends React.Component {
state = {
name: '肖'
}
render() {
return this.props.render(this.state);
}
}
//父組件
class App extends React.Component {
render() {
return (
<div>
父組件
<Hello render={msg => {
return <p>{msg.name}</p>
}} />
</div>
)
}
}
children代替render屬性
- 注意:并不是該模式叫 render props就必須使用名為render的prop篱瞎,實際上可以使用任意名稱的prop
- 把prop是一個函數(shù)并且告訴組件要渲染什么內(nèi)容的技術叫做: render props模式
- 推薦:使用childre代替render屬性
// 子組件
class Hello extends React.Component {
state = {
name: '肖'
}
render() {
return this.props.children(this.state);
}
}
//父組件
class App extends React.Component {
render() {
return (
<div>
父組件
<Hello>
{({ name }) => <p>{name}</p>}
</Hello>
</div>
)
}
}
高階組件
高階組件(HOC苟呐、Higher-Order Component) 是一個函數(shù),接收要包裝的組件俐筋,返回增強后的組件
高階組件內(nèi)部創(chuàng)建了一個類組件牵素,在這個類組件中提供復用的狀態(tài)邏輯代碼,通過prop將復用的狀態(tài)傳遞給被包裝組件
WrappedComponent
創(chuàng)建一個函數(shù)澄者,名稱約定以with開頭
指定函數(shù)參數(shù)笆呆,參數(shù)應該以大寫字母開頭
在函數(shù)內(nèi)部創(chuàng)建一個類組件请琳,提供復用的狀態(tài)邏輯代碼,并返回
在該組件中赠幕,渲染參數(shù)組件俄精,同時將狀態(tài)通過prop傳遞給參數(shù)組件
調(diào)用該高階組件,傳入要增強的組件榕堰,通過返回值拿到增強后的組件竖慧,并將其渲染到頁面
function WithMouse(WrappedComponent) {
class Mouse extends React.Component {
state = {
time: new Date().getTime()
}
render() {
return <WrappedComponent {...this.state} />
}
}
return Mouse
}
// 需要加強的組件
const Position = (props) => {
console.log(props);
return <p>{props.time}</p>
}
// 傳遞封裝加強
let MousePosition = WithMouse(Position);
//父組件
class App extends React.Component {
render() {
return (
<div>
<MousePosition></MousePosition>
</div>
)
}
}
設置displayName
- 使用高階組件存在的問題:得到兩個組件的名稱相同
- 原因:默認情況下,React使用組件名稱作為
displayName
- 解決方式:為高階組件設置
displayName
逆屡,便于調(diào)試時區(qū)分不同的組件 -
displayName的作用:用于設置調(diào)試信息(React Developer Tools信息)
image.png
傳遞props
- 問題:如果沒有傳遞props圾旨,會導致props丟失問題
- 解決方式: 渲染
WrappedComponent
時,將state和props一起傳遞給組件
image.png
React原理
setState()說明
-
setState()
更新數(shù)據(jù)是異步的 - 注意:使用該語法魏蔗,后面的
setState
不要依賴前面setState
的值 - 多次調(diào)用
setState
碳胳,只會觸發(fā)一次render
推薦語法
- 推薦:使用
setState((state,props) => {})
語法 - 參數(shù)state: 表示最新的state
- 參數(shù)props: 表示最新的props
add = () => {
this.setState(({ count }) => {
return {
count: count + 1
}
})
console.log(this.state.count); //js是單線程 先執(zhí)行同步再執(zhí)行異步
}
第二個參數(shù)
- 場景:在狀態(tài)更新(頁面完成重新渲染)后立即執(zhí)行某個操作
- 語法:
setState(update[,callback])
add = () => {
this.setState(({ count }) => {
return {
count: count + 1
}
}, () => {
console.log('這個回調(diào)函數(shù)會在狀態(tài)更新后立即執(zhí)行')
})
console.log(this.state.count); //js是單線程 先執(zhí)行同步再執(zhí)行異步
}
JSX語法的轉(zhuǎn)化過程
- JSX僅僅是
createElement()
方法的語法糖(簡化語法) - JSX語法被 @babel/preset-react 插件編譯為
createElement()
方法 -
React 元素: 是一個對象,用來描述你希望在屏幕上看到的內(nèi)容
image.png
組件更新機制
-
setState() 的兩個作用
- 修改state
- 更新組件
過程:父組件重新渲染時沫勿,也會重新渲染子組件挨约,但只會渲染當前組件子樹(當前組件以其所有子組件)
組件性能優(yōu)化
減輕state
- 減輕state:只存儲跟組件渲染相關的數(shù)據(jù)(比如:count/ 列表數(shù)據(jù) /loading等)
- 注意:不用做渲染的數(shù)據(jù)不要放在state中
- 對于這種需要在多個方法中用到的數(shù)據(jù),應該放到this中
class Hello extends React.Component {
// 初始化
componentDidMount() {
this.timeID = setInterval(() => {
console.log(1);
}, 2000)
}
// 卸載 移除計時器
componentWillUnmount() {
clearInterval(this.timeID);
}
render() {
return <div>子組件</div>
}
}
避免不必要的重新渲染
- 組件更新機制:父組件更新會引起子組件也被更新产雹,這種思路很清晰
- 問題:子組件沒有任何變化時也會重新渲染
- 如何避免不必要的重新渲染诫惭?
- 解決方式:使用鉤子函數(shù) shouldComponentUpdate(nextProps, nextState)
- 在這個函數(shù)中,nextProps和nextState是最新的狀態(tài)以及屬性
- 作用:這個函數(shù)有返回值蔓挖,如果返回true夕土,代表需要重新渲染,如果返回false瘟判,代表不需要重新渲染
- 觸發(fā)時機:更新階段的鉤子函數(shù)怨绣,組件重新渲染前執(zhí)行(shouldComponentUpdate => render)
class App extends React.Component {
state = {
count: 0
}
// 每次點擊生成一個隨機數(shù)
add = () => {
this.setState({
count: Math.floor(Math.random() * 3)
})
}
// 將要更新ui的時候會執(zhí)行這個鉤子函數(shù)
shouldComponentUpdate(nextProps, nextState) {
// 判斷一下當前生成的 值是否與頁面的值相等
if (nextState.count !== this.state.count) {
console.log('已改變');
return true
}
return false
}
render() {
return (
<div>
隨機數(shù):{this.state.count} <br />
<button onClick={this.add}>按鈕</button>
</div>
)
}
}
利用props參數(shù)來判斷是否需要進行更新
class App extends React.Component {
state = {
number: 0
}
// 點擊事件,每次點擊生成一個隨機數(shù)
hanldeBtn = () => {
this.setState({
number: Math.floor(Math.random() * 3)
})
}
render() {
return (
<div>
<NumberBox number={this.state.number} />
<button onClick={this.hanldeBtn}>生成隨機數(shù)</button>
</div>
)
}
}
class NumberBox extends React.Component {
// 將要更新UI的時候會執(zhí)行這個鉤子函數(shù)
shouldComponentUpdate(nextProps, nextState) {
// 判斷一下當前生成的 值是否與頁面的值相等
if (nextProps.number !== this.props.number) {
return true
}
return false
}
render() {
return (
<h1>隨機數(shù):{this.props.number} </h1>
)
}
}
純組件
- 純組件: PureComponent 與 React.Component 功能相似
- 區(qū)別: PureComponent 內(nèi)部自動實現(xiàn)了 shouldComponentUpdate鉤子拷获,不需要手動比較
- 原理:純組件內(nèi)部通過分別比對前后兩次 props和state的值篮撑,來決定是否重新渲染組件
class App extends React.PureComponent{
render(){
return (
<div>純組件</div>
)
}
}
實現(xiàn)原理
- 說明:純組件內(nèi)部的對比是 shallow compare(淺層對比)
- 對于值類型來說:比較兩個值是否相同
- 引用類型:只比對對象的引用地址是否相同
const obj = { name: '肖' };
const newObj = obj;
newObj.name = '哈';
console.log(newObj == obj); //ture
- 注意:state 或 props 中屬性值為引用類型時,應該創(chuàng)建新數(shù)據(jù)匆瓜,不要直接修改原數(shù)據(jù)
虛擬DOM和Diff算法
本質(zhì)上就是一個JS對象赢笨,用來描述你希望在屏幕上看到的內(nèi)容
Diff算法
- 初次渲染時,React會根據(jù)初始化的state(model)驮吱,創(chuàng)建一個虛擬DOM對象(樹)
- 根據(jù)虛擬DOM生成真正的DOM茧妒,渲染到頁面
- 當數(shù)據(jù)變化后(setState()),會重新根據(jù)新的數(shù)據(jù)左冬,創(chuàng)建新的虛擬DOM對象(樹)
- 與上一次得到的虛擬DOM對象桐筏,使用Diff算法比對(找不同),得到需要更新的內(nèi)容
-
最終拇砰,React只將變化的內(nèi)容更新(patch)到DOM中梅忌,重新渲染到頁面
image.png
React路由
現(xiàn)代的前端應用大多數(shù)是SPA(單頁應用程序)绊袋,也就是只有一個HTML頁面的應用程序。因為它的用戶體驗更好铸鹰、對服務器壓力更小癌别,所以更受歡迎。為了有效的使用單個頁面來管理多頁面的功能蹋笼,前端路由應運而生展姐。
- 前端路由功能:讓用戶從一個視圖(頁面)導航到另一個視圖(頁面)
- 前端路由是一套映射規(guī)則,在React中剖毯,是URL路徑與組件的對應關系
- 使用React路由簡單來說圾笨,就是配置路徑和組件
安裝
npm install --save react-router-dom
導入
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
// 導入
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
// 定義內(nèi)容
const First = () => {
return <p>頁面一的內(nèi)容</p>
}
//使用Router組件包裹整個應用
const App = () => {
return (
<Router>
<div>
{/* 指定路由入口 */}
<Link to="/first">頁面一</Link>
{/* 指定路由出口 */}
<Route path="/first" component={First}></Route>
</div>
</Router>
)
}
常用組件說明
-
Router組件:包裹整個應用,一個React應用只需要使用一次
- 兩種常用的Router: HashRouter和BrowserRouter
- HashRouter: 使用URL的哈希值實現(xiàn) (localhost:3000/#/first)
- 推薦 BrowserRouter:使用H5的history API實現(xiàn)(localhost3000/first)
-
Link組件:用于指定導航鏈接(a標簽)
- 最終Link會編譯成a標簽逊谋,而to屬性會被編譯成 a標簽的href屬性
-
Route組件:指定路由展示組件相關信息
- path屬性:路由規(guī)則擂达,這里需要跟Link組件里面to屬性的值一致
- component屬性:展示的組件
- Route寫在哪,渲染出來的組件就在哪
路由的執(zhí)行過程
- 當我們點擊Link組件的時候胶滋,修改了瀏覽器地址欄中的url
- React路由監(jiān)聽地址欄url的變化
-
React路由內(nèi)部遍歷所有的Route組件板鬓,拿著Route里面path規(guī)則與pathname進行匹配
image.png - 當路由規(guī)則(path)能夠匹配地址欄中的pathname時,就展示該Route組件的內(nèi)容
編程式導航
- 場景:點擊登陸按鈕究恤,登陸成功后俭令,通過代碼跳轉(zhuǎn)到后臺首頁,如何實現(xiàn)部宿?
- 編程式導航:通過JS代碼來實現(xiàn)頁面跳轉(zhuǎn)
- history是React路由提供的抄腔,用于獲取瀏覽器歷史記錄的相關信息
- push(path):跳轉(zhuǎn)到某個頁面,參數(shù)path表示要跳轉(zhuǎn)的路徑
- go(n):前進或后退功能理张,參數(shù)n表示前進或后退頁面數(shù)量
this.props.history.push('路由')
默認路由
- 現(xiàn)在的路由都是通過點擊導航菜單后展示的赫蛇,如果進入頁面的時候就主動觸發(fā)路由呢
- 默認路由:表示進入頁面時就會匹配的路由
- 默認路由:只需要把path設置為
'/'
<Route path="/" component={Home}></Route>
匹配模式
模糊匹配模式
- 當Link組件的to屬性值為 '/login' 時候,為什么默認路由也被匹配成功?
- 默認情況下,React路由是模糊匹配模式
- 模糊匹配規(guī)則:只要pathname以path開頭就會匹配成功
// 定義內(nèi)容
const First = () => {
return <p>頁面一的內(nèi)容</p>
}
//使用Router組件包裹整個應用
const App = () => {
return (
<Router>
<div>
{/* 指定路由入口 */}
<Link to="/first">頁面一</Link>
{/* 指定路由出口 */}
{/* 模糊匹配 匹配成功*/}
<Route path="/" component={First}></Route>
</div>
</Router>
)
}
- path 代表Route組件的path屬性
- pathname 代表Link組件的to屬性(也就是
location.pathname
)
image.png
精準匹配
- 默認路由認可情況下都會展示汤纸,如果避免這種問題?
- 給Route組件添加exact屬性作煌,讓其變?yōu)?strong>精準匹配模式
- 精確匹配:只有當path和pathname完全匹配時才會展示改路由
{/* 此時,該組件只能匹配pathname="/" 這種情況 */}
<Route exact path="/" component={First}></Route>