博客 / 詳情

返回

精益 React 學習指南 (Lean React)- 4.3 React Tricks

react tricks

編輯中。。。

React 自身提供的 API 並不多,但總有一些比較 trick 的 API 和點是平時可能忽略的,本節將列舉一下相關的點。

  • setState

    • setState function param

    • setState 延遲

  • ref

    • ref as function

    • 區分 DOM node 和 React node

    • 利用 ref 實現父組件到子組件的通信

  • children

    • top api reactChildren

    • children as a function

  • dangerouslySetInnerHTML

  • 狀態雙向綁定

  • 受控組件

    • 通過 value=null 轉變為非受控

    • radio buttons

setState

setState(function)

setState 的方法定義:

void setState(
      function|object nextState,
      [function callback]
    )

通常第一個參數為 object nextState ,nextState 會被 merge 到組件的 state 數據當中,根據定義可以看出第一個參數也可以為 function,當為函數時會獲得兩個參數 previousStatecurrentState ,其返回值當做 nextState 被 merge 到 state 中。

setState(function(previousState, currentProps) {
  return {myInteger: previousState.myInteger + 1};
});

setState 延遲

首先介紹一下 transaction ,transaction 是數據庫操作的概念,將對數據的操作通過 transaction 來表示,將操作和執行分離,下面以一個偽代碼的方式表示對數據的操作步驟:

// 代碼只是為了理解 transaction 和 react 無關
transactionManager = new TransactionManager
transactionManager.add(new Transaction(function(){
    // 修改數據的操作1
}))
transactionManager.add(new Transaction(function(){
    // 修改數據的操作2
}))
transactionManager.perform()

可以看到數據的修改操作和執行時分開的,React 和許多現代前端框架都借鑑了這一設計模式,將操作和執行分離 。 這樣的好處是可以做到極大的性能優化,舉個例子,我們知道 DOM 操作是極其耗時的,為了優化性能,我們可以將 DOM 操作合併一起執行。 React 的 DOM 更新也是合併執行的,這得益於 transaction 設計。

説了這麼多,為的是理解 setState 的延時特性:setState 修改狀態過後並不會立即生效,而是會被 React 批量執行(batch update)

eg:

// state = {a:1}
this.setState({a:2});
console.log(this.state.a) // 1

注:transaction 在 React 中的設計和數據庫的 transaction 是不同的,但不礙於理解這裏的延遲批量執行策略,在 React 的實現章節中會詳解 React 的 transaction 。

因為延遲執行的特性,不能用上面的方法同步獲取最新狀態,為了獲取 setState 成功過後的狀態,可以使用 callback, callback 會在修改 state 並重渲染過後調用

setState(nextState, function()) {
    // do something after setState  
})

ref

ref as function

在某些情況下,父組件可能需要引用子組件並調用其中的方法或找到子組件的 DOM,React 提供了 ref 屬性作為實現方式,通常的做法是傳遞一個字符串,然後在組件中引用 refs

eg:

<input ref="myInput" />
var input = this.refs.myInput;
var inputValue = input.value;
var inputRect = input.getBoundingClientRect();

ref 也可以作為函數,函數傳遞的參數為對應組件的實例 :

render: function() {
    return (
      <TextInput
        ref={function(input) {
          if (input != null) {
            input.focus();
          }
        }} />
    );
},

React 官方更推薦 function , string 類型會在將來廢除。

區分 DOM node 和 React node

需要注意的是,因組件不同 ref 獲取到的引用也不同,主要有兩種類型

  1. 自定義組件:ref 引用的是 React 組件實例;

  2. 原生組件: 如 div、input ,ref 引用的是 DOM node;

如果是自定義組件,為了獲取組件的 DOM 可以使用 ReactDOM.findDOMNode 方法:

const $dom = ReactDOM.findDOMNode(ref);

利用 ref 實現父組件到子組件的通信

對於自定義組件的情況,ref 可以獲取子組件的實例,也就可以訪問子組件的實例方法。 雖然這種通信方式有違應用的 reactive 設計,但在實現第三方組件的時候,可以用過這種方式實現一些 trick 的通信。

eg:

const Child = React.createClass({
    render() {
        return <div> ...</div>
    },
    foo() {
        console.log('call child foo');
    }
})

const Parent = React.createClass({
    render() {
        return (<div>
                    <Child ref={child => {child.foo();}} />
                </div>)
    }
})

children

children top api

我們已經知道了可以通過 children 訪問傳遞給組件的子組件,分兩種情況:

  1. 多個子組件:children 為一個數組

  2. 單個子組件:children 為單個組件

為了更方便的操作 children, React 提供了一些工具函數

React.children.map();
React.children.forEach();
React.children.count();
// 返回唯一的一個 chid, 否則會報錯
React.children.only();
React.children.toArray();

Q:為什麼不用 Array.prototype.map, children 不是數組嗎?
A:children 只有一個 child 的情況,作為一個組件實例,並非一個數組元素,除此之外還有一種叫 keyed fragment 的 children 類型,應當把 children 視為 不透明對象 ,操作它需要通過 children api。

keyed fragment

為了解釋 keyed fragment ,先引入一個問題:

如果定義瞭如下的這樣一個組件:

const Swapper = React.createClass({
  propTypes: {
    // `leftChildren` and `rightChildren` can be a string, element, array, etc.
    leftChildren: React.PropTypes.node,
    rightChildren: React.PropTypes.node,

    swapped: React.PropTypes.bool
  },
  render() {
    var children;
    if (this.props.swapped) {
      children = [this.props.rightChildren, this.props.leftChildren];
    } else {
      children = [this.props.leftChildren, this.props.rightChildren];
    }
    return <div>{children}</div>;
  }
});

children 如果 rightChildren 和 leftChildren 都為相同類型的數組,當 swapped 變量改變過後,左右會交換,出現的問題是所有的 children 都會 unmount 和 remount,因為對於 組件 set 並沒有辦法設置 key。

解決辦法有兩種, 第一種是左右 children 封裝在兩個不同的 wrapper div 中, 這樣因為每個 child 都有 key , 所以不會導致 unmount。

第二種辦法是通過 keyed fragment,先來看看 keyed fragment 如果解決這個問題:

const createFragment = require('react-addons-create-fragment');

if (this.props.swapped) {
  children = createFragment({
    right: this.props.rightChildren,
    left: this.props.leftChildren
  });
} else {
  children = createFragment({
    left: this.props.leftChildren,
    right: this.props.rightChildren
  });
}

通過這種方式可以為 set 創建 key, createFragment 返回的 children 同樣可以通過 React.children API 進行操作。

在未來 React 可能提供如下 API 來替換 createFragment

return (
  <div>
    <x:frag key="right">{this.props.rightChildren}</x:frag>,
    <x:frag key="left">{this.props.leftChildren}</x:frag>
  </div>
);

clone Element

在某些情況下,可能需要複製 React Element 並生成新的 Element 並附帶一些屬性,使用方式:

ReactElement cloneElement(
  ReactElement element,
  [object props],
  [children ...]
)

對於使用場景上一節的 Decorator Pattern 就是最好的例子。

function as children

在上一節的 Presenter Pattern 中已經介紹過,可以將傳遞一個函數作為 children
更多相關討論可參考

https://discuss.reactjs.org/t...

user avatar zzd41 頭像 ivyzhang 頭像 dujing_5b7edb9db0b1c 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.