React填坑

Virtual Dom

在瀏覽器環(huán)境下,DOM操作遠比JS操作開銷大,且項目越復雜,DOM操作的開銷越大,為了提升DOM渲染的效率,需要遵循的原則是盡量減少DOM操作。于是Virtual Dom橫空出世!
Virtual Dom顧名思義,是使用JS模擬DOM結(jié)構(gòu);并把DOM變化的對比,放在JS層來做,以此來提高重繪性能(將開銷大的Dom操作轉(zhuǎn)化為高效的JS操作)

<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
</ul>

//用JS模擬DOM如下:
{
  tag: 'ul',
  attrs: {
    id: 'list'
  },
  children: [
    {
      tag: 'li',
      attrs: {className: 'item'},
      children: ['Item 1']
    },
    {
      tag: 'li',
      attrs: {className: 'item'},
      children: ['Item 1']
    }
  ]
}

snabbdom

snabbdom為virtual dom的一個實現(xiàn)庫,其核心API有2:
h():創(chuàng)建virtual dom
patch(vnode, mewnode):渲染前對比兩個虛擬節(jié)點后并完成真實渲染

diff

virtual dom中(React、Vue)的diff算法起源于Linux的diff命令,用于比對文本的不同之處,是一個古老的命令。在virtual dom操作中,找出需要重新渲染的節(jié)點的過程,就是React中的diff算法。

//簡略模擬diff:
function creatElement(vnode){
  var tag = vnode.tag
  var attrs = vnode.attrs || {}
  var children = vnode.children || {}
  if(!tag){
    return null
  }
  var elem = document.creatElement(tag)
  var attrName
  for (attrName in attrs) {
    if(attrs.hasOwnProperty(attrName)){
      elem.setAttribute(attrName, attrs[attrName])
    }
  }
  chlidren.forEach((childVnode) => {
    elem.appendChild(creatElement(childVnode))//遞歸
  })
  return elem
}
function updateChildren(Vnode, newVnode){
  var children = vnode.children || {}
  var newChildren = newVnode.children || {}
  children.forEach((childVnode, index) => {
    var newChildVnode = newChildren[index]
    if(childVnode.tag === newChildVnode.tag){
      updateChildren(childVnode, newChildVnode)
    } else {
      replace(childVnode, newChildVnode)
    }
  })
}

JSX

在React中負責VIew的編寫,其本質(zhì)上是語法糖,由于瀏覽器無法識別JSX,實際運行時由React庫中的核心函數(shù)React.creatElement將JSX翻譯成JS,再由JS渲染為HTML,類似于virtual dom經(jīng)由h()轉(zhuǎn)為真實節(jié)點一樣。
React首次渲染時,調(diào)用patch(container, vnode)
通過setState()函數(shù)再次渲染時,調(diào)用patch(newVnode, vnode)

JSX自定義組件的解析過程

  1. React.creatElement(App, null)
    首先調(diào)用React的核心函數(shù),傳入自定義組件App
  2. var app = new App()
    Appclass,進行類的實例化
  3. return app.render()
    調(diào)用類實例的render()方法,并繼續(xù)步驟1,直至沒有自定義組件為止

setState()方法

  • 異步
    setState()方法是異步的,這是因為一次操作中,可能執(zhí)行多次setState,根據(jù)瀏覽器單線程的特性和性能上的考量,無需每次執(zhí)行setState的時候都重新渲染,只需進行最后一次渲染即可。
......
{
setState({a: 1})
setState({a: 2})
setState({a: 3})//只執(zhí)行最后一次,前兩次不執(zhí)行
}
......
  • 過程
    1 每個React組件實例,均有繼承自Component類的renderComponent方法。
    2 執(zhí)行renderComponent方法會再次執(zhí)行組件的render方法。
    3 render方法返回newVnode,同時系統(tǒng)緩存有preVnode的信息。
    4 執(zhí)行patch(preVnode, newVnode)。

React生命周期相關(guān)

組件生命周期

shouldComponentUpdate

