React思想

函數(shù)式編程,對應(yīng)的是聲明式編程,聲明式編程的本質(zhì)的lambda驗算(是一個匿名函數(shù),即沒有函數(shù)名的函數(shù)。Lambda表達式可以表示閉包)

React元素

React元素使用JSON格式的代碼:

const DeleteAccount = () => ({
    type: 'div',
    props: {
        children: [
            {
                type: 'p',
                props: {
                    children: 'Are you sure?'
                }
            },
            {
                type: DangerButton,
                props: {
                    children: 'Confirm'
                }
            },
            {
                type:Button,
                props: {
                    children: 'Cancel'
                }
            }
        ]
    }
})

React.createElement表示的代碼:

var DeleteAccount = function() {
    return React.createElment(
        'div',
        null,
        React.createElment(
            'p',
            null,
            'Are you sure?'
        ),
        React.createElment(
            DangerButton,
            null,
            'Confirm',
        ),
        React.createElment(
            Button,
            { color: 'blue' },
            'Cancel'
        )
    )
}

組件

組件封裝的時候,組件的幾項規(guī)范標準:

  • 基本的封裝性
  • 簡單的聲明周期呈現(xiàn)
  • 明確的數(shù)據(jù)流動

Web Component 的4個組成部分:

  • HTML Templates定義了模板的概念
  • Custom Elements定義了組件的展現(xiàn)形式
  • Shadow DOM定義了組件的作用域范圍,可以包括樣式
  • HTML Imports提出了新的引入方式


    Web Components組成

React組件的構(gòu)建:
Web Component通過自定義元素的方式實現(xiàn)組件化,而React的本質(zhì)就是關(guān)心元素的構(gòu)成,React組件即為組件元素。組件元素被描述成純粹的JSON對象。
React組件基本上由3個部分組成 -- 屬性(props),狀態(tài)(state)以及生命周期


React組件的組成

React所有組件繼承自頂層類 React.Component。它的定義非常簡潔,只是初始化了 React.Component方法,聲明了props、context、refs等,并在原型上定義了setState和forceUpdate方法。內(nèi)部初始化的聲明周期方法與createClass方法使用的是同一個方法創(chuàng)建的。這類組件會創(chuàng)建實例對象。

無狀態(tài)組件:使用無狀態(tài)函數(shù)構(gòu)建的組件,只傳入props和context兩個參數(shù),不存在state,沒有生命周期方法。它創(chuàng)建時始終保持了一個實例,避免了不必要的檢查和內(nèi)存分配,做到了內(nèi)部優(yōu)化

state

注意:setState是一個異步方法,一個生命周期內(nèi)所有的setState方法會合并操作。

智能組件(smart Component):內(nèi)部(State)更新數(shù)據(jù),控制組件渲染
木偶組件(dumb COmponent):外部(props)更新數(shù)據(jù),控制組件渲染

props

props是properties的縮寫。props是React用來組件之間互相聯(lián)系的一種機制,通俗的說就像方法的參數(shù)一樣。

  • 子組件prop
    在React中有一個重要且內(nèi)置的prop--children,它代表組件的子組件集合。

  • 組件props
    在組件的prop中某一個屬性,我們傳入節(jié)點,然后渲染這個節(jié)點,和 children 類似

  • 用function prop與父組件通信

  • propTypes
    用于規(guī)范props的類型與必須的狀態(tài)。

生命周期

分為兩類:

  • 當組件在掛載或卸載時
  • 當組件接收新的數(shù)據(jù)時,即組件更新時

組件的掛載,這個過程主要做組件狀態(tài)初始化

// 推薦使用這個例子作為模板寫初始化組件
 class App extends Component { 
    // props類型檢查
    static propTypes = {
      // ... 
    };
    // 默認類型
    static defaultProps = {
     // ...
    };
    constructor(props) { super(props);
        this.state = {
           // ...
        }; 
    }
    componentWillMount() { 
        // ...
    }

    componentDidMount() { 
        // ...
    }
    render() {
      return <div>This is a demo.</div>;
    } 
}

