React學習之漫談React

  • 事件系統

  1. 合成事件的綁定方式
`<button onClick={this.handleClick}>Test</button>`
  1. 合成事件的實現機制:事件委派和自動綁定。
  2. React合成事件系統的委託機制,在合成事件內部僅僅是對最外層的容器進行了綁定,而且依賴事件的冒泡機制完成了委派。
  • 表單

  1. React受控組件更新state的流程:
  1. 能夠經過在初始state中設置表單的默認值。
  2. 每當表單的值發生變化時,調用onChange事件處理器。
  3. 事件處理器經過合成事件對象e拿到改變後的狀態,並更新應用的state。
  4. setState觸發視圖的從新渲染,完成表單組件值的更新。
  1. 受控組件和非受控組件的最大區別是:非受控組件的狀態並不會受應用狀態的控制,應用中也多了局部組件狀態,而受控組件的值來自於組件的state。
  • 樣式處理

  1. CSS模塊化遇到了哪些問題?全局污染,命名混亂,依賴管理不完全,沒法共享變量,代碼壓縮不完全。
  2. CSS Modules模塊化方案:啓用CSS Modules,樣式默認局部,使用composes來組合樣式。
  • 組件間通訊

  1. 子組件向父組件通訊
  1. 利用回調函數
  2. 利用自定義事件機制
  1. 當須要讓子組件跨級訪問信息時,咱們還可使用context來實現跨級父子組件間的通訊。
  2. 沒有嵌套關係的組件通訊:咱們在處理事件的過程當中須要注意,在componentDidMount事件中,若是組件掛載完成,再訂閱事件;當組件卸載的時候,在componentWillUnmount事件中取消事件的訂閱。
  • 組件間抽象

  1. mixin 的目的,就是爲了創造一種相似多重繼承的效果,或者說,組合。實際上,包括C++等一些年齡較大的OOP語言,都有一個強大可是危險的多重繼承特性。現代語言權衡利弊,大都捨棄了它,只採用單繼承。可是單繼承在實現抽象的時候有不少不便,爲了彌補缺失,Java引入接口(interface),其餘一些語言則引入了mixin的技巧。

封裝mixin方法
方法:css

const mixin = function(obj, mixins) {
    const newObj = obj;
    newObj.prototype = Object.create(obj.prototype);
    for (let prop in mixins) {
        if (mixins.hasOwnProperty(prop)) {
            newObj.prototype[prop] = mixins[prop]; 
        }
    }
    return newObj; 
}

應用:react

const BigMixin = { 
    fly: () => {
        console.log('I can fly'); 
    }
};
const Big = function() { 
    console.log('new big');
};
const FlyBig = mixin(Big, BigMixin);
const flyBig = new FlyBig(); // => 'new big'
flyBig.fly(); // => 'I can fly'

上面這段代碼實現對象混入的方法是:用賦值的方式將mixin對象裏的方法都掛載到原對象上。數組

  1. 在React中使用mixin

React在使用createClass構建組件時提供了mixin屬性,好比官方封裝的:PureRenderMixin。瀏覽器

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
React.createClass({
    mixins: [PureRenderMixin],
    render() {
        return <div>foo</div>;
    }
});

在createClass對象參數中傳入數組mixins,裏面封裝了咱們須要的模塊。mixins數組也能夠添加多個mixin。同時,在React中不容許出現重名普通方法的mixin。而若是是生命週期方法,則React將會將各個模塊的生命週期方法疊加在一塊兒而後順序執行。
使用createClass實現的mixin爲組件作了兩件事:安全

  1. 工具方法:這是mixin的基本功能,若是但願共享一些工具類的方法,就能夠直接定義它們而後在組件中使用。
  2. 生命週期繼承,props和state合併。mixin可以合併生命週期方法。若是有不少mixin來定義componentDidMount這個週期,那麼React會很機智的將它們都合併起來執行。一樣,mixin也能夠做state和props的合併。
  1. ES6 Classes和decorator

然而,當咱們使用ES6 classes的形式構建組件的時候,卻並不支持mixin。爲了使用這個強大的功能,咱們還須要採起其餘方法,來達到模塊重用的目的。可使用ES7的語法糖decorator來實現class上的mixin。core-decorators庫爲開發者提供了一些實用的decorator, 其中也正好實現了咱們想要的@mixin。性能優化

import React, { Component } from 'React'; 
import { mixin } from 'core-decorators';
const PureRender = { 
    shouldComponentUpdate() {}
};
const Theme = { 
    setTheme() {}
};
@mixin(PureRender, Theme)
class MyComponent extends Component {
    render() {} 
}

mixin的問題數據結構

  1. 破壞了原有組件的封裝:mixin會混入方法,給原有的組件帶來新特性。但同時它也可能帶來新的state和props,這意味着組件有一些「不可見」的狀態須要咱們去維護。另外,mixin也有可能去依賴其餘的mixin,這樣會創建一個mixin的依賴鏈,當咱們改動一個mixin的狀態,頗有可能也會影響其餘的mixin。
  2. 命名衝突
  3. 增長複雜性

