進階react.js

組件生命周期

組件的生命周期有助于理解組件的運行方式坏晦,完成更復雜的組件功能、分析組件錯誤原因等
組件的生命周期: 組件從被創(chuàng)建到掛載到頁面中運行爱榔,再到組件不在時卸載的過程
生命周期的每個階段總是伴隨著一些方法調(diào)用翁狐,這些方法就是生命周期的鉤子函數(shù)
構造函數(shù)的作用:為開發(fā)人員在不同階段操作組件提供了實際


image.png
創(chuàng)建時(掛載階段)
image.png
class App extends React.Component {
  constructor() {
    super()
    console.log(1)
  }
  componentDidMount() {
    console.log(3)
  }
  render() {
    console.log(2)
    return (
      <div>
        啊哈
      </div>
    )
  }
}
image.png
更新時

執(zhí)行時機:setState()办铡、 forceUpdate()双抽、 組件接收到新的props
說明:以上三者任意一種變化百框,組件就會重新渲染

image.png

卸載時

執(zhí)行時機:組件從頁面中消失
作用:用來做清理操作


image.png

新版完整生命鉤子函數(shù)

image.png

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
    • 更新組件
  • 過程:父組件重新渲染時沫勿,也會重新渲染子組件挨约,但只會渲染當前組件子樹(當前組件以其所有子組件)

image.png

組件性能優(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)容


image.png
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>
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赚瘦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奏寨,更是在濱河造成了極大的恐慌起意,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件病瞳,死亡現(xiàn)場離奇詭異揽咕,居然都是意外死亡悲酷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門亲善,熙熙樓的掌柜王于貴愁眉苦臉地迎上來设易,“玉大人,你說我怎么就攤上這事蛹头《俜危” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵渣蜗,是天一觀的道長屠尊。 經(jīng)常有香客問我,道長耕拷,這世上最難降的妖魔是什么讼昆? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮骚烧,結果婚禮上浸赫,老公的妹妹穿的比我還像新娘。我一直安慰自己赃绊,他們只是感情好掺炭,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凭戴,像睡著了一般涧狮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上么夫,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天者冤,我揣著相機與錄音,去河邊找鬼档痪。 笑死涉枫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的腐螟。 我是一名探鬼主播愿汰,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乐纸!你這毒婦竟也來了衬廷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汽绢,失蹤者是張志新(化名)和其女友劉穎吗跋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡跌宛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年酗宋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疆拘。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜕猫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哎迄,到底是詐尸還是另有隱情回右,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布芬失,位于F島的核電站楣黍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏棱烂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一哩治、第九天 我趴在偏房一處隱蔽的房頂上張望衬鱼。 院中可真熱鬧业筏,春花似錦、人聲如沸鸟赫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抛蚤。三九已至台谢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岁经,已是汗流浹背朋沮。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缀壤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓筋夏,卻偏偏與公主長得像苍糠,于是被迫代替她去往敵國和親叁丧。 傳聞我的和親對象是個殘疾皇子岳瞭,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

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

  • 函數(shù)是面向過程的拥娄,函數(shù)的調(diào)用不需要主體,而方法是屬于對象的瞳筏,調(diào)用方法需要一個主體-即對象姚炕。 npm install...
    Gukson666閱讀 467評論 0 3
  • 0. 前言 “任時光匆匆流去掸刊,我只在乎你”石窑,最近這些時間都沒怎么更新文章,希望大家不要見怪啊,哈哈烁兰!最近公司在做r...
    舊丶時候閱讀 1,437評論 5 2
  • 一币喧、扎實基礎,無招勝有招 前端領域各種新技術朱巨、新思想不斷涌現(xiàn)史翘,AngularJS、React、Vue.js琼讽、Nod...
    AC編程閱讀 1,695評論 0 2
  • 我又膨脹了必峰,現(xiàn)在打算寫一寫Redux的原理了... Redux原理 這里我們先講redux最簡單實現(xiàn)∽甑牛空講原理吼蚁,不...
    IUVO閱讀 330評論 0 0
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險问欠,但是人生放棄了冒險肝匆,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 6,050評論 0 4