React開發(fā)時,一個很奇妙的事情就是當stateprops未發(fā)生改變時,組件依然會重新渲染,所以當追求性能的時候,shouldComponentUpdate就派上了用場。shouldComponentUpdate生命周期函數(shù)是重渲染時render()函數(shù)調(diào)用前被調(diào)用的函數(shù),它接受兩個參數(shù):nextPropsnextState,分別表示下一個props和下一個state。并且,當函數(shù)返回false時候,阻止接下來的render()的調(diào)用,阻止組件重渲染,而返回true時,組件照常重渲染。

//通過對比屬性來確定組件是否更新
shouldComponentUpdate(nextProps,nextState){
  if(nextProps.numberObject.number == this.props.numberObject.number){
    return false
  }
  return true
}

一個警告

Warning: setState(...): Can only update a mounted or mounting 
component. This usually means you called setState() on an 
unmounted component. This is a no-op. Please check the code for 
the OrderTableList component.

在組件生命周期中,設(shè)立標志位來檢驗組件是否掛載,可規(guī)避此警告

componentDidMount(){
  this.mounted = true
  //some code
}
if(this.mounted){
  this.setState({})
}
componentWillUnmount(){
  this.mounted = false
}

react數(shù)據(jù)流

在并不復雜的webapp中,其實是可以不用redux的!

  • 父組件向子組件傳遞數(shù)據(jù),類似如下的思路
<Father>
  <Son data={this.state.data}/>
</Father>
  • 子組件向父組件傳遞數(shù)據(jù)
<Father>
  <Son changeData={this.addData.bind(this)}/>
</Father>
//Father中有addData()操作數(shù)據(jù),并將此方法向下傳遞下去(到Son)
addData(){
  //do sth
}
//Son中有方法負責傳遞數(shù)據(jù),調(diào)用this.props上傳遞下來的方法處理,并將自身數(shù)據(jù)傳入其中
do(){
  this.props.changeData(mydata)
}
  • 子組件與子組件間傳遞數(shù)據(jù)
    這里說的是有公共父組件的應用場景,即將子組件一的數(shù)據(jù)上傳到父組件,經(jīng)由父組件中轉(zhuǎn)后將數(shù)據(jù)下發(fā)到子組件二
  • ref
    當父組件需要直接操作子組件時,即調(diào)用子組件方法,可以使用ref
<Father>
  <Son ref='loginAlert'/>
</Father>
~~~~~~~~~
~~~~~~~~~
this.refs.loginAlert.someFuc(this為Father組件上下文)

關(guān)于父組件更新而子組件不更新的問題

react中,當父組件通過props向子組件傳遞數(shù)據(jù)時,當父組件更新時,子組件會重新render,注意是重新render,子組件并不會完成卸載——加載過程,因此,constructor構(gòu)造函數(shù)并不會再次執(zhí)行,通過this.state語句自然不會傳入props的值。這種情況下,子組件可以定義為“無狀態(tài)”組件,直接使用this.props.data即可。

Immutable.js

var a = 'a'
var b = a
var b = 'b'
console.log(a) //b

修改引用型對象是一樣不安全的行為

var a = 'a'
var b = Object.assign({}, a)
console.log(a === b) //false

可以使用ES6方法生成指向不同內(nèi)存地址的新對象(concat(),...均可),可以使操作對象的行為變得相對安全,但是這種不斷新開辟內(nèi)存的行為會增大系統(tǒng)的開銷,此時immutable.js隆重登場 ,其核心思想為,對象一經(jīng)創(chuàng)建,其不會被改變;無論對其進行什么操作,都返回一個新對象;老對象始終不變,而新對象通過樹形結(jié)構(gòu)共享原則,既保留原來對象不變,又保留變化的這個部分,而因為每次變化僅為一部分,避免了深拷貝帶來的性能損耗,同時可以進行任意時刻的數(shù)據(jù)回溯。

Redux

