react16常見api

Vue 與 React 兩個框架的粗略區別對比

Vue 的優勢包括:

  1. 模板和渲染函數的彈性選擇
  2. 簡單的語法及項目創建
  3. 更快的渲染速度和更小的體積

React 的優勢包括:

  1. 更適用於大型應用和更好的可測試性
  2. 同時適用於 Web 端和原生 App
  3. 更大的生態圈帶來的更多支持和工具

相似之處

React 與 Vue 有很多相似之處,React 和 Vue 都是非常優秀的框架,它們之間的相似之處多過不同之處,並且它們大部分最棒的功能是相通的:如他們都是 JavaScript 的 UI 框架,專注於創造前端的富應用。不同於早期的 JavaScript 框架“功能齊全”,Reat 與 Vue 只有框架的骨架,其他的功能如路由、狀態管理等是框架分離的組件。

  • 兩者都是用於創建 UI 的 JavaScript 庫;
  • 兩者都快速輕便;
  • 都有基於組件的架構;
  • 都是用虛擬 DOM;
  • 都可放入單個 HTML 文件中,或者成為更復雜 webpack 設置中的模塊;
  • 都有獨立但常用的路由器和狀態管理庫;
  • 它們之間的最大區別是 Vue 通常使用 HTML 模板文件,而 React 則完全是 JavaScript。Vue 有雙向綁定語法糖。

不同點

  • Vue 組件分為全局註冊和局部註冊,在 react 中都是通過 import 相應組件,然後模版中引用;
  • props 是可以動態變化的,子組件也實時更新,在 react 中官方建議 props 要像純函數那樣,輸入輸出一致對應,而且不太建議通過 props 來更改視圖;
  • 子組件一般要顯示地調用 props 選項來聲明它期待獲得的數據。而在 react 中不必需,另兩者都有 props 校驗機制;
  • 每個 Vue 實例都實現了事件接口,方便父子組件通信,小型項目中不需要引入狀態管理機制,而 react 必須自己實現;
  • 使用插槽分發內容,使得可以混合父組件的內容與子組件自己的模板;
  • 多了指令系統,讓模版可以實現更豐富的功能,而 React 只能使用 JSX 語法;
  • Vue 增加的語法糖 computed 和 watch,而在 React 中需要自己寫一套邏輯來實現;
  • react 的思路是 all in js,通過 js 來生成 html,所以設計了 jsx,還有通過 js 來操作 css,社區的 styled-component、jss 等;而 vue 是把 html,css,js 組合到一起,用各自的處理方式,vue 有單文件組件,可以把 html、css、js 寫到一個文件中,html 提供了模板引擎來處理。
  • react 做的事情很少,很多都交給社區去做,vue 很多東西都是內置的,寫起來確實方便一些, 比如 redux 的 combineReducer 就對應 vuex 的 modules, 比如 reselect 就對應 vuex 的 getter 和 vue 組件的 computed, vuex 的 mutation 是直接改變的原始數據,而 redux 的 reducer 是返回一個全新的 state,所以 redux 結合 immutable 來優化性能,vue 不需要。
  • react 是整體的思路的就是函數式,所以推崇純組件,數據不可變,單向數據流,當然需要雙向的地方也可以做到,比如結合 redux-form,組件的橫向拆分一般是通過高階組件。而 vue 是數據可變的,雙向綁定,聲明式的寫法,vue 組件的橫向拆分很多情況下用 mixin。

社區活躍度

從兩者的 github 表現來看(數據取於 2019-09-16)

react16常見api


react16常見api

可以看出 vue 的 star 數量已經是前端框架中最火爆的。從維護上來看,react 是 facebook 在維護,而 vue 現階段雖然也有了團隊,但主要還是尤雨溪在維護貢獻代碼,並且阿里巴巴開源的混合式框架 weex 也是基於 vue 的,所以我們相信 vue 未來將會得到更多的人和團隊維護。

根據不完全統計,包括餓了麼、簡書、高德、稀土掘金、蘇寧易購、美團、天貓、荔枝 FM、房多多、Laravel、htmlBurger 等國內外知名大公司都在使用 vue 進行新項目的開發和舊項目的前端重構工作。

使用 React 的公司 facebook、Twitter、INS、Airbnb、Yahoo、ThoughtWorks、螞蟻金服、阿里巴巴、騰訊、百度、口碑、美團、滴滴出行、餓了麼、京東、網易等。

UI 生態


vuereactpc 端iview、element 等Ant Design、Materal-UI 等h5 端有贊 vant、mintui 等Ant Design Mobile、weui混合開發weexui、bui-weexteaset、react-native-elements微信小程序iview、Weapp、zanuiiView Weapp、Taro UI