針對這些困擾,React提出的新的方式來取代mixin,那就是高階組件。app

  1. 高階組件

若是已經理解高階函數,那麼理解高階組件也很容易的。高階函數:就是一種這樣的函數,它接受函數做爲參數輸入,或者將一個函數做爲返回值。例如咱們常見的方法map, reduce, sort等都是高階函數。高階組件和和高階函數很相似,高階組件就是接受一個React組件做爲參數輸入,輸出一個新的React組件。高階組件讓咱們的代碼更具備複用性、邏輯性與抽象性,它能夠對render方法做劫持,也能夠控制props和state。
實現高階組件的方法有以下兩種:框架

  1. 屬性代理:高階組件經過被包裹的React組件來操做props。
  2. 反向繼承:高階組件繼承於被包裹的React組件。

屬性代理
示例代碼:less

import React, { Component } from 'React';
const MyContainer = (WrappedComponent) => 
    class extends Component {
        render() {
            return <WrappedComponent {...this.props} />;
        } 
    }

在代碼中咱們能夠看到,render方法返回了傳入的WrappedComponent組件。這樣,咱們就能夠經過高階組件來傳遞props。這種方式就是屬性代理。
如何使用上面這個高階組件:

import React, { Component } from 'React';
class MyComponent extends Component { 
    // ...
}
export default MyContainer(MyComponent);

這樣組件就能夠一層層的做爲參數被調用,原始組件久具有了高階組件對它的修飾。這樣,保持單個組件封裝的同時也保留了易用行。
從功能上, 高階組件同樣能夠作到像mixin對組件的控制:

  1. 控制props

咱們能夠讀取、增長、編輯或是移除從WrappedComponent傳進來的props。
例如:新增props

import React, { Component } from 'React';
const MyContainer = (WrappedComponent) => 
    class extends Component {
        render() {
            const newProps = {  text: newText, };
            return <WrappedComponent {...this.props} {...newProps} />; 
        }
    }

注意:

<WrappedComponent {...this.props}/>
// is equivalent to
React.createElement(WrappedComponent, this.props, null)

這樣,當調用高階組件的時候,就可使用text這個新的props了。

  1. 經過refs使用引用
  2. 抽象state

高階組件能夠講原組件抽象爲展現型組件,分離內部狀態。

const MyContainer = (WrappedComponent) => 
    class extends Component {
        constructor(props) { 
            super(props); 
            this.state = { name: '', 4 };
            this.onNameChange = this.onNameChange.bind(this); 
        }

        onNameChange(event) { 
            this.setState({
                name: event.target.value, 
            })
        }
        render() {
            const newProps = {
                name: {
                    value: this.state.name, 
                    onChange: this.onNameChange,
                }, 
            }
            return <WrappedComponent {...this.props} {...newProps} />; 
        }
    }

在這個例子中,咱們把組件中對name prop 的onChange方法提取到高階組件中,這樣就有效的抽象了一樣的state操做。
使用方式

@MyContainer
class MyComponent extends Component {
    render() {
        return <input name="name" {...this.props.name} />;
    } 
}

反向繼承

const MyContainer = (WrappedComponent) => 
   class extends WrappedComponent {
        render() {
            return super.render();
        } 
    }
  • 組件性能優化

性能優化的思路

影響網頁性能最大的因素是瀏覽器的重排(repaint)和重繪(reflow)。React的Virtual DOM就是儘量地減小瀏覽器的重排和重繪。從React渲染過程來看,如何防止沒必要要的渲染是解決問題的關鍵。

性能優化的具體辦法

  1. 儘可能多使用無狀態函數構建組件

無狀態組件只有props和context兩個參數。它不存在state,沒有生命週期方法,組件自己即有狀態組件構建方法中的render方法。在合適的狀況下,都應該必須使用無狀態組件。無狀態組件不會像React.createClass和ES6 class會在調用時建立新實例,它建立時始終保持了一個實例,避免了沒必要要的檢查和內存分配,作到了內部優化。

  1. 拆分組件爲子組件,對組件作更細粒度的控制
相關重要概念:純函數

純函數的三大構成原則:

  • 給定相同的輸入,它老是返回相同的輸出: 好比反例有 Math.random(), New Date();
  • 過程沒有反作用:即不能改變外部狀態;
  • 沒有額外的狀態依賴:即方法內部的狀態都只能在方法的生命週期內存活,這意味着不能在方法內使用共享的變量。

純函數很是方便進行方法級別的測試及重構,它可讓程序具備良好的擴展性及適應性。純函數是函數式變成的基礎。React組件自己就是純函數,即傳入指定props獲得必定的Virtual DOM,整個過程都是可預測的。

