Element(React)原始碼分析系列4--Radio元件

hello等風來發表於2019-06-06

前言

學習就好比是座大山,人們沿著不同的路登山,分享著自己看到的風景。你不一定能看到別人看到的風景,體會到別人的心情。只有自己去登山,才能看到不一樣的風景,體會更加深刻。一千個讀者就有一千個哈姆雷特,但是莎士比亞心中的哈姆雷特肯定只有一個。就好比element原始碼只有一個,但每個人看到的都是不一樣的風景。element原始碼解讀是一個系列,每一個元件細膩的背後都能看到前端工程師付出的心血,本篇帶來的是Element原始碼分析系列4-Radio(單選框)

簡介

單選框這個元件看似簡單,實則知識點眾多,較為複雜,如果寫一個html的原生單選框,那確實很簡單,但是封裝一個完整的單選元件就不那麼簡單了。element團隊在整個radio元件的設計和構思真可謂十分的細緻,沒有一行多餘的程式碼。從基礎的css到選擇邏輯都無不彰顯其巧奪天工的思想。

劃重點

1、如何隱藏原始radio標籤預設樣式

我們都知道原生的radio標籤很醜,樣式在各個瀏覽器不統一,作為一個元件不可能就這樣屈服,況且人這種物種總是希望統駕馭所有的物質之上,包括程式碼。所以必須自己實現所有radio按鈕的樣式,那麼如何自己實現一個可控制的radio標籤呢?看element是怎麼實現的。

radio
看圖不難分析出radio元件的html樣式,整個大盒子被一個laber標籤包裹著,laber裡分為兩個span,第一個span是用來展示選擇按鈕的,第二個span是來描述選項的。第一個span裡應該包括input(用來做radio)和span(隱藏真正的input)兩個標籤。其相關的html結構如下:

<label className='el-radio'>
    <span>
      <span className="el-radio__inner"></span>
      <input 
        type="radio"
        className="el-radio__original"
      />
    </span>
    <span className="el-radio__label">
      {children || value}
    </span>
</label>
複製程式碼

問題一:如何做到隱藏原始radio標籤預設樣式?作者巧用opacity:0,真正的input透明度為0,且是絕對定位脫離文件流,因此不佔空間且我們看不到,注意不是display:none或者visibility:hidden,如果是none或者hidden的話則無法觸發滑鼠點選了,所以只有opacity:0才能達到目的.

.el-radio__original {
    opacity: 0;
    outline: 0;
    position: absolute;
    z-index: -1;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
}
複製程式碼

2、如何點選選擇按鈕展示不同的狀態

如何做到點選旋鈕顯示不同的狀態。通過radio標籤可分析出,點選即可以理解為一個事件(這裡用onChange表示)。通過點選事件傳遞一個props值(checked)顯示不同的狀態。所以在input上一定會有一個事件和一個所要傳遞的狀態。

<input 
    type="radio"
    className="el-radio__original"
    checked={checked}  // 傳遞的狀態
    onChange={this.onChange.bind(this)}   // 事件
 />
複製程式碼

那麼接下來就是定義事件了,其核心程式碼為:

constructor(props) {
    super(props);
    this.state = {
      checked: this.getChecked(props)  // 定義一個state,受props影響
    };
  }
  componentWillReceiveProps(props) {
    const checked = this.getChecked(props);  
    if (this.state.checked !== checked) {   // 維持每次點選時只選中一個
      this.setState({ checked });
    }
  }
  getChecked(props) {
    return  Boolean(props.checked)  // 根據傳遞的props,輸出true/false
  }
  onChange(e) {
    const checked = e.target.checked;  // 定義被點選項的checked為true
    if (checked) {
      if (this.props.onChange) {
        this.props.onChange(this.props.value);  // 向外暴露一個onChange事件並攜帶value值
      }
    }
    this.setState({ checked }); // 更新被點選項的state為true
  }
複製程式碼

如果你對上述描述的還是雨裡霧裡,那麼下面這張圖可以更好的幫組你理解element-radio標籤事件的邏輯:

Element(React)原始碼分析系列4--Radio元件
從圖中我們可以看出,radio是如何做到點選改變狀態,並且又如何維護每次點選完後radio的狀態。

  • 傳入checked作為props決定每個radio的狀態
  • 根據傳入的props,定義一個內部的state,跟props的checked保持一致
  • 點選事件改變當前的radio的狀態,更新state中的checked,並且向外傳遞一個value
  • 外面接受到傳來的value,重新定義props
  • 通過生命週期函式更新內部的state,讓其保持每個radio一致

3、Radio.Group如何做到單選框組

單選框組故名思議是將所有的radio包裹一層,由最外層原始事件來決定每個radio的狀態。故需要用到React的兩個API:React.Children.map、React.cloneElement

技術擴充套件:

  • React.Children 提供了用於處理 this.props.children 不透明資料結構的實用方法。

React.Children.map(children, function[(thisArg)])

在 children 裡的每個直接子節點上呼叫一個函式,並將 this 設定為 thisArg。如果 children 是一個陣列,它將被遍歷併為陣列中的每個子節點呼叫該函式。如果子節點為 null 或是 undefined,則此方法將返回 null 或是 undefined,而不會返回陣列。

  • React.cloneElement: 以 element 元素為樣板克隆並返回新的 React 元素。返回元素的 props 是將新的 props 與原始元素的 props 淺層合併後的結果。

React.cloneElement(element, [props], [...children])

先了解這兩個API,在來看看element中Radio.Group的原始碼就很簡單來。

<div ref='RadioGroup' className='el-radio-group'>
    {
      React.Children.map(this.props.children, element => {
        if (!element) {
          return null
        }

        return React.cloneElement(element, Object.assign({}, element.props, {
          onChange: this.onChange.bind(this),
          value: this.props.value,
        }))
      })
    }
</div>
複製程式碼

通過遍歷所有的radio元件,克隆出相應的radio元件,併為其附上radio的一些屬性,包括方法和value。

結語

縱觀整個radio的設計和實現,每個設計的過程都十分的微妙,尤其是radio如何更新狀態,在平時業務中,我們也經常會遇到這種需求,但是真正的實現起來卻沒有這麼簡潔。原始碼的學習或多或少可以讓我們增強自己的程式碼能力和業務能力。那麼跟隨我的腳步,帶你一步一步解析整個element原始碼的奇思妙想。山不再高,有仙則靈;水不在深,有心則成。後續將推出更多原始碼解析文章。原始碼請?這裡element-radio原始碼

相關文章