React初探

react首先是相似一個組件庫的js文件,包含view和controller的庫。javascript

react組件根據平臺自己能夠映射成原生控件和web dom。css

採用babel的編譯工具將jsx轉換成js來描述對應的元素。html

 

Portal前端

Portal 提供了一種將子節點渲染到存在於父組件之外的 DOM 節點的優秀的方案。html5

一個 portal 的典型用例是當父組件有 overflow: hidden 或 z-index 樣式時,但你須要子組件可以在視覺上「跳出」其容器。例如,對話框、懸浮卡以及提示框:java

ReactDOM.createPortal(child, container)

vitrualDomreact

Babel 會把 JSX 轉譯成一個名爲 React.createElement() 函數調用。webpack

React.createElement() 會預先執行一些檢查,以幫助你編寫無錯代碼,但實際上它建立了一個這樣的對象web

const element = { type: 'h1', props: { className: 'xxxClass', children: 'Hello, world!' } };

createElement函數內部作的操做很簡單,將props和子元素進行處理後返回一個ReactElement對象算法

接下來調用:

      render: ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback)。 將目標元素插入指定節點container

這些對象被稱爲 「React 元素」。它們描述了你但願在屏幕上看到的內容。React 經過讀取這些對象,而後使用它們來構建 DOM 以及保持隨時更新。

想要將一個 React 元素渲染到根 DOM 節點中,只需把它們一塊兒傳入 ReactDOM.render()。

React DOM 會將元素和它的子元素與它們以前的狀態進行比較,並只會進行必要的更新來使 DOM 達到預期的狀態。

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

與瀏覽器的 DOM 元素不一樣,React 元素是建立開銷極小的普通對象。React DOM 會負責更新 DOM 來與 React 元素保持一致。

全部 React 組件都必須像純函數同樣保護它們的 props 不被更改。

若是是首次渲染,VitrualDom不具備任何優點,甚至它要進行更多的計算,消耗更多的內存。

VitrualDom的優點在於React的Diff算法和批處理策略,React在頁面更新以前,提早計算好了如何進行更新和渲染DOM。

isValidElement()驗證對象是否爲 React 元素,返回值爲 true 或 false

 

diff算法:

先找到新舊樹的前置元素和後置元素,並分別用前置和後置指針指向。

一、若是新舊樹前置元素相同,前置指針日後移;

二、若是碰到前置元素不相同,則找後置元素,若是後置元素相同,則後置指針前移。

三、若是前置和後置元素都不相同,則比較舊樹的前置元素和新樹的後置元素,若是相同,分別挪動舊樹的前置指針和新樹的後置指針。

四、若是舊樹的前置元素和新樹的後置元素不相同,則比較舊樹的後置元素和新樹的前置元素,若是相同,分別挪動舊樹的後置指針和新樹的前置指針。

五、若是都不相同,使用數組P記錄下舊樹中前置指針和後置指針之間的元素在新樹的下標,若是在舊樹中找不到,表示新增,下標爲-1。找出P數組中的LIS序列。

     重新樹的尾置指針遍歷新樹。若是新樹元素在新樹中的下標在LIS中存在,則不移動,若是不存在,則移動

diff算法,只對比同級元素

 

如何防止xss攻擊:

React渲染時會把沒有$$typeof標識,以及規則校驗不經過的組件過濾掉。 防止xss攻擊

無狀態組件:使用無狀態函數構建的組件成爲無狀態組件,只傳入props,context兩個參數,不存在state,沒有生命週期方法。無狀態組件在調用時不會建立新實例,避免了沒必要要的檢查和內存分配。

 

Refs:

FancyButton 使用 React.forwardRef 來獲取傳遞給它的 ref,而後轉發到它渲染的 DOM button

const FancyButton = React.forwardRef((props, ref) => (  <button ref={ref} className="FancyButton">  {props.children}  </button> )); // 你能夠直接獲取 DOM button 的 ref: const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;

 

Fragments

Fragments 容許你將子列表分組,而無需向 DOM 添加額外節點。

還有一種新的短語法可用於聲明它們,但還沒有獲得全部流行工具的支持。

 

React.PureComponent 來代替手寫 shouldComponentUpdate。但它只進行淺比較,因此當 props 或者 state 某種程度是可變的話,淺比較會有遺漏,那你就不能使用它了。

 