propTypes和defaultProps聲明成靜態(tài)屬性,意味著從類外面也可以訪問他們,比如:App.propTypes和App.defaultProps

組件的卸載

componentWillUnmount
這個方法中,我們常常會執(zhí)行一些清理方法,如事件回收或是清除定時器

數(shù)據(jù)更新過程

更新過程指父組件向下傳遞props或組件自身執(zhí)行setState方法時發(fā)生的一系列更新動作。

import React, { Component, PropTypes } from 'react';
class App extends Component {
  componentWillReceiveProps(nextProps) {
    // this.setState({})
  }
shouldComponentUpdate(nextProps, nextState) {
  // return true;
}
  componentWillUpdate(nextProps, nextState) {
    // ...
  }
  componentDidUpdate(prevProps, prevState) {
    // ...
  }
  render() {
    return <div>This is a demo.</div>;
  }
}

如果state更新了,那么會依次執(zhí)行 shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate

外來組件prop傳遞進來使組件更新

componentWillReceiveProps(nextProps) {
  if ( 'activeIndex' in nextProps ) {
    this.setState({
      activeIndex: nextProps.activeIndex
    })
  }
}

整體流程

React聲明周期整體流程圖
使用ES6 classes與createClass構(gòu)建組件方法的異同

React與DOM

ReactDOM

ReactDOM中的API很少,只有findDOMNode、unmountComponentAtNode和render

  1. findDOMNode
    DOM真正被添加到HTML中的生命周期方法是componentDidMount和componentDidUpdate方法。ReactDOM提供findDOMNode:DOMElement findDOMNode(ReactComponent component),假設(shè)當前組件加載完時獲取當前DOM,則可以使用findDOMNode
import React, { Component } from 'react'; 
import ReactDOM from 'react-dom';
class App extends Component { 
  componentDidMount() {
     // this 為當前組件的實例
      const dom = ReactDOM.findDOMNode(this); 
  }
  render() {} 
}
  1. render
ReactComponent render( 
  ReactElement element, 
  DOMElement container, 
  [function callback]
)

該方法把元素掛在到container中,并返回element的實例(即refs引用)。如果是無狀態(tài)組件,render會返回null。組件掛載完畢時,callback就會被調(diào)用。
與 render 相反,React 還提供了一個很少使用的 unmountComponentAtNode 方法來進行
卸載操作

refs

ref -> reference
ref返回一個實例對象,也可以是一個回調(diào)函數(shù),focus的巧妙實現(xiàn),就是使用回調(diào)函數(shù)

import React, { Component } from 'react';
class App extends Component { 
    constructor(props){
        super(props);
        this.handleClick = this.handleClick.bind(this); 
    }

