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,當為函數時會獲得兩個參數 previousState、currentState ,其返回值當做 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 獲取到的引用也不同,主要有兩種類型
-
自定義組件:ref 引用的是 React 組件實例;
-
原生組件: 如 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 訪問傳遞給組件的子組件,分兩種情況:
-
多個子組件:children 為一個數組
-
單個子組件: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...