Memo:

React.memo 爲高階組件。它與 React.PureComponent 很是類似,但它適用於函數組件,但不適用於 class 組件。

若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在 React.memo 中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。

默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。

此方法僅做爲性能優化的方式而存在。但請不要依賴它來「阻止」渲染,由於這會產生 bug。

與 class 組件中 shouldComponentUpdate() 方法不一樣的是,若是 props 相等,areEqual 會返回 true;若是 props 不相等,則返回 false。這與 shouldComponentUpdate 方法的返回值相反。

function MyComponent(props) { /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) { /* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false */ } export default React.memo(MyComponent, areEqual);

 

Immutable

  React 作性能優化時最經常使用的就是 shouldComponentUpdate 方法,但它默 認返回 true,即始終會執行 render 方法,

而後作 Virtual DOM 比較,並得出是否須要作真實 DOM的更新,這裏每每會帶來不少不必的渲染。我們也能夠在 shouldComponentUpdate 

中使用深拷貝和深比較來避免無必要的 render, 但深拷貝和深比較通常都是很是昂貴的選擇。

  Immutable.js則提供了簡潔、高效的判斷數據是否變化的方法,只需 === 和 is 比較就能知 道是否須要執行 render,而這個操做幾乎零成本,因此能夠極大提升性能。

  for (const key in nextProps) {

    if (nextProps.hasOwnProperty(key) &&

    !is(thisProps[key], nextProps[key])) { return true;}

   }

       return false;

cloneElement

React.cloneElement: 給指定組件傳遞props,以 element 元素爲樣板克隆並返回新的 React 元素。返回元素的 props 是將新的 props 與原始元素的 props 淺層合併後的結果。新的子元素將取代現有的子元素,而來自原始元素的 key和 ref 將被保留。

React.cloneElement(
  element,
  [props],
  [...children]
)

React.children: 獲取當前組件的子組件

 

合成事件:

React本身構造了合成事件對象SyntheticEvent,SyntheticEvent 實例將被傳遞給你的事件處理函數。

這是一個跨瀏覽器原生事件包裝器。 它具備與瀏覽器原生事件相同的接口,包括stopPropagation() 和 preventDefault() 等等,在全部瀏覽器中他們工做方式都相同。

若是由於某些緣由,當你須要使用瀏覽器的底層事件時,只須要使用 nativeEvent 屬性來獲取便可。

SyntheticEvent 是合併而來。這意味着 SyntheticEvent 對象可能會被重用,並且在事件回調函數被調用後,全部的屬性都會無效。

合成事件其實是組件掛載時註冊到document上的,事件冒泡到document被監聽到以後,會生成一個合成事件對象並分發給對應的handler去處理

       事件捕獲:會優先調用結構樹最外層的元素上綁定的事件監聽器,而後依 次向內調用,一直調用到目標元素上的事件監聽器爲止。

       事件冒泡:則與事件捕獲的表現相反,它會從目標元素向外傳播事件,由內而外直到最外層。

 

非受控組件

非受控組件:一個表單組件沒有 value props(單選按鈕和複選框對應的是 checked prop) 時,就能夠稱爲非受控組件。

      它是一種反模式,它的值不受組件自身的 state 或 props 控制。一般, 須要經過爲其添加 ref prop 來訪問渲染後的底層 DOM 元素。

 

CSS Modules

classnames樣式庫:classNames({ 'btn': true, 'btn-pressed': this.state.isPressed, 'btn-over': !this.state.isPressed && this.state.isHovered,});

CSS Modules: 能最大化地結合現有 CSS 生態和 JavaScript 模塊化能力,其 API 很是簡潔。 發佈時依舊編譯出單獨的 JavaScript 和 CSS 文件。

         如今,webpack css-loader 內置 CSS Modules 功能。

啓用 CSS Modules 的代碼以下:

// webpack.config.js

css?modules&localIdentName=[name]__[local]-[hash:base64:5]

加上 modules 即爲啓用,其中 localIdentName 是設置生成樣式的命名規則。

使用了 CSS Modules 後,就至關於給每一個 class 名外加了 :local,以此來實現樣式的局部化。若是咱們想切換到全局模式,可使用 :global 包裹