  handleClick() {
    if (this.myTextInput !== null) {
        this.myTextInput.focus(); }
    }
    render() { 
        return (
            <div>
                <input type="text" ref={(ref) => this.myTextInput = ref} /> <input
                type="button"
                value="Focus the text input" onClick={this.handleClick}
            /> </div>
       ); 
    

React之外的DOM操作

DOM操作可以歸納為對DOM的增、刪、改、查?!安椤敝傅氖菍OM屬性、樣式的查看,比如DOM的位置、寬、高等信息。如果要調(diào)用HTML5 Audio/Video的play方法和input的focus方法,這時智能使用相應(yīng)的DOM方法來實現(xiàn)。
比如Popup等組件

componentDidUpdate(prevProps, prevState) {
  if ( !this.state.isActive  && prevState.isActive ) {
     document.removeEventListener('click', this.hidePopup);
  }

  if( this.state.isActive && !prevState.isActive ) {
    document.addEventListener('click', this.hidePopup)
  }
}

componentWillUnmount() {
  document.removeEventListener('click', this.hidePopup)
}

if( !this.isMounted() ) {
  return false;
}

const node = ReactDOM.findDOMnode(this);
const target = e.target || e.srcElement;
const isInside = node.contains(target);

if ( this.state.isActive && !isInside ) {
  this.setState({
    isActive: false
  })
}

計算DOM的尺寸(即位置信息),提供width和height這樣的工具函數(shù)

function width(el) {
  const styles = el.ownerDocument.defaultView.getComputedStyle(el, null);
  const width = parseFloat(styles.width.indexOf('px') !== -1 ? styles.width : 0);
  const boxSizing = styles.boxSizing || 'content-box';
  if (boxSizing === 'border-box') {
    return width;
  }
  const borderLeftWidth = parseFloat(styles.borderLeftWidth);
  const borderRightWidth = parseFloat(styles.borderRightWidth);
  const paddingLeft = parseFloat(styles.paddingLeft);
  const paddingRight = parseFloat(styles.paddingRight);
  return width - borderRightWidth - borderLeftWidth - paddingLeft - paddingRight;
}

漫談React

事件系統(tǒng)

Virtual DOM在內(nèi)存中是以對象的形式存在的。React基于Virtual DOM實現(xiàn)了一個SyntheticEvent(合成事件)層,我們所定義的事件處理器會接收到一個SyntheticEvent對象的實例,它完全符合W3C標準。
所有事件自動綁定到最外層上,如需訪問原生事件對象,可以使員工nativeEvent屬性

合成事件實現(xiàn)機制

主要對合成事件做了兩件事:事件委派和自動綁定。

  1. 事件委派
    事件代理機制。它并不會把事件處理函數(shù)直接綁定到真實的節(jié)點上,而是把所有事件綁定到結(jié)構(gòu)的最外層,使用一個統(tǒng)一的事件監(jiān)聽器,這個事件監(jiān)聽器上維持了一個映射來保存所有組件內(nèi)部的事件監(jiān)聽和處理函數(shù)。當組件掛載或卸載時,只是在這個統(tǒng)一的事件監(jiān)聽器上插入或刪除一些對象;;當事件發(fā)生時,首先被這個統(tǒng)一的事件監(jiān)聽器處理,然后在映射里找到真正的事件處理函數(shù)并調(diào)用。
  2. 自動綁定
    每個方法的上下文都會指向該組件的實例,即自動綁定this為當前組件
    只綁定,不傳參,可以使用一種便捷的方案--雙冒號語法,babel已經(jīng)支持
<button onClick={this.handleClick.bind(this)}></button>

<button onClick={::this.handleClick}></button>

// 箭頭函數(shù)。自動綁定定義此函數(shù)作用域的this,因此我們需要對它使用bind方法
const handleClick = (e) => {
  console.log(e)
}
<button onClick={this.handleClick}></button>
// 或者
<button onClick={() => this.handleClick()}></button>

React中使用原生事件

componentDidMount會在組件已經(jīng)完成安裝并且在瀏覽器中存在真實的DOM后調(diào)用,因此我們就可以完成原生事件的綁定。
值得注意的是,在 React 中使用 DOM 原生事件時,一定要在組件卸載時手動移除,否則很可能出現(xiàn)內(nèi)存泄漏的問題。在原生事件中阻止冒泡行為,可以阻止React合成事件的傳播,阻止React事件冒泡的行為,不能阻止原生事件的冒泡。
對于無法使用React合成事件的場景,我們還需要使用原生事件來完成。

對比React合成事件與JavaScript原生事件

  • 事件傳播與阻止事件傳播
    DOM事件傳播三個階段:事件捕獲階段、目標對象本身的事件處理程序調(diào)用以及事件冒泡。
    React只有事件冒泡
  • 事件類型
  • 事件綁定方式
    原生事件綁定方式
    1、直接在DOM元素綁定
    <button onclick='alert(1)'></button>
    2、JS中,通過為元素的事件屬性賦值的方式實現(xiàn)綁定
    el.onClick = e => { }
    3、通過事件監(jiān)聽函數(shù)實現(xiàn)綁定(addEventListener)

React只有一種
<button onClick={this.handleClick.bind(this)}></button>

4、事件對象
React不存在兼容性問題,DOM事件對象在W3C和IE標準中存在差異。

表單

受控組件

每當表單的狀態(tài)發(fā)生改變時,都會被寫入到組件的state中,這種組件在React中被稱為受控組件(controlled Component),其上綁定了一個change事件,表單的數(shù)據(jù)元素組件的state,并通過props傳入。
Reaact受控組件更新state的流程:
(1)可以通過在初始state中設(shè)置表單的默認值
(2)每當表單的值繁盛變化時,調(diào)用onChange事件處理器
(3)事件處理器通過合成事件對象e拿到改變后的狀態(tài),并更新應(yīng)用的state
(4)setState觸發(fā)視圖的重新渲染,完成表單組件值的更新

非受控組件

如果一個表單組件沒有value props(單選按鈕和復(fù)選框?qū)?yīng)的是checked prop)時,就可以稱為非受控組件。

