react 高級特性整理

這一篇會整理一些react常見的高級特性以及它們的應用場景:

  • 函數(shù)組件
  • 非受控組件
  • protals
  • context
  • 異步加載組件
  • shouldComponentUpdate 優(yōu)化
  • pureComponent 和 memo優(yōu)化
  • 了解Immutable概念

還有一部分關于組件公共邏輯抽離的高級特性酷含,由于篇幅太長,我會另寫一篇來介紹:

  • HOC 高階組件
  • Render Props

函數(shù)組件

  • 純函數(shù),輸入props跃闹,輸出JSX
  • 沒有實例惩阶,沒有生命周期蜈亩,不包含state狀態(tài)
  • 不能擴展其它方法
類組件和函數(shù)組件對比

兩者的選擇:

  • 如果僅是視圖展示,沒有狀態(tài)的話短纵,邏輯簡單远舅,建議使用函數(shù)組件闰蛔;
  • 如果要定義內部狀態(tài)竞思,邏輯比較復雜,可能用到生命周期的話钞护,建議使用類組件

非受控組件

非受控組件盖喷,就是不受組件內部state控制的組件,這時表單數(shù)據(jù)將交由 DOM 節(jié)點來處理:

  • 初始值由defaultValuedefaultChecked來使用state賦值
  • 但表單內容修改后难咕,對應的state值不會修改课梳,因為沒有通過onChange等事件回傳
  • 要拿到表單內容修改的值,會使用refref通過createRef來創(chuàng)建)來獲取對應dom余佃,然后獲取對應的值
import React, {Component} from 'react'
// // class 類組件
class NonFormInput extends Component {
  constructor(props) {
    super(props)
    this.state = {
      name: '小花', 
      flag: true
    }
    // 創(chuàng)建ref暮刃,react要通過createRef來創(chuàng)建,不能像vue一樣直接使用字符串
    this.nameInputRef = React.createRef()
    this.fileInputRef = React.createRef()
  }
  render() {
    const {name, flag} = this.state
    return <div>
      {/* 
        使用defaultValue賦初始值 
        ref的作用就是用來標識dom的爆土,如vue中的ref="xxx"
      */}
      <input defaultValue={name} ref={this.nameInputRef}/>
      {/* this.state.name不會隨著表單內容改變 */}
      <span>state.name:{name}</span>
      <br/>
      <button onClick={this.alertName}>alert name</button>

      <hr/>
      <input type="file" ref={this.fileInputRef}/>
      <button onClick={this.alertFile}>alert file</button>
    </div>
  }
  alertName = () => {
    // ref指代的dom元素椭懊,<input value="小花">
    console.log(this.nameInputRef.current)  
    // value值
    alert(this.nameInputRef.current.value)  
  }
  alertFile = () => {
    const ele = this.fileInputRef.current
    console.log(ele.files[0].name)
  }
}
export default NonFormInput
image.png

使用場景:

  • 必須手動操作Dom元素,setState實現(xiàn)不了的
  • 常見的有文件上傳<input type=file>步势,因為它的值只能由用戶設置氧猬,不能通過代碼控制
  • 某些富文本編輯器,需要傳入dom元素

受控和非受控選擇:

  • 優(yōu)先使用受控組件坏瘩,符合react設計原則
  • 必須操作dom時盅抚,再使用非受控組件

Portals

Portals是將組件渲染到指定到dom元素上,可以是脫離父組件甚至是root根元素倔矾,放到其以外的元素上妄均,類似vue3 teleport的作用

先看下未使用Portals樣子:

// ProtalsDemo.js
import React, {Component} from 'react'

class ProtalsDemo extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return <div className="model">
      {/* this.props.children等于vue中的slot插槽 */}
      {this.props.children}
    </div>
  }
}
export default ProtalsDemo