對於樣式複用,CSS Modules 只提供了惟一的方式來處理——composes 組合。

/* components/Button.css */ .base { /* 全部通用的樣式 */ }

/* settings.css */.primary-color { color: #f40; }

.primary {composes: base; composes: $primary-color from './settings.css'; /* primary 其餘樣式 */}

若是不想頻繁地輸入 styles.**,可使用 react-css-modules 庫。它經過高階組件的形式來 避免重複輸入 styles.**。能夠這麼寫---styleName="root"

使用 CSS Modules,容易使用 :global 去解決特殊狀況,使用 react-css-modules 可寫成 <div className="global-css" styleName="local-module"></div>,

這種形式輕鬆對應全局和局部;

 

跨級組件通訊:

一、

在子組件定義 static contextTypes = {color: PropTypes.string} ,經過this.context.color獲取頂層組件的color屬性

在頂層組件定義 static childContextTypes = {color: PropTypes.string},  實現方法 getChildContext() {return{color: 'red'}

二、

沒有嵌套關係的,那隻能經過能夠影響全局的一些機制去考慮。import { EventEmitter } from 'events';

在 componentDidMount 事件中,若是組件掛載完成,再訂閱事件;當組件卸載的時候,在 componentWillUnmount 事件中取消事件的訂閱。

 

mixins

對於廣義的 mixin 方法,就是用賦值的方式將 mixin 對象裏的方法都掛載到原對象上,來實 現對對象的混入。

React 在使用 createClass 構建組件時提供了 mixin 屬性,mixins: ['xxx','xxx'],

在不一樣的 mixin 裏實現兩個名字同樣的普通方法,這會形成衝突。所以, 在 React 中是不容許出現重名普通方法的 mixin。

若是是 React 生命週期定義的方法,則會將各個模塊的生命週期方法疊加在一塊兒順序執行。

 

使用咱們推薦的 ES6 classes 形式構建組件時,它並不支持 mixin。

對於實現 mixin 方法來講,這就沒什麼不同了。但既然講到了語法糖,就來說講另外一個語 法糖 decorator,正巧能夠用來實現 class 上的 mixin。

core-decorators 庫爲開發者提供了一些實用的 decorator,其中實現了咱們正想要的 @mixin。 下面解讀一下其核心實現:

function handleClass(target, mixins) {

  if (!mixins.length) { throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); }

  for (let i = 0, l = mixins.length; i < l; i++) {
    // 獲取 mixins 的 attributes 對象
    const descs = getOwnPropertyDescriptors(mixins[i]);

    // 批量定義 mixins 的 attributes 對象

    for (const key in descs) {

      if (!(key in target.prototype)){

        defineProperty(target.prototype, key, descs[key]);

      }

    }

  }

}

源代碼十分簡單,它將每個 mixin 對象的方法都疊加到 target 對象的原型上 以達到 mixin 的目的。這樣,就能夠用 @mixin 來作多個重用模塊的疊加了。

這裏用了getOwnPropertyDescriptor 和 defineProperty 這兩個方法,

好處在於 defineProperty 這個方法,也就是定義與賦值的區別,定義是 對已有的定義,賦值則是覆蓋已有的定義。因此說前者並不會覆蓋已有方法,但後者會。

 

HOC:

  屬性代理是常見高階組件的實現方法

  const MyContainer = (WrappedComponent) =>

  class extends Component {---這裏將Component替換成WrappedComponent就實現了反向繼承,除了一些靜態方法,包括生命週期,state,各類function,咱們均可以獲得。

咱們同時能夠以此進行hijack(劫持),也就是控制它的render函數。在render()中調用superRender(),而後經過在外層嵌套的方式改變原有渲染

    handleClick = () => {console.log('clicked');}

    render() {

      const otherProps = {handleClick:this.handleClick}
      return <WrappedComponent {...this.props}  ref={instanceComponent => this.instanceComponent = instanceComponent}/>;

    }

  }

  export default MyContainer//--->這是一個hoc組件

 

  class MyComponent extends Component { ... }

  export default MyContainer(MyComponent);

  高階組件能夠看作是裝飾器模式(Decorator Pattern)在React的實現。即容許向一個現有的對象添加新的功能,同時又不改變其結構,屬於包裝模式(Wrapper Pattern)的一種

ES7中添加了一個decorator的屬性,使用@符表示,上面一行能夠改寫成@MyContainer

  能夠在hoc高階組件中自定義事件,並經過props傳遞下去,在hoc高階組件中使用ref,獲取當前被包含組件的引用ref

 

 

react生命週期:

主要經過 3 個階段進行管理—— MOUNTING、RECEIVE_PROPS 和 UNMOUNTING。

當首次掛載組件時,按順序執行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount。

當從新掛載組件時,此時按順序執行 getInitialState、componentWillMount、render 和componentDidMount,但並不執行 getDefaultProps。

當再次渲染組件時,組件接受到更新狀態,此時按順序執行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。

當卸載組件時,執行 componentWillUnmount。

creatClass是建立自定義組件的入口方法,負責管理生命週期中的 getDefaultProps。該方法在整個生命週期中只執行一次,這樣全部實例初始化的 props 將會被共享。

因爲 getDefaultProps 是經過構造函數進行管理的,因此也是整個生命週期中最早開始執行 的。

在 componentWillMount 中調用 setState 方法,是不會觸發 re-render的,而是會進行 state 合併,且 inst.state = this._processPendingState (inst.props, inst.context) 是在 componentWillMount 以後執行的,

所以 componentWillMount 中 的 this.state 並非最新的,在 render 中才能夠獲取更新後的 this.state。

React 是利用更新隊列 this._pendingStateQueue 以及更新狀態 this._pendingReplace State 和 this._pendingForceUpdate 來實現 setState 的異步更新機制。

 

在 componentWillReceiveProps 中調 用 setState,是不會觸發 re-render 的,而是會進行 state 合併。

且在 componentWillReceivePropsshouldComponentUpdate 和 componentWillUpdate 中也仍是沒法獲取到更新後的 this.state,

即此 時訪問的 this.state 仍然是未更新的數據,須要設置 inst.state = nextState 後才能夠,所以 只有在 render 和 componentDidUpdate 中才能獲取到更新後的 this.state。

 

在 componentWillUnmount,則執行並重置全部相關參數、更新隊列以及更新狀態,如 果此時在 componentWillUnmount 中調用 setState,

是不會觸發 re-render 的,這是由於全部更新 隊列和更新狀態都被重置爲 null,並清除了公共類,完成了組件卸載操做。

componentWillMountcomponentWillReceivePropscomponentWillUpdate

getDerivedStateFromProps 取代了。主要因爲fiber致使上面三個函數會重複調用

 

setstate

React 利用狀態隊列機制實現了 setState的異步更新,避免頻繁地重複更新 state。

1.將setState傳入的partialState參數存儲在當前組件實例的state暫存隊列中。

  當調用 setState 時,實際上會執行 enqueueSetState 方法,_pendingStateQueue 更新隊列 對partialState 進行合併操做,

  經過 enqueueUpdate 執行 state 更新。

function enqueueUpdate(component) {

  ensureInjected();

  // 若是不處於批量更新模式
  if (!batchingStrategy.isBatchingUpdates) {

            3.若是未處於批量更新狀態,將批量更新狀態標識設置爲true,用事務再次調用前一步方法,保證當前組件加入到了待更新組件隊列中。

    batchingStrategy.batchedUpdates(enqueueUpdate, component);

    return;

  }

  2.判斷當前React是否處於批量更新狀態,若是是,將當前組件加入待更新的組件隊列中。

  // 若是處於批量更新模式,則將該組件保存在 dirtyComponents 中

  dirtyComponents.push(component);

}

batchedUpdates: function(callback, a, b, c, d, e) {
  var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

  ReactDefaultBatchingStrategy.isBatchingUpdates = true;

  if (alreadyBatchingUpdates) {

    callback(a, b, c, d, e);

  } else {

    4.調用事務的waper方法,遍歷待更新組件隊列依次執行更新。

    transaction.perform(callback, null, a, b, c, d, e);----事務

  }

},

5.執行生命週期componentWillReceiveProps。

6.將組件的state暫存隊列中的state進行合併,得到最終要更新的state對象,並將隊列置爲空。

7.執行生命週期componentShouldUpdate,根據返回值判斷是否要繼續更新。

8.執行生命週期componentWillUpdate。

9.執行真正的更新,render。

10.執行生命週期componentDidUpdate。

在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 調 用 setState ,又會調用 

shouldComponentUpdate 和 componentWillUpdate 方法,所以形成循環調用,使得瀏覽器內存佔滿後崩潰。

 

setTimeout 裏面調用setState的時候,把它丟到列隊裏,並無去執行,而是先執行的 finally 主進程代碼塊,等 finally 執行完了, isBatchingUpdates 又變爲了 false ,
致使最後去執行隊列裏的 setState 時候,表現就會和原生事件同樣,能夠同步拿到最新的state的值。
 
react-router-dom:

重構history類。並做爲react.context的value傳遞給route子組件

hash:

在url中多了以#結尾的hash值,可是賦值先後雖然頁面的hash值改變致使頁面完整的url發生了改變,可是頁面是不會刷新的。

此外,還有一個名爲hashchange的事件,能夠監聽hash的變化,咱們能夠經過下面兩種方式來監聽hash的變化:

window.onhashchange=function(event){

   console.log(event);

}

History:

History.back(): 返回瀏覽器會話歷史中的上一頁,跟瀏覽器的回退按鈕功能相同

History.go(): 能夠跳轉到瀏覽器會話歷史中的指定的某一個記錄頁

History.forward():指向瀏覽器會話歷史中的下一頁,跟瀏覽器的前進按鈕相同

History.pushState():pushState能夠將給定的數據壓入到瀏覽器會話歷史棧中,該方法接收3個參數,對象,title和一串url。pushState後會改變當前頁面url,可是不會伴隨着刷新

History.replaceState():replaceState將當前的會話頁面的url替換成指定的數據,replaceState後也會改變當前頁面的url,可是也不會刷新頁面。

 BrowserRouter:

用<BrowserRouter> 組件包裹整個App系統後,就是經過html5的history來實現無刷新條件下的前端路由。

若是用history作爲路由的基礎,那麼須要用到的是history.pushState和history.replaceState,在不刷新的狀況下能夠改變url的地址,

且若是頁面發生回退back或者forward時,會觸發popstate事件。

監聽history.urlChange(),更新context中的location和history的值,以及match屬性

Link:

<Link>相似於html中的a標籤,此外<Link>在改變url的時候,能夠將一些屬性傳遞給匹配成功的Route,供相應的組件渲染的時候使用。

to屬性的值也能夠是一個對象,該對象能夠包含一下幾個屬性:pathname、seacth、hash和state,其中前3個參數與如何改變url有關,

最後一個state參數是給相應的改變url時,傳遞一個對象參數。

舉例來講: <Link to={{pathname:'/home',search:'?sort=name',hash:'#edit',state:{a:1}}}>Home</Link>

 

class Link extends React.Component {

   handleClick = event => {

  event.preventDefault();

     const { history } = this.context.router;

     const { replace, to } = this.props;

     if (replace) {

       history.replace(replace);

     } else {

      history.push(to);

     }

   }

  };

  render(){

    const { replace, to, innerRef, ...props } = this.props;

     <a {...props} onClick={this.handleClick}/>

  }

}

Route

Route組件也很簡單,其props中接受一個最主要的屬性path,Route作的事情只有一件:

當url改變的時候,根據context中history的變化,將history.location.pathname屬性與自身的path作對比,若是匹配成功,則渲染該組件的componet或者children屬性所賦值的那個組件。

 

 

跨域請求的一種解決方案:

const proxy = require('http-proxy-middleware');

module.exports = function(app) {

  app.use(proxy('/api', { target: 'http://localhost:5000/' }));

};

 

suspend和lazy懶加載 

 

 

 

react 性能優化:

網絡層的優化,gzip等

 pureComponent

 在shouldUpdate()深淺比較(immutable.js可避免淺比較帶來的問題)

 key

 React.Memo來緩存組件

 避免動態建立函數props

 suspend和lazy懶加載

 使用css隱藏和顯示組件而不是使用?:來加載和卸載組件

 避免深層次的state數據傳遞,會讓無關中間組件render,可經過context傳數據

 組件顆粒化,減小無關的render

 reselector: 接受Redux store state做爲參數緩存mapStateToProps的計算結果,避免重複計算

 immutablejs 提高操做數據結構的性能

相關文章
相關標籤/搜索