無論您選擇React.js還是Vue.js,兩個框架都沒有相當大的差異,根據您的要求,這個決定是非常主觀的。如果您想將前端JavaScript框架集成到現有應用程序中,Vue.js是更好的選擇,如果您想使用JavaScript構建移動應用程序,React絕對是您的選擇。

react16 版本常見 api

先來看一下 react 暴露出來的 API

<code>const React = {  Children: {    map,    forEach,    count,    toArray,    only  },  createRef,  Component,  PureComponent,  createContext,  forwardRef,  Fragment: REACT_FRAGMENT_TYPE,  StrictMode: REACT_STRICT_MODE_TYPE,  unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,  unstable_Profiler: REACT_PROFILER_TYPE,  createElement: __DEV__ ? createElementWithValidation : createElement,  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,  isValidElement: isValidElement,  version: ReactVersion,  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals}/<code>

Children

這個對象提供了一堆幫你處理 props.children 的方法,因為 children 是一個類似數組但是不是數組的數據結構,如果你要對其進行處理可以用 React.Children 外掛的方法。

createRef

新的 ref 用法,React 即將拋棄 這種 string ref 的用法,將來你只能使用兩種方式來使用 ref

<code>class App extends React.Component {  constructor() {    this.ref = React.createRef()  }  render() {    return 

// or return

createContext

(this.funRef = node)} /> }}

createContext 是官方定稿的 context 方案,在這之前我們一直在用的老的 context API 都是 React 不推薦的 API,現在新的 API 釋出,官方也已經確定在 17 大版本會把老 API 去除(老 API 的性能不是一般的差)。

新 API 的使用方法:

<code>const { Provider, Consumer } = React.createContext('defaultValue')const ProviderComp = (props) => (      {props.children}  )const ConsumerComp = () => (      {(value) => 

{value}

} )/<code>

react 生命週期

目前 react 16.8 +的生命週期分為三個階段,分別是掛載階段、更新階段、卸載階段

  • 掛載階段: constructor(props): 實例化。
    staticgetDerivedStateFromProps 從 props 中獲取 state。
    render 渲染。
    componentDidMount: 完成掛載。
  • 更新階段: staticgetDerivedStateFromProps 從 props 中獲取 state。
    shouldComponentUpdate 判斷是否需要重繪。
    render 渲染。
    getSnapshotBeforeUpdate 獲取快照。
    componentDidUpdate 渲染完成後回調。
  • 卸載階段: componentWillUnmount 即將卸載。
  • 錯誤處理: staticgetDerivedStateFromError 從錯誤中獲取 state。
    componentDidCatch 捕獲錯誤並進行處理。