// 在index.js引入組件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
import ProtalsDemo from './advance/ProtalsDemo'
ReactDOM.render(
  <React.StrictMode>
    <ProtalsDemo>
      model內容
    </ProtalsDemo>
  </React.StrictMode>,
  document.getElementById('root')
);
reportWebVitals();
正常組件的層級

使用Portals后:
需要使用ReactDOM.createPortal創(chuàng)建protal對象,它有兩個參數(shù):1. 要改變位置的組件哪自, 2. 要改變的目標dom位置

// ProtalsDemo.js

import React, {Component} from 'react'
// 需要先引入 ReactDOM
import ReactDOM from 'react-dom'
class ProtalsDemo extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    // 使用 Portal 將組件放在指定的dom元素上
    // 兩個參數(shù):第一個為要顯示組件
    // 第二個為要放至的dom元素位置
    return ReactDOM.createPortal(
      <div className="model">
        {this.props.children}
      </div>,
      document.body
    )
  }

}
export default ProtalsDemo
portal

使用場景:

  • 父級元素 overflow:hidden丰包,遮擋子元素的展示
  • 父組件的z-index值太小,導致子組件內容被遮擋
  • fixed布局的組件需要放在body第一層級的壤巷,比如上面例子的彈窗

context

上下文context用于向每個組件傳遞一些公共信息邑彪,當組件嵌套層級過深時,使用props傳遞太過麻煩隙笆,使用redux又太過度設計锌蓄,這時就會使用context來傳遞升筏,它的作用類似于vue中的provide inject的作用
它的使用方法如下:

  1. 創(chuàng)建一個自定義的context上下文對象撑柔,比如ThemeContext
    這個對象是祖先子孫組件中的樞紐,所有組件要通過它來進行通信
// contextType.js
import React from 'react'
// 1. 通過createContext創(chuàng)建一個`context`對象
// theme: 是自定義的上下文名稱
// ThemeContext: 是自定義的上下文對象您访,是后續(xù)祖先孫子組件的中間承接者铅忿,所以要導出方便子孫組件使用
export const ThemeContext = React.createContext('theme')
  1. 在父組件中引入剛定義的上下文對象ThemeContext,并使用ThemeContext.Provide組件包裹所有子孫組件灵汪,并在其value屬性上設置要共享的狀態(tài)
// Fahter.js
import React from 'react'
// 導入context上下文對象
import { ThemeContext } from './contextType'
import Son from './Son'

export default class Father extends React.Component {
  constructor(props) {
    super(props)
    // 2. 最外層組件定義要共享的變量檀训,比如這里共享主題顏色 themeColor
    this.state = {
      themeColor: 'light'
    }
  }
  render() {
    let { themeColor } = this.state
    // 3. 由最外層組件使用上下文變量ThemeContext柑潦,通過Provide提供要共享的數(shù)據(jù)value
    return <ThemeContext.Provider value={themeColor}> 
      <div>
        這是父組件的內容內容內容
        <Son />
        <button onClick={this.changeTheme}>改變主題</button>
      </div>
    </ThemeContext.Provider>
  }
  changeTheme = () => {
    this.setState({
      themeColor: 'dark'
    })
  }
}
  1. 在子組件中使用時,類組件函數(shù)組件使用context對象的方式是不一樣的峻凫,下面我使用兩個組件例子來說明渗鬼,子組件使用類組件,孫組件中使用函數(shù)組件

  2. 子組件(類組件)使用context

    • 導入上下文對象ThemeContext
    • 給類組件設置當前組件的contextType荧琼,指明這個組件要共享的上下文對象:Son.contextType = ThemeContext
    • 通過this.context獲取父組件傳的共享狀態(tài)并使用
import React from 'react'
// 4. 子組件中導入上下文對象
import { ThemeContext } from './contextType'
// 導入孫子組件
import Grandson from './Grandson'

class Son extends React.Component {
  render() {
    // 6. 通過this.context獲取共享數(shù)據(jù)
    const theme = this.context
    // 7. 在子組件中正常使用即可      
    return <div>
        這是子組件的內容譬胎,從父組件中獲取的共享數(shù)據(jù)為: {theme}
        <Grandson />
      </div>
  }
}