受控組件和非受控組件的最大區(qū)別是:非受控組件的狀態(tài)并不會受應(yīng)用狀態(tài)的控制,應(yīng)用中也多了局部組件狀態(tài),而受控組件的值來自于組件的 state。

表單組件的幾個重要屬性

  1. 狀態(tài)屬性
  • value:類型為text的input,textarea組件以及select組件
  • checked:類型為radio,checkbox的組件借助boolean類型的selected
  • selected:該屬性可作用于select組件下面的option上

樣式處理

基本樣式處理

  • 自定義組件建議支持className prop,讓用戶使用時添加自定義樣式
  • 設(shè)置行內(nèi)樣式時需要使用對象

使用 classnames庫來才做類。

CSS Modules

Vjeux拋出了React開發(fā)中遇到的一系列CSS相關(guān)問題,有以下幾點:

  • 全局污染
  • 命名混亂
  • 依賴管理不徹底
  • 無法共享變量
  • 代碼壓縮不徹底

啟用CSS Modules

// webpack.config.js  的css-loader
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
  • 使用composes來組合樣式,進行樣式的復(fù)用
.base {  /*所有通用樣式*/ }

.normal {
  compose: base;
  /* normal 其他樣式 */
}
  • class命名技巧
    BEM命名:
    1、Block:對應(yīng)模塊名,如Dialog
    2、Element:對應(yīng)模塊中的節(jié)點名Confirm Button
    3、Modifier:對應(yīng)節(jié)點相關(guān)的狀態(tài),如disableed

組件中通信

  • 父組件向子組件通信
    通過props向子組件傳遞需要的信息

  • 子組件向父組件通信
    1、利用回調(diào)函數(shù)
    2、利用自定義事件機制

跨級組件通信

使用context
父組件中定義 ChildContext

// 父組件
static childContextTypes = {
  color: PropTypes.string
}

getChildContext() {
  return {
    color: 'red'
  }
}

// 子組件
static contextTypes = {
  color: PropTypes.string
}

<p style={ { background: this.context.color } }></p>

組件間抽象

廣義中的mixin方法,就是用賦值的方式將mixin對象里的方法都掛載到原對象上,來實現(xiàn)對對象的混入。

ES6 Classes與decorator
ES并沒有改變JavaScript面向?qū)ο蠓椒ɑ谠偷谋举|(zhì)。decorator是運用在運行時的方法。

高階組件

higher-order function(高階函數(shù))在函數(shù)式 編程中是一個基本的概念,它描述的是這樣一種函數(shù):這種函數(shù)接受函數(shù)作為輸入,或是輸出一 個函數(shù)。

高階組件(higher-order component),類似于高階函數(shù),它接受React 組件作為輸入,輸出一 個新的 React 組件。

實現(xiàn)高階組件的方法有如下兩種:

  • 屬性代理(props proxy)。高階組件通過被包裹的React組件來操作props
  • 反向繼承(inheritance inversion)。高階組件繼承于被包裹的React組件。
  1. 屬性代理