具體辦法

拆分組件爲子組件,對組件作更細粒度的控制。保持純淨狀態,可讓方法或組件更加專一(focus),體積更小(small),更獨立(independent),更具備複用性(reusability)和可測試性(testability)。

  1. 運用PureRender,對變動作出最少的渲染
相關重要概念: PureRender

PureRender的Pure便是指知足純函數的條件,即組件被相同的props和state渲染會獲得相同的結果。在React中實現PureRender須要從新實現shouldComponentUpdate生命週期方法。shouldComponentUpdate是一個特別的方法,它接收須要更新的props和state,其本質是用來進行正確的組件渲染。當其返回false的時候,再也不向下執行生命週期方法;當其返回true時,繼續向下執行。組件在初始化過程當中會渲染一個樹狀結構,當父節點props改變的時候,在理想狀況下只需渲染一條鏈路上有關props改變的節點便可;可是,在默認狀況下shouldComponentUpdate方法返回true,React會從新渲染全部的節點。
有一些官方插件實現了對shouldComponentUpdate的重寫,而後本身也能夠作一些代碼的優化來運用PureRender。

具體辦法
  1. 運用PureRender

使用官方插件react-addons-pure-render-mixin實現對shouldComponentUpdate的重寫

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }
  render() {
    return <div className={this.props.className}>foo</div>
  }
}

它的原理是對object(包括props和state)作淺比較,即引用比較,非值比較。好比只用關注props中每個是否全等(若是是prop是一個對象那就是隻比較了地址,地址同樣就算是同樣了),而不用深刻比較。

  1. 優化PureRender

避免不管如何都會觸發shouldComponentUpdate返回true的代碼寫法。避免直接爲prop設置字面量的數組和對象,就算每次傳入的數組或對象的值沒有變,但它們的地址也發生了變化。
如如下寫法每次渲染時style都是新對象都會觸發shouldComponentUpdate爲true:

<Account style={{color: 'black'}} />

改進辦法:將字面量設置爲一個引用:

const defaultStyle = {};
<Account style={this.props.style || defaultStyle} />

避免每次都綁定事件,若是這樣綁定事件的話每次都要生成一個新的onChange屬性的值:

render() {
  return <input onChange={this.handleChange.bind(this)} />
}

該儘可能在構造函數內進行綁定,若是綁定須要傳參那麼應該考慮抽象子組件或改變現有數據結構:

constructor(props) {
  super(props);
  this.handleChange = this.handleChange.bind(this);
}
handleChange() {
  ...
}
render() {
  return <input onChange={this.handleChange} />
}

在設置子組件的時候要在父組件級別重寫shouldComponentUpdate。

  1. 運用immutable

JavaScript中對象通常是可變的,由於使用引用賦值,新的對象的改變將影響原始對象。爲了解決這個問題是使用深拷貝或者淺拷貝,但這樣作又形成了CPU和內存的浪費。Immutable data很好地解決了這個問題。Immutable data就是一旦建立,就不能再更改的數據。對Immutable對象進行修改、添加或刪除操做,都會返回一個新的Immutable對象。Immutable實現的原理是持久化的數據結構。即便用舊數據建立新數據時,保證新舊數據同時可用且不變。同時爲了不深拷貝帶來的性能損耗,Immutable使用告終構共享(structural sharing),即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其餘節點則進行共享。

  • 自動化測試

jest 是 facebook 開源的,用來進行單元測試的框架,功能比較全面,測試、斷言、覆蓋率它均可以,另外還提供了快照功能。 對測試羣衆來講,從質量保證的角度出發,單元測試覆蓋率100%是否就足夠了呢?確定不夠啊! 結合實際的項目經驗來看,jest的測試還能夠根據產品的實際需求,作一些諸如: 點擊某個頁面元素後,須要在頁面上顯示新的區塊,而且要加載指定的的css的測試; 點擊某個link,須要跳轉到指定的網站的測試; 等等 這些測試本來在UI自動化功能測試中也比較常見,這裏咱們均可以把它們挪到低層中去。因此具體的測試用例,在單元測試覆蓋率超級高的前提下,咱們測試的羣衆還能夠跟研發結對完成。或者指導研發完成,要不乾脆本身加上去算了。另外,產品的功能性測試完成的狀況下,咱們還須要考慮下非功能性的問題,例如兼容性、性能、安全性等。再加上測試金字塔的頂端之上,其實還有探索性測試的位置。產品的基本功能由單元測試保障了,剩下的時間,咱們能夠作更多的探索性測試了不是嗎?總之,幹掉UI自動化功能測試只是一個加速測試反饋週期、減小投入成本的嘗試。軟件的質量不只僅是測試攻城獅的事情,而是整個團隊的責任。堅持一些重要的編碼實踐,好比state less的組件、build security in等,也是提升質量的重要手段。
相關文章
相關標籤/搜索