// 5. 類組件設置當前組件的contextType,指明這個組件要共享的上下文對象
Son.contextType = ThemeContext

export default Son
  1. 孫組件(函數(shù)組件)中使用
    • 導入上下文對象ThemeContext
    • 使用上下文對象的Consumer組件命锄,通過回調函數(shù)方式來獲取對應的共享狀態(tài)
// 8. 孫組件中導入上下文對象
import { ThemeContext } from './contextType'

export default function Grandson(props) {

  // 9. 函數(shù)組件沒辦法從this中獲取context堰乔,所以要借助上下文對象ThemeContext的Consumer來獲取
  return <ThemeContext.Consumer>
    { value => <p>這是孫子函數(shù)組件,從Father組件中獲取到共享數(shù)據(jù): {value}</p> }
  </ThemeContext.Consumer>
}
context使用

context使用

異步組件加載

  • React.lazy
    React.lazy通常會和Suspense結合脐恩,來達成異步加載的效果镐侯,它類似vue3 defineAsyncComponent的作用;
import React,{ Component, Suspense } from 'react';
// 異步導入組件
const AsyncComp = React.lazy(() => import('./FormInput'))
class SuspenseDemo extends Component {
  render() {
      // fallback代表異步操作之前的展示效果
     return <Suspense fallback={<div>Loading...</div>}>
        {/* 這里是異步引入的組件 */}
        <AsyncComp/>
      </Suspense>
  }
}

export default SuspenseDemo;

shouldComponentUpdate 優(yōu)化

shouldComponentUpdatereact的一個生命周期驶冒,顧名思義苟翻,就是用于設置是否進行組件更新,常用的場景是用來優(yōu)化子組件的渲染
SCU默認返回true骗污,即react默認重新渲染所有子組件袜瞬,當父組件內容更新時,所有子組件都要更新身堡,無論這子組件內容是否有更新邓尤;
我們可以在子組件的shouldComponentUpdate生命周期中設置,只有當子組件某些狀態(tài)(注意這里最好是用不可變狀態(tài)來判斷贴谎,否則性能優(yōu)化代價太大)發(fā)生更新時汞扎,我們才返回true讓其重新渲染,從而提升渲染性能擅这;否則返回false澈魄,不渲染

shouldComponentUpdate(nextProps, nextState) {
     // 只有父組件xxx狀態(tài)改變時,當前子組件才重新渲染
    if(nextProps.xxx !== this.props.xxx) {
      return true;
    }
    return false;
  }

因為這個講起來篇幅太長仲翎,這里不再擴展痹扇,想具體了解的,可以參考 shouldComponentUpdate

PureComponent 和 memo

PureComponentmeno其實就是react內部提供的具有SCU淺比較優(yōu)化的Component組件溯香,PureComponent(純組件)針對的是類組件的使用方式鲫构,而meno針對的是函數(shù)組件的使用方式,當props或者state改變時玫坛,PureComponent將對propsstate進行淺比較结笨,如果有發(fā)生改變的話,則重新渲染,否則不渲染炕吸。

注意伐憾,使用PureComponentmeno的前提是,使用不可變值的狀態(tài)赫模,否則這個淺比較是起不到優(yōu)化作用的

對大部分需求來說树肃,PureComponentmeno已經(jīng)能滿足性能優(yōu)化的需求了,但這要求我們設計的數(shù)據(jù)層級不要太深瀑罗,且要使用不可變量

PureComponent的使用非常簡單扫外,就是把React.Component換成React.PureCompoennt就可以了,它會隱式在SCU中對propsstate進行淺比較廓脆。

// 改為PureComponent
import React, { PureComponent } from 'react';
export default class PureCompDemo extends PureComponent {
  // ...
}

memo的用法筛谚,稍微麻煩一些,需要自己手寫一個類似的scu淺拷貝的方法停忿,然后通過React.memo將這個方法應用到函數(shù)組件返回:

import React from 'react'
// 要使用的函數(shù)組件
function Mycomponent(props) {
  console.log('render')
  return <p>{props.name}</p>
}

// 需要自己手寫一個類似scu的方法
function areEqual(preProps, nextProps) {
  // console.log(preProps.name, nextProps.name)
  if(preProps.name !== nextProps.name) {
    return true;
  }
  return false;
}

// 通過memo將手寫的SCU使用到函數(shù)組件中
export default React.memo(Mycomponent, areEqual)

了解 Immutable

前面我們多次提到Immutable不可變值的理念驾讲,但是是怎么使用的呢?

Immutable顧名思義席赂,就是不可改變的值吮铭,它是一種持久化數(shù)據(jù)。一旦被創(chuàng)建就不會被修改颅停。修改Immutable對象的時候返回新的Immutable谓晌。但是原數(shù)據(jù)不會改變。使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)的時候癞揉,會保證舊數(shù)據(jù)同時可用且不變纸肉,同時為了避免深度復制復制所有節(jié)點的帶來的性能損耗,Immutable使用了結構共享喊熟,即如果對象樹種的一個節(jié)點發(fā)生變化柏肪,只修改這個節(jié)點和受他影響的父節(jié)點,其他節(jié)點則共享芥牌。

Immutable其實不單react中可以使用烦味,在其它地方也可以使用,只不過它和react的理念十分緊密壁拉,所以通常會結合起來一起使用和說明谬俄。

先看下它的基本使用:
npm i immutable

import immutable from "immutable";

export default function ImmutableDemo() {
  let map = immutable.Map({
    name: '小花',
    age: 3
  })
  console.log(map)  // Map {size: 2, ...}
  // map原對象永遠不會改變,只有創(chuàng)建新對象
  let map1 = map.update('name', (val) => '小小')
  return <div>
    <p>{map.get('name')}</p>
    <p>{map.get('age')}</p>
    <p>{map1.get('name')}</p>
  </div>
}

簡單總結一下:

  • 是不可變值的
  • 基于共享數(shù)據(jù)(但不是深拷貝)弃理,速度好
  • 有一定的學習和遷移成本溃论,按需使用
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市案铺,隨后出現(xiàn)的幾起案子蔬芥,更是在濱河造成了極大的恐慌梆靖,老刑警劉巖控汉,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笔诵,死亡現(xiàn)場離奇詭異,居然都是意外死亡姑子,警方通過查閱死者的電腦和手機乎婿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來街佑,“玉大人谢翎,你說我怎么就攤上這事°逯迹” “怎么了森逮?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長磁携。 經(jīng)常有香客問我褒侧,道長,這世上最難降的妖魔是什么谊迄? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任闷供,我火速辦了婚禮,結果婚禮上统诺,老公的妹妹穿的比我還像新娘歪脏。我一直安慰自己,他們只是感情好粮呢,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布婿失。 她就那樣靜靜地躺著,像睡著了一般啄寡。 火紅的嫁衣襯著肌膚如雪移怯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天这难,我揣著相機與錄音舟误,去河邊找鬼。 笑死姻乓,一個胖子當著我的面吹牛嵌溢,可吹牛的內容都是我干的。 我是一名探鬼主播蹋岩,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼赖草,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剪个?” 一聲冷哼從身側響起秧骑,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乎折,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绒疗,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年骂澄,在試婚紗的時候發(fā)現(xiàn)自己被綠了吓蘑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡坟冲,死狀恐怖磨镶,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情健提,我是刑警寧澤琳猫,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站私痹,受9級特大地震影響脐嫂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜侄榴,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一雹锣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧癞蚕,春花似錦蕊爵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至恒水,卻和暖如春会放,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钉凌。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工咧最, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人御雕。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓矢沿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酸纲。 傳聞我的和親對象是個殘疾皇子捣鲸,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容