import React, { Componet } from 'react';

const MyContainer = (WrappedComponent) => {
  class extends Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

// 使用高階組件
class MyComponent extends Compnent {
  // ...
}

export default MyContainer(MyComponent);

// 使用decorator
@MyContainer
class MyComponent extends Component {
  render() {}
}

export default MyComponent;

上述執(zhí)行聲明周期的過程類似堆棧調(diào)用:
didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount

高階組件的功能:

  • 控制props
  • 通過refs使用引用
import React, { Component } from 'react';

const MyContainer = (WrappedComponent) => 
  class extends Component {
    // 得到實例對象
    proc(wrappedComponentInstance) {
    // 執(zhí)行實例方法
      wrappedComponentInstance.method()
    }

    render() {
      const props = Object.assign({}, this.props, {
        ref: this.proc.bind(this)
      })
      return <WrappedCompoent {...props} />
    }
  }
  • 抽象State
  • 使用其他元素包裹WrappedComponent
  1. 反向繼承
const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

他的HOC調(diào)用順序和隊列是一樣的:
didmount -> HOC didmount -> (HOCs didmount) -> will unmount -> HOC will didmount -> (HOCs will unmount)
它有兩個比較大的特點:

  • 渲染劫持
    渲染劫持指的就是高階組件可以控制 WrappedComponent 的渲染過程,并渲染各種各樣的結(jié)果。
// 渲染劫持的示例
const MyContainer = (WrappedComponent) => 
  class extends WrappedComponet {
    render() {  
      if ( this.props.loggedIn ) {
        return super.render()
      } else {
        return null;
      }
    }
  }
  • 控制state
const MyContainer = (WrappedComponent) => 
  class extends WrappedComponent {
    render() { 
      return (
        <div>
          <h2>HOC Debugger Component</h2>
          <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre><p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre> 
          {super.render()}
        </div> 
      );
  } 
}
  1. 組件命名
    react-redux庫中已經(jīng)實現(xiàn)了HOC.displayName =HOC(${getDisplayName(WrappedComponent)})``

可以使用recompose庫

  1. 組件參數(shù)
import React, { Component } from 'react';
function HOCFactoryFactory(...param) {
  // 可以做一些改變params的事
  return function HOCFactory(WrappeComponent) {
    return class HOC extend Component {
      render() {
        return <WrappedComponent {...this.props} />
      }
    }
  }
}

// 使用
HOCFactoryFactory(params)(WrappedComponent)

@HOCFactoryFactory(params)
class WrappedComponent extends React.Component {  }

組件疊加使用

多個組件方法,疊加使用,可以利用 compose方法包裹
const FinalSelector = compose(asyncSelectDecorator,searchDecorator, selectedItemDecorator)(Selector);

組合式組件架構(gòu)

性能優(yōu)化

1、純函數(shù)
2、pureRender
官方在早期就為開發(fā)者提供了名為 react-addons-pure-render-mixin 的插件。其原理為重新實現(xiàn)了 shouldComponentUpdate生命周期方法,讓當前傳入的 props 和 state 與之前的作淺比較,如果返回 false,那么組件就不會執(zhí)行 render 方法。
3、Immutable
4、key
5、react-addons-perf
量化以上所做的性能優(yōu)化的效果。

動畫

React Transition

生命周期

  • componentWillAppear
  • componentDidAppear
  • componentWillEnter
  • componentDidEnter
  • componentWillLeave
  • componentDidLeave
    componentWillxxx 在什么時候觸發(fā)很容易判斷,只需在componentWillReceiveProps 中對 this.props.children 和nextProps.children 做一個比較即可。而 componentDidxxx 要何時觸發(fā)呢?
    可以給 componentWillxxx 提供一個回調(diào)函數(shù),用來執(zhí)行componentDidxxx。

緩動函數(shù)

自動化測試

Jest

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

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

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