Redux數(shù)據(jù)流
Redux數(shù)據(jù)流
  • storeRedux的核心,其集成了reducers和一些中間件,Action發(fā)出動作后,集成了reducersstore隨即發(fā)生狀態(tài)變化,然后數(shù)據(jù)便會從根容器(Provider)向下傳遞;由于數(shù)據(jù)在全局有且只有一份(Store),因此在React應用中頻繁修改state容易引起數(shù)據(jù)混亂,因此在Redux數(shù)據(jù)流中,需要使用Immutable Data。
  • Redux數(shù)據(jù)流中,一個Action有一個或者多個Reducer與之對應,多個ReducercombineReducer包裹并傳入Store
  • 容器組件和視圖組件


    容器和視圖組件
中間件
const logger = (store) => (next) => (action) => {
  //do something
} //科里化函數(shù)
構(gòu)建store
import {creatStore, applyMiddleware} from 'redux'
import {fromJS} from 'immutable'
import thunk from 'redux-thunk'
import creatLogger from 'redux-logger'
import promiseMiddleware from 'redux-promise'

import reducers from './reducers'
const middlewares = [thunk , promiseMiddleware, creatLogger()]
function configerStore( initialState = fromJS({}){
  const store = creatStore(reducers(), initialState,  applyMiddleware(...middlewares))
  if(module.hot){
    module.hot.accept( () => {
      store.replaceReducer(require('./reducers').default) //熱重載更新reducers
    })
  }
  return store
}
export default configerStore
connect

用于建立store和actions的連接,使reducers可以接收到發(fā)出的actions,從而改變store中的數(shù)據(jù),即將action和reducer都綁定在一個實例上。connect()接受4個參數(shù):

mapStateToProps #把store中的數(shù)據(jù)作為屬性綁定到組件上,從而可以...this.props獲取
mapDispatchToProps #方法的綁定,從而可以this.props獲取action調(diào)用
mergeProps
options

Mobx

Mobx數(shù)據(jù)流相較Redux而言,概念更少。Mobx基于一份數(shù)據(jù)進行修改(Redux數(shù)據(jù)基于淺拷貝,每次更新都生成新的數(shù)據(jù)對象,但是由于淺拷貝和虛擬DOM的存在,對于整體性能的影響有限),因其對store數(shù)據(jù)的修改可以使用JS修改普通對象的方式,過程比較靈活(不嚴謹)。

const mobxStore = observable(
  count: 0,
  add: action(function(num){
    this.count += num
  })
)
mobxStore.add(1)
  • Mobx應用中用Class組織
  • Mobx重要概念
@observable
@action
@computed
autorun()//數(shù)據(jù)store更新時調(diào)用
  • 通過@inject('storeName')向組件注入數(shù)據(jù)
  • 通過@observer監(jiān)聽組件

React16新特性

  • rollup打包,體積小
  • 使用Fiber重寫
  • 一些實用的功能:
  1. error boundary,捕獲React渲染過程中出現(xiàn)的錯誤,通過新的鉤子函數(shù):componentIDidCatch
  2. New render return types,可以在Render方法中返回Array和String,也可以使用Fragment代替沒有實際意義的div
  3. Portals,用于創(chuàng)建浮層或者Popup組件
  4. Better sever-side rendering

React中使用PropTypes進行類型檢查

從React15.5起,React內(nèi)置的類型檢測庫開始作為獨立的存在,開發(fā)者可以通過‘prop-types’類型檢測庫捕獲更多的bug,PropTypes 輸出了一系列的驗證器,可以用來確保接收到的參數(shù)是有效的。當給 props 傳遞了一個不正確的值時,JavaScript控制臺將會顯示一條警告。

import PropTypes from 'prop-types'

class A extends React.Component {
   render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

A.PropTypes = {
  name: PropTypes.string
}

同一組件無法重新渲染的解決方案

當使用路由切換時,不同的路由下對應的同一個組件 ,這時候React并不會重新渲染組件,此時只是傳入的props有了一定的變化,這時需要利用React的鉤子方法:componentWillReceiveProps,通過比對props,來完成新數(shù)據(jù)的獲取,進而重新渲染組件。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容