<code>class ExampleComponent extends react.Component {  // 構造函數,最先被執行,我們通常在構造函數里初始化state對象或者給自定義方法綁定this  constructor() {}  //getDerivedStateFromProps(nextProps, prevState)用於替換 `componentWillReceiveProps` ,該函數會在初始化和 `update` 時被調用  // 這是個靜態方法,當我們接收到新的屬性想去修改我們state,可以使用getDerivedStateFromProps  static getDerivedStateFromProps(nextProps, prevState) {    // 新的鉤子 getDerivedStateFromProps() 更加純粹, 它做的事情是將新傳進來的屬性和當前的狀態值進行對比, 若不一致則更新當前的狀態。    if (nextProps.riderId !== prevState.riderId) {      return {        riderId: nextProps.riderId      }    }    // 返回 null 則表示 state 不用作更新    return null  }  // shouldComponentUpdate(nextProps, nextState),有兩個參數nextProps和nextState,表示新的屬性和變化之後的state,返回一個布爾值,true表示會觸發重新渲染,false表示不會觸發重新渲染,默認返回true,我們通常利用此生命週期來優化react程序性能  shouldComponentUpdate(nextProps, nextState) {    return nextProps.id !== this.props.id  }  // 組件掛載後調用  // 可以在該函數中進行請求或者訂閱  componentDidMount() {}  // getSnapshotBeforeUpdate(prevProps, prevState):這個方法在render之後,componentDidUpdate之前調用,有兩個參數prevProps和prevState,表示之前的屬性和之前的state,這個函數有一個返回值,會作為第三個參數傳給componentDidUpdate,如果你不想要返回值,可以返回null,此生命週期必須與componentDidUpdate搭配使用  getSnapshotBeforeUpdate() {}  // 組件即將銷燬  // 可以在此處移除訂閱,定時器等等  componentWillUnmount() {}  // 組件銷燬後調用  componentDidUnMount() {}  // componentDidUpdate(prevProps, prevState, snapshot):該方法在getSnapshotBeforeUpdate方法之後被調用,有三個參數prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三個參數是getSnapshotBeforeUpdate返回的,如果觸發某些回調函數時需要用到 DOM 元素的狀態,則將對比或計算的過程遷移至 getSnapshotBeforeUpdate,然後在 componentDidUpdate 中統一觸發回調或更新狀態。  componentDidUpdate() {}  // 渲染組件函數  render() {}  // 以下函數不建議使用  UNSAFE_componentWillMount() {}  UNSAFE_componentWillUpdate(nextProps, nextState) {}  UNSAFE_componentWillReceiveProps(nextProps) {}}/<code> 

react 版本 17 將棄用幾個類組件 API 生命週期: componentWillMount, componentWillReceiveProps和 componentWillUpdate。

react 事件機制

簡單的理解 react 如何處理事件的,React 在組件加載(mount)和更新(update)時,將事件通過 addEventListener 統一註冊到 document 上,然後會有一個事件池存儲了所有的事件,當事件觸發的時候,通過 dispatchEvent 進行事件分發。

引用新手學習 react 迷惑的點(二)

  • react 裡面綁定事件的方式和在 HTML 中綁定事件類似,使用駝峰式命名指定要綁定的 onClick 屬性為組件定義的一個方法{this.handleClick.bind(this)}。
  • 由於類的方法默認不會綁定 this,因此在調用的時候如果忘記綁定,this 的值將會是 undefined。通常如果不是直接調用,應該為方法綁定 this,將事件函數上下文綁定要組件實例上。

綁定事件的四種方式

<code>class Button extends react.Component {  constructor(props) {    super(props)    this.handleClick1 = this.handleClick1.bind(this)  }  //方式1:在構造函數中使用bind綁定this,官方推薦的綁定方式,也是性能最好的方式  handleClick1() {    console.log('this is:', this)  }  //方式2:在調用的時候使用bind綁定this  handleClick2() {    console.log('this is:', this)  }  //方式3:在調用的時候使用箭頭函數綁定this  // 方式2和方式3會有性能影響並且當方法作為屬性傳遞給子組件的時候會引起重渲問題  handleClick3() {    console.log('this is:', this)  }  //方式4:使用屬性初始化器語法綁定this,需要babel轉義  handleClick4 = () => {    console.log('this is:', this)  }  render() {    return (       

) }}/<code>

為什麼直接調用方法會報錯

<code>class Foo extends React.Component {  handleClick() {    this.setState({ xxx: aaa })  }  render() {    return   }}/<code>

會被 babel 轉化成

<code>React.createElement(  'button',  {    onClick: this.handleClick  },  'Click me')/<code>

“合成事件”和“原生事件”

react 實現了一個“合成事件”層( syntheticeventsystem),這抹平了各個瀏覽器的事件兼容性問題。所有事件均註冊到了元素的最頂層-document 上,“合成事件”會以事件委託( eventdelegation)的方式綁定到組件最上層,並且在組件卸載( unmount)的時候自動銷燬綁定的事件。

react 組件開發

react 組件化思想

一個 UI 組件的完整模板
<code>import classNames from 'classnames'class Button extends react.Component {  //參數傳參與校驗  static propTypes = {    type: PropTypes.oneOf(['success', 'normal']),    onClick: PropTypes.func  }  static defaultProps = {    type: 'normal'  }  handleClick() {}  render() {    let { className, type, children, ...other } = this.props    const classes = classNames(      className,      'prefix-button',      'prefix-button-' + type    )    return (      

this.handleClick}> {children}

) }}/<code>

函數定義組件(Function Component)

純展示型的,不需要維護 state 和生命週期,則優先使用 FunctionComponent

  1. 代碼更簡潔,一看就知道是純展示型的,沒有複雜的業務邏輯
  2. 更好的複用性。只要傳入相同結構的 props,就能展示相同的界面,不需要考慮副作用。
  3. 打包體積小,執行效率高
