歡迎關注個人公衆號睿Talk
,獲取我最新的文章:
javascript
React的Ref特性是React聲明式編程(Declarative Programming)設計哲學的一個重要補充。以前對它的認識只是停留在非受控組件這種特殊場景,直到最近爲了實現項目中的一個特殊功能,纔對它有了更深的理解。java
React的官方解釋是這樣的:編程
In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch.
當中提到了幾個關鍵的概念。segmentfault
值得一提的是當中聲明式編程(Declarative Programming)和命令式編程(Imperative Programming)的區別。聲明式編程的特色是隻描述要實現的結果,而不關心如何一步一步實現的,而命令式編程則相反,必須每一個步驟都寫清楚。以數組爲例,若是要打印出數組全部元素,聲明式編程是這麼寫:數組
let arr = [1,2,3]; const printElement = (element) => console.log(element); arr.forEach( printElement );
而用命令式編程,會這麼寫:ide
let arr = [1,2,3]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); }
經過對比,咱們能夠很直觀的感覺到聲明式編程的好處。代碼的核心功能就是這句:函數式編程
arr.forEach( printElement );
咱們能夠根據語義直觀的理解代碼的功能是:針對數組的每個元素,將它的值打印出來。沒必要關心實現其的細節。而命令式編程必須將每行代碼讀懂,而後再整合起來理解整體實現的功能。函數
React有2個基石設計理念:一個是聲明式編程,一個是函數式編程。函數式編程之後有機會再展開講。聲明式編程的特色體如今2方面:動畫
所以,在使用React的時候,通常不多須要用到Ref。那麼,Ref的使用場景又是什麼?this
React官方文檔是這麼說的:
There are a few good use cases for refs:
- Managing focus, text selection, or media playback.
- Triggering imperative animations.
- Integrating with third-party DOM libraries.
Avoid using refs for anything that can be done declaratively.
意思是:
最後還補了一句:若是要實現的功能能夠經過聲明式的方式實現,就不要藉助Ref。若是你就是那麼任性,要使用Ref,具體該怎麼作?
class CustomTextInput extends React.Component { constructor(props) { super(props); this.focusTextInput = this.focusTextInput.bind(this); } focusTextInput() { // 獲取DOM元素後能夠直接操做DOM API this.textInput.focus(); } render() { // 經過Ref獲取DOM元素,再保存在實例變量focusTextInput中 return ( <div> <input type="text" ref={(input) => { this.textInput = input; }} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
// CustomTextInput組件的定義跟上面徹底相同 class AutoFocusTextInput extends React.Component { componentDidMount() { // 這裏直接調用CustomTextInput實例的focusTextInput方法 this.textInput.focusTextInput(); } render() { return ( // 經過Ref獲取CustomTextInput實例,再保存在實例變量textInput中 <CustomTextInput ref={(input) => { this.textInput = input; }} /> ); } }
理解了基本使用後,再回到我遇到的真實場景。
先簡單描述下項目要實現的效果:在一個頁面中分左右兩部分,左邊顯示商品的列表,右邊顯示選中商品的購物車。一次能夠將左邊的多個商品,添加到右邊的購物車中。因爲具體的實現細節比較複雜,當時的分工是一我的實現左側的商品列表,另外一人負責右邊的購物車。若是用傳統的React設計理念來實現,必需要藉助左邊列表組件和右邊購物車組件的共同父組件,也就是頁面的根組件,來維護選中的商品數組。而後再將商品數組傳入購物車展現。這樣作的話實現起來很是不方便,要把購物車中的不少邏輯都放在父組件中,而實際上這些邏輯是購物車本身獨立使用的,跟其它組件並無交互。左側的列表組件只須要將選中的商品告知購物車便可,後續的邏輯由購物車本身實現。
考慮再三後,咱們決定經過Ref的方式將其內部的addProduct的方法暴露出來給父組件,當選中一個商品後,列表組件將商品信息傳遞給父組件,父組件再經過addProduct方法將商品信息傳入購物車。由購物車組件本身來維護客戶購買的全部商品數據。總體邏輯就是這樣,具體代碼此處略過,主要描述的是思路。也許你會問爲啥不將商品信息經過props傳入購物車組件?實現上是沒問題的,都能達到效果。但咱們認爲顯式的調用addProduct方法會更加直觀的表達語義,同時對addProduct方法也作了限制,只負責添加商品信息,不作更多的邏輯判斷。
若是說還有沒更好的實現方式,實際上是有的,能夠經過Redux來管理整個頁面的狀態。但引入Redux後,代碼的維護成本會隨之上升,目前暫時不做考慮。
本文以項目中遇到的設計問題爲起點,介紹了React Ref特性的使用場景和具體的使用方法,順便還對比了聲明式編程和命令式編程2種編程風格,對React的設計理念做了簡要的解讀。
這篇文章是八個月前寫的,隨着業務的快速發展,當初寫的代碼沒法承載新業務,要全盤重構了。重構的設計中,咱們引入了Redux來作狀態管理,組件之間的耦合度一會兒就降低了不少,複雜業務的實現也變得容易了。因此我認爲最優的實現方案是使用Redux,而不是Ref。