<code>import react from 'react'function MyComponent(props) {  let { firstName, lastName } = props  return (          

{[firstName, lastName].join(' ')}

/<code>

會被 babel 轉義成

<code>return React.createElement(  'div',  null,  React.createElement('img', { src: '//p2.ttnews.xyz/5fd22eefe960a9acc5446110.jpg', className: 'profile' }),  React.createElement('h3', null, [firstName, lastName].join(' ')))/<code>

createElement 函數對 key 和 ref 等特殊的 props 進行處理,並獲取 defaultProps 對默認 props 進行賦值,並且對傳入的孩子節點進行處理,最終構造成一個 reactElement 對象(所謂的虛擬 DOM)。 reactDOM.render 將生成好的虛擬 DOM 渲染到指定容器上,其中採用了批處理、事務等機制並且對特定瀏覽器進行了性能優化,最終轉換為真實 DOM。

那麼, React.createElement 是在做什麼?看下相關部分代碼:

<code>var ReactElement = function(type, key, ref, self, source, owner, props) {  var element = {    // This tag allow us to uniquely identify this as a React Element    $$typeof: REACT_ELEMENT_TYPE,    // Built-in properties that belong on the element    type: type,    key: key,    ref: ref,    props: props,    // Record the component responsible for creating this element.    _owner: owner  }  // ...  return element}ReactElement.createElement = function(type, config, children) {  // ...  return ReactElement(    type,    key,    ref,    self,    source,    ReactCurrentOwner.current,    props  )}/<code>

ES6class 定義一個純組件( PureComponent)

組件需要維護 state 或使用生命週期方法,則優先使用 PureComponent


<code>class MyComponent extends react.Component {  render() {    let { name } = this.props    return 

Hello, {name}

}}/<code>
PureComponent

Component & PureComponent 這兩個類基本相同,唯一的區別是 PureComponent 的原型上多了一個標識, shallowEqual(淺比較),來決定是否更新組件,淺比較類似於淺複製,只會比較第一層。使用 PureComponent 相當於省去了寫 shouldComponentUpdate 函數

<code>if (ctor.prototype && ctor.prototype.isPureReactComponent) {  return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)}/<code>

這是檢查組件是否需要更新的一個判斷,ctor 就是你聲明的繼承自 Component or PureComponent 的類,他會判斷你是否繼承自 PureComponent,如果是的話就 shallowEqual 比較 state 和 props。

React 中對比一個 ClassComponent 是否需要更新,只有兩個地方。一是看有沒有 shouldComponentUpdate 方法,二就是這裡的 PureComponent 判斷

使用不可變數據結構 Immutablejs

Immutable.js 是 Facebook 在 2014 年出的持久性數據結構的庫,持久性指的是數據一旦創建,就不能再被更改,任何修改或添加刪除操作都會返回一個新的 Immutable 對象。可以讓我們更容易的去處理緩存、回退、數據變化檢測等問題,簡化開發。並且提供了大量的類似原生 JS 的方法,還有 LazyOperation 的特性,完全的函數式編程。

<code>import { Map } from 'immutable'const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 })const map2 = map1.set('b', 50)map1 !== map2 // truemap1.get('b') // 2map2.get('b') // 50map1.get('a') === map2.get('a') // true/<code>

可以看到,修改 map1 的屬性返回 map2,他們並不是指向同一存儲空間,map1 聲明瞭只有,所有的操作都不會改變它。

ImmutableJS提供了大量的方法去更新、刪除、添加數據,極大的方便了我們操縱數據。除此之外,還提供了原生類型與 ImmutableJS 類型判斷與轉換方法:

<code>import { fromJS, isImmutable } from 'immutable'const obj = fromJS({  a: 'test',  b: [1, 2, 4]}) // 支持混合類型isImmutable(obj) // trueobj.size() // 2const obj1 = obj.toJS() // 轉換成原生 `js` 類型/<code>

ImmutableJS 最大的兩個特性就是: immutable data structures(持久性數據結構)與 structural sharing(結構共享),持久性數據結構保證數據一旦創建就不能修改,使用舊數據創建新數據時,舊數據也不會改變,不會像原生 js 那樣新數據的操作會影響舊數據。而結構共享是指沒有改變的數據共用一個引用,這樣既減少了深拷貝的性能消耗,也減少了內存。

比如下圖:

react16常見api

左邊是舊值,右邊是新值,我需要改變左邊紅色節點的值,生成的新值改變了紅色節點到根節點路徑之間的所有節點,也就是所有青色節點的值,舊值沒有任何改變,其他使用它的地方並不會受影響,而超過一大半的藍色節點還是和舊值共享的。在 ImmutableJS 內部,構造了一種特殊的數據結構,把原生的值結合一系列的私有屬性,創建成 ImmutableJS 類型,每次改變值,先會通過私有屬性的輔助檢測,然後改變對應的需要改變的私有屬性和真實值,最後生成一個新的值,中間會有很多的優化,所以性能會很高。

高階組件( higher order component)

高階組件是一個以組件為參數並返回一個新組件的函數。HOC 運行你重用代碼、邏輯和引導抽象。

<code>function visible(WrappedComponent) {  return class extends Component {    render() {      const { visible, ...props } = this.props      if (visible === false) return null      return     }  }}/<code>

上面的代碼就是一個 HOC 的簡單應用,函數接收一個組件作為參數,並返回一個新組件,新組建可以接收一個 visible props,根據 visible 的值來判斷是否渲染 Visible。 最常見的還有 Redux 的 connect 函數。除了簡單分享工具庫和簡單的組合,HOC 最好的方式是共享 react 組件之間的行為。如果你發現你在不同的地方寫了大量代碼來做同一件事時,就應該考慮將代碼重構為可重用的 HOC。下面就是一個簡化版的 connect 實現:

<code>export const connect = (  mapStateToProps,  mapDispatchToProps) => WrappedComponent => {  class Connect extends Component {    static contextTypes = {      store: PropTypes.object    }    constructor() {      super()      this.state = {        allProps: {}      }    }    componentWillMount() {      const { store } = this.context      this._updateProps()      store.subscribe(() => this._updateProps())    }    _updateProps() {      const { store } = this.context      let stateProps = mapStateToProps        ? mapStateToProps(store.getState(), this.props)        : {}      let dispatchProps = mapDispatchToProps        ? mapDispatchToProps(store.dispatch, this.props)        : {}      this.setState({        allProps: {          ...stateProps,          ...dispatchProps,          ...this.props        }      })    }    render() {      return     }  }  return Connect}/<code>

代碼非常清晰,connect 函數其實就做了一件事,將 mapStateToProps 和 mapDispatchToProps 分別解構後傳給原組件,這樣我們在原組件內就可以直接用 props 獲取 state 以及 dispatch 函數了。

高階組件的應用

某些頁面需要記錄用戶行為,性能指標等等,通過高階組件做這些事情可以省去很多重複代碼。

日誌打點
<code>function logHoc(WrappedComponent) {  return class extends Component {    componentWillMount() {      this.start = Date.now()    }    componentDidMount() {      this.end = Date.now()      console.log(        `${WrappedComponent.dispalyName} 渲染時間:${this.end - this.start} ms`      )      console.log(`${user}進入${WrappedComponent.dispalyName}`)    }    componentWillUnmount() {      console.log(`${user}退出${WrappedComponent.dispalyName}`)    }    render() {      return     }  }}/<code>

可用、權限控制

<code>function auth(WrappedComponent) {  return class extends Component {    render() {      const { visible, auth, display = null, ...props } = this.props      if (visible === false || (auth && authList.indexOf(auth) === -1)) {        return display      }      return     }  }}/<code>
表單校驗

基於上面的雙向綁定的例子,我們再來一個表單驗證器,表單驗證器可以包含驗證函數以及提示信息,當驗證不通過時,展示錯誤信息:

<code>function validateHoc(WrappedComponent) {  return class extends Component {    constructor(props) {      super(props)      this.state = { error: '' }    }    onChange = event => {      const { validator } = this.props      if (validator && typeof validator.func === 'function') {        if (!validator.func(event.target.value)) {          this.setState({ error: validator.msg })        } else {          this.setState({ error: '' })        }      }    }    render() {      return (                  

{this.state.error || ''}

/<code>


<code>const validatorName = {  func: (val) => val && !isNaN(val),  msg: '請輸入數字'}const validatorPwd = {  func: (val) => val && val.length > 6,  msg: '密碼必須大於6位'}/<code>

HOC 的缺陷

  • HOC 需要在原組件上進行包裹或者嵌套,如果大量使用 HOC,將會產生非常多的嵌套,這讓調試變得非常困難。
  • HOC 可以劫持 props,在不遵守約定的情況下也可能造成衝突。

render props

一種在 React 組件之間使用一個值為函數的 prop 共享代碼的簡單技術 具有 render prop 的組件接受一個函數,該函數返回一個 React 元素並調用它而不是實現自己的渲染邏輯。

<code> 

Hello {data.target}

} />/<code>

setState 數據管理

不要直接更新狀態

<code>// Wrong 此代碼不會重新渲染組件,構造函數是唯一能夠初始化 this.state 的地方。this.state.comment = 'Hello'// Correct 應當使用 setState():this.setState({ comment: 'Hello' })/<code>

組件生命週期中或者 react 事件綁定中,setState 是通過異步更新的,在延時的回調或者原生事件綁定的回調中調用 setState 不一定是異步的。

  • 多個 setState() 調用合併成一個調用來提高性能。
  • this.props 和 this.state 可能是異步更新的,不應該依靠它們的值來計算下一個狀態。
<code>// Wrongthis.setState({  counter: this.state.counter + this.props.increment})// Correctthis.setState((prevState, props) => ({  counter: prevState.counter + props.increment}))/<code>

原生事件綁定不會通過合成事件的方式處理,會進入更新事務的處理流程。 setTimeout 也一樣,在 setTimeout 回調執行時已經完成了原更新組件流程,不會放入 dirtyComponent 進行異步更新,其結果自然是同步的。

setState 原理

setState 並沒有直接操作去渲染,而是執行了一個 updateQueue(異步 updater 隊列),

<code>setState( stateChange ) {    Object.assign( this.state, stateChange );    //合併接收到的state||stateChange改變的state(setState接收到的參數)    renderComponent( this );//調用render渲染組件}/<code>

這種實現,每次調用 setState 都會更新 state 並馬上渲染一次(不符合其更新優化機制),所以我們要合併 setState。

具體可以閱讀源碼 ReactUpdateQueue.js

ErrorBoundary、 Suspense 和 Fragment

ErrorBoundaries

react 16 提供了一個新的錯誤捕獲鉤子 componentDidCatch(error,errorInfo), 它能將子組件生命週期裡所拋出的錯誤捕獲, 防止頁面全局崩潰。demo componentDidCatch 並不會捕獲以下幾種錯誤

  • 事件機制拋出的錯誤(事件裡的錯誤並不會影響渲染)
  • Error Boundaries 自身拋出的錯誤
  • 異步產生的錯誤
  • 服務端渲染

lazy、Suspence 延遲加載組件

lazy 需要跟 Suspence 配合使用,否則會報錯。

lazy 實際上是幫助我們實現代碼分割 ,類似 webpack 的 splitchunk 的功能。

Suspense 意思是能暫停當前組件的渲染, 當完成某件事以後再繼續渲染。簡單來說就是減少首屏代碼的體積,提升性能。


<code>import react, { lazy, Suspense } from 'react'const OtherComponent = lazy(() => import('./OtherComponent'))function MyComponent() {  return (    loading.../<code>
)}/<code>


一種簡單的預加載思路, 可參考 preload

<code>const OtherComponentPromise = import('./OtherComponent')const OtherComponent = react.lazy(() => OtherComponentPromise)/<code>

Fragments(v16.2.0)

Fragments 允許你將子列表分組,避免向 DOM 添加額外的節點。

<code>render() {  return (    <>                      >  );}/<code>

reactFiber 架構分析

react-fiber是為了增強動畫、佈局、移動端手勢領域的適用性,最重要的特性是對頁面渲染的優化: 允許將渲染方面的工作拆分為多段進行。

reactFiber 架構解決了什麼問題

react-fiber 可以為我們提供如下幾個功能:

  • 設置渲染任務的優先
  • 採用新的 Diff 算法
  • 採用虛擬棧設計允許當優先級更高的渲染任務和較低優先的任務之間來回切換

Fiber 如何做到異步渲染 VirtualDom 和 Diff 算法

眾所周知,畫面每秒鐘更新 60 次,頁面在人眼中顯得流暢,無明顯卡頓。每秒 60 次,即 16ms 要更新一次頁面,如果更新頁面消耗的時間不到 16ms,那麼在下一次更新時機來到之前會剩下一點時間執行其他的任務,只要保證及時在 16ms 的間隔下更新界面就完全不會影響到頁面的流暢程度。fiber 的核心正是利用了 60 幀原則,實現了一個基於優先級和 requestIdleCallback 的循環任務調度算法。

<code>function fiber(剩餘時間) {  if (剩餘時間 > 任務所需時間) {    做任務  } else {    requestIdleCallback(fiber)    // requestIdleCallback 是瀏覽器提供的一個 api,可以讓瀏覽器在空閒的時候執行回調,    // 在回調參數中可以獲取到當前幀剩餘的時間,fiber 利用了這個參數,    // 判斷當前剩下的時間是否足夠繼續執行任務,    // 如果足夠則繼續執行,否則暫停任務,    // 並調用 requestIdleCallback 通知瀏覽器空閒的時候繼續執行當前的任務  }}/<code>

react hooks

在 react 16.7 之前, react 有兩種形式的組件, 有狀態組件(類)和無狀態組件(函數)。官方解釋:hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。個人理解:讓傳統的函數組件 function component 有內部狀態 state 的函數 function,簡單來說就是 hooks 讓函數組件有了狀態,可以完全替代 class。

接下來梳理 Hooks 中最核心的 2 個 api, useState 和 useEffect

useState

useState 是一個鉤子,他可以為函數式組件增加一些狀態,並且提供改變這些狀態的函數,同時它接收一個參數,這個參數作為狀態的默認值。

<code>const [count, setCount] = useState(initialState)/<code>

使用 Hooks 相比之前用 class 的寫法最直觀的感受是更為簡潔

<code>function App() {  const [count, setCount] = useState(0)  return (     

You clicked {count} times

/<code>

useEffect(fn)

在每次 render 後都會執行這個鉤子。可以將它當成是 componentDidMount、 componentDidUpdate``、componentWillUnmount 的合集。因此使用 useEffect 比之前優越的地方在於:

可以避免在 componentDidMount、 componentDidUpdate 書寫重複的代碼; 可以將關聯邏輯寫進一個 useEffect(在以前得寫進不同生命週期裡);

深入理解 react 原理

react 虛擬 dom 原理剖析

react 組件的渲染流程

使用 react.createElement 或 JSX 編寫 react 組件,實際上所有的 JSX 代碼最後都會轉換成 react.createElement(...),Babel 幫助我們完成了這個轉換的過程。

createElement 函數對 key 和 ref 等特殊的 props 進行處理,並獲取 defaultProps 對默認 props 進行賦值,並且對傳入的孩子節點進行處理,最終構造成一個 reactElement 對象(所謂的虛擬 DOM)。

reactDOM.render 將生成好的虛擬 DOM 渲染到指定容器上,其中採用了批處理、事務等機制並且對特定瀏覽器進行了性能優化,最終轉換為真實 DOM。

虛擬 DOM 的組成

即 reactElementelement 對象,我們的組件最終會被渲染成下面的結構:

<code>`type`:元素的類型,可以是原生 html 類型(字符串),或者自定義組件(函數或 class)`key`:組件的唯一標識,用於 Diff 算法,下面會詳細介紹`ref`:用於訪問原生 dom 節點`props`:傳入組件的 props,chidren 是 props 中的一個屬性,它存儲了當前組件的孩子節點,可以是數組(多個孩子節點)或對象(只有一個孩子節點)`owner`:當前正在構建的 Component 所屬的 Component`self`:(非生產環境)指定當前位於哪個組件實例`_source`:(非生產環境)指定調試代碼來自的文件(fileName)和代碼行數(lineNumber)/<code>

當組件狀態 state 在更改的時候,react 會自動調用組件的 render 方法重新渲染整個組件的 UI。當然如果真的這樣大面積的操作 DOM,性能會是一個很大的問題,所以 react 實現了一個 VirtualDOM,組件 DOM 結構就是映射到這個 VirtualDOM上,react 在這個 VirtualDOM 上實現了一個 diff 算法,當要重新渲染組件的時候,會通過 diff 尋找到要變更的 DOM 節點,再把這個修改更新到瀏覽器實際的 DOM 節點上,所以實際上不是真的渲染整個 DOM 樹。這個 VirtualDOM 是一個純粹的 JS 數據結構,所以性能會比原生 DOM 快很多。

react 是如何防止 XSS 的

reactElement 對象還有一個 $$typeof屬性,它是一個 Symbol 類型的變量 Symbol.for('react.element'),當環境不支持 Symbol 時, $$typeof 被賦值為 0xeac7。這個變量可以防止 XSS。如果你的服務器有一個漏洞,允許用戶存儲任意 JSON 對象, 而客戶端代碼需要一個字符串,這可能為你的應用程序帶來風險。JSON 中不能存儲 Symbol 類型的變量,而 react 渲染時會把沒有 \$\$typeof 標識的組件過濾掉。

diff 算法

傳統的 diff 算法通過循環遞歸對節點一次對比,效率很低,算法複雜度達到 O(n^3),其中 n 是樹中節點的總數,React 通過制定大膽的策略,將 O(n^3) 複雜度的問題轉換成 O(n) 複雜度的問題。

diff 策略:

  1. web ui 中 Dom 節點跨層級的移動操作很少, diff 算法比較新舊節點的時候,比較只會在同層級比較,不會跨層級比較
  2. 擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。
  3. 對於同一層級的一組子節點,他們可以通過唯一 key 進行區分

基於以上三個前提策略,React 分別對 tree diff、 component diff 以及 element diff 進行算法優化,事實也證明這三個前提策略是合理且準確的,它保證了整體界面構建的性能。簡單的講就是:

具體可以參考React 源碼剖析系列 - 不可思議的 react diff

  • React 通過制定大膽的 diff 策略,將 O(n3) 複雜度的問題轉換成 O(n) 複雜度的問題;
  • React 通過分層求異的策略,對 tree diff 進行算法優化;
  • React 通過相同類生成相似樹形結構,不同類生成不同樹形結構的策略,對 component diff 進行算法優化;
  • React 通過設置唯一 key 的策略,對 element diff 進行算法優化;

建議,在開發組件時,保持穩定的 DOM 結構會有助於性能的提升;建議,在開發過程中,儘量減少類似將最後一個節點移動到列表首部的操作,當節點數量過大或更新操作過於頻繁時,在一定程度上會影響 React 的渲染性能。


react 性能分析與優化

減少不必要的渲染

在使用 classComponent 進行開發的時候,我們可以使用 shouldComponentUpdate 來減少不必要的渲染,那麼在使用 react hooks 後,我們如何實現這樣的功能呢?

解決方案: React.memo和 useMemo 對於這種情況,react 當然也給出了官方的解決方案,就是使用 React.memo 和 useMemo。

React.memo

React.momo 其實並不是一個 hook,它其實等價於 PureComponent,但是它只會對比 props。使用方式如下(用上面的例子):

<code>import React, { useState } from 'react'export const Count = React.memo(props => {  const [data, setData] = useState({    count: 0,    name: 'cjg',    age: 18  })  const handleClick = () => {    const { count } = data    setData({      ...data,      count: count + 1    })  }  return })/<code>

useMemo

useMemo 它的用法其實跟 useEffects 有點像,我們直接看官方給的例子

<code>function Parent({ a, b }) {  // Only re-rendered if `a` changes:  const child1 = useMemo(() => , [a])  // Only re-rendered if `b` changes:  const child2 = useMemo(() => , [b])  return (    <>      {child1}      {child2}    >  )}/<code>

從例子可以看出來,它的第二個參數和 useEffect 的第二個參數是一樣的,只有在第二個參數數組的值發生變化時,才會觸發子組件的更新。

引用React hooks 實踐

使用 shouldComponentUpdate() 防止不必要的重新渲染

當一個組件的 props 或 state 變更,React 會將最新返回的元素與之前渲染的元素進行對比,以此決定是否有必要更新真實的 DOM,當它們不相同時 React 會更新該 DOM。

即使 React 只更新改變了的 DOM 節點,重新渲染仍然花費了一些時間。在大部分情況下它並不是問題,但是如果渲染的組件非常多時,就會浮現性能上的問題,我們可以通過覆蓋生命週期方法 shouldComponentUpdate 來進行提速。

shouldComponentUpdate 方法會在重新渲染前被觸發。其默認實現總是返回 true,如果組件不需要更新,可以在 shouldComponentUpdate 中返回 false 來跳過整個渲染過程。其包括該組件的 render 調用以及之後的操作。

<code>shouldComponentUpdate(nextProps, nextState) {   return nextProps.next !== this.props.next}/<code>

React 性能分析器

React 16.5 增加了對新的開發者工具 DevTools 性能分析插件的支持。此插件使用 React 實驗性的 Profiler API 來收集有關每個組件渲染的用時信息,以便識別 React 應用程序中的性能瓶頸。它將與我們即將推出的 time slicing(時間分片) 和 suspense(懸停) 功能完全兼容。

redux

Store:保存數據的地方,你可以把它看成一個容器,整個應用只能有一個 Store。

State: Store 對象包含所有數據,如果想得到某個時點的數據,就要對 Store 生成快照,這種時點的數據集合,就叫做 State。

Action: State 的變化,會導致 View 的變化。但是,用戶接觸不到 State,只能接觸到 View。所以,State 的變化必須是 View 導致的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。

ActionCreator:View 要發送多少種消息,就會有多少種 Action。如果都手寫,會很麻煩,所以我們定義一個函數來生成 Action,這個函數就叫 ActionCreator。

Reducer: Store 收到 Action 以後,必須給出一個新的 State,這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reducer。 Reducer 是一個函數,它接受 Action 和當前 State 作為參數,返回一個新的 State。

dispatch:是 View 發出 Action 的唯一方法。

redux 的基本原理

然後我們過下整個工作流程:

首先,用戶(通過 View)發出 Action,發出方式就用到了 dispatch 方法。

然後, Store 自動調用 Reducer,並且傳入兩個參數:當前 State 和收到的 Action, Reducer 會返回新的 State

State 一旦有變化, Store 就會調用監聽函數,來更新 View。

到這兒為止,一次用戶交互流程結束。可以看到,在整個流程中數據都是單向流動的,這種方式保證了流程的清晰。

redux 中間件

Redux 的中間件提供的是位於 action 被髮起之後,到達 reducer 之前的擴展點,換而言之,原本 view -> action -> reducer -> store 的數據流加上中間件後變成了 view -> action -> middleware -> reducer -> store ,在這一環節我們可以做一些 “副作用” 的操作,如 異步請求、打印日誌等。

redux 中間件通過改寫 store.dispatch 方法實現了 action -> reducer 的攔截,從上面的描述中可以更加清晰地理解 redux 中間件的洋蔥圈模型:

<code>中間件A -> 中間件B-> 中間件C-> 原始 dispatch -> 中間件C -> 中間件B ->  中間件A/<code> 

這也就提醒我們使用中間件時需要注意這個中間件是在什麼時候 “搞事情” 的,比如 redux-thunk 在執行 next(action) 前就攔截了類型為 function 的 action,而 redux-saga 就在 next(action) 才會觸發監聽 sagaEmitter.emit(action), 並不會攔截已有 action 到達 reducer。


文章來源於前端迷 ,作者前端迷


分享到:


相關文章: