React項目中Uncontrolled Component的運用

uncontrolled是React中一個很重要概念,起源於(不知該概念是否更早在其它領域出現過)React對一些form元素(input, textarea等)的封裝,官方文檔給出一些描述:html

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.react

實際上,uncontrolled思想的運用已經遠遠超出了form元素的範疇,合理的使用uncontrolled component能夠很大程度的簡化代碼,提升項目的可維護性。本文將結合幾個經常使用的例子,總結我的在項目實踐中對uncontrolled思想的運用。若有錯誤,歡迎指出。git

Uncontrolled Component在可維護性上的優點。

「高內聚低耦合」是模塊設計中很重要的原則。對於一些純UI組件,uncontrolled模式將狀態封裝於組件內部,減小組件通訊,很是符合這一原則。著名的開源項目React-Draggable爲咱們提供了很好的示例。github

可拖拽組件的uncontrolled實現:app

import React from 'react'
import Draggable from 'react-draggable'

class App extends React.Component {
  render() {
    return (
      <Draggable> <div>Hello world</div> </Draggable>
    );
  }
}
複製代碼

可拖拽組件的controlled實現:ide

import React from 'react'
import {DraggableCore} from 'react-draggable'

class App extends React.Component {
   state = {
    position: {x: 0, y: 0}
  }

  handleChange = (ev, v) => {
    const {x, y} = this.state.position
    const position = {
      x: x + v.deltaX,
      y: y + v.deltaY,
    }

    this.setState({position})
  }

  render() {

    const {x, y} = this.state.position
    return (
      <DraggableCore onDrag={this.handleChange} position={this.state.position} > <div style={{transform: `translate(${x}px, ${y}px)`}}> Hello world </div> </DraggableCore>
    );
  }
}

複製代碼

比較以上兩個示例,uncontrolled component將拖拽的實現邏輯、組件位置對應的state等所有封裝在組件內部。做爲使用者,咱們絲絕不用關心其的運做原理,即便出現BUG,定位問題的範圍也能夠鎖定在組件內部,這對提升項目的可維護性是很是有幫助的。函數

Mixed Component組件的具體實現

上文提到的React-Draggable功能實現相對複雜,依據controlled和uncontrolled分紅了兩個組件,更多的時候,每每是一個組件承載了兩種調用方式。(Mixed Component) 例如Ant.Design存在有許多例子:學習

  • Pagination組件中有currentdefaultCurrent
  • Switch組件中的checkeddefaultChecked
  • Slider組件中的valuedefaultValue

把兩種模式集中在一個組件中,如何更好的組織代碼呢?以Switch爲例:ui

class Switch extends Component {
  constructor(props) {
    super(props);

    let checked = false;

    // 'checked' in props ? controlled : uncontrolled
    if ('checked' in props) {
      checked = !!props.checked;
    } else {
      checked = !!props.defaultChecked;
    }
    this.state = { checked };
  }

  componentWillReceiveProps(nextProps) {
    // 若是controlled模式,同步props,以此模擬直接使用this.props.checked的效果
    if ('checked' in nextProps) {
      this.setState({
        checked: !!nextProps.checked,
      });
    }
  }

  handleChange(checked) {
    // controlled: 僅觸發props.onChange
    // uncontrolled: 內部改變checked狀態
    if (!('checked' in this.props)) {
      this.setState({checked})
    }

    this.props.onChange(checked)
  }

  render() {
    return (
      // 根據this.state.checked 實現具體UI便可
    )
  }
}

複製代碼

Uncontrolled思想在類Modal組件的擴展

在通常React的項目中,咱們一般會使用以下的方式調用Modal組件:this

class App extends React.Component {
  state = { visible: false }

  handleShowModal = () => {
    this.setState({ visible: true })
  }

  handleHideModal = () => {
    this.setState({ visible: false })
  }

  render() {
    return (
      <div> <button onClick={this.handleShowModal}>Open</button> <Modal visible={this.state.visible} onCancel={this.handleHideModal} > <p>Some contents...</p> <p>Some contents...</p> </Modal> </div>
    )
  }
}
複製代碼

根據React渲染公式UI=F(state, props),這麼作並無什麼問題。可是若是在某個組件中大量(不用大量,三個以上就深感痛苦)的使用到類Modal組件,咱們就不得不定義大量的visible state和click handle function分別控制每一個Modal的展開與關閉。最具表明性的莫過於自定義的Alert和Confirm組件,若是每次與用戶交互都必須經過state控制,就顯得過於繁瑣,莫名地增長項目複雜度。 所以,咱們能夠將uncontrolled的思想融匯於此,嘗試將組件的關閉封裝於組件內部,簡化大量冗餘的代碼。以Alert組件爲例:

// Alert UI組件,將destroy綁定到須要觸發的地方
class Alert extends React.Component {
  static propTypes = {
    btnText: PropTypes.string,
    destroy: PropTypes.func.isRequired,
  }

   static defaultProps = {
    btnText: '肯定',
  }

  render() {
    return (
      <div className="modal-mask"> <div className="modal-alert"> {this.props.content} <button className="modal-alert-btn" onClick={this.props.destroy} > {this.props.btnText} </button> </div> </div>
    )
  }
}

// 用於渲染的中間函數,建立一個destroy傳遞給Alert組件
function uncontrolledProtal (config) {
  const $div = document.createElement('div')
  document.body.appendChild($div)

  function destroy() {
    const unmountResult = ReactDOM.unmountComponentAtNode($div)
    if (unmountResult && $div.parentNode) {
      $div.parentNode.removeChild($div)
    }
  }

  ReactDOM.render(<Alert destroy={destroy} {...config} />, $div) return { destroy, config } } /** * 考慮到API語法的優雅,咱們經常會把相似功能的組件統一export。例如: * https://ant.design/components/modal/ * Modal.alert * Modal.confirm * * https://ant.design/components/message/ * message.success * message.error * message.info */ export default class Modal extends React.Component { // ... } Modal.alert = function (config) { return uncontrolledProtal(config) } 複製代碼

以上咱們完成了一個uncontrolled模式的Alert,如今調用起來就會很方便,再也不須要定義state去控制show/hide了。在線預覽

import Modal from 'Modal'

class App extends React.Component {
  handleShowModal = () => {
    Modal.alert({
      content: <p>Some contents...</p>
    })
  }

  render() {
    return (
      <div> <button onClick={this.handleShowModal}>Open</button> </div>
    )
  }
}
複製代碼

結語

uncontrolled component在代碼簡化,可維護性上都有必定的優點,可是也應該把握好應用場景:「確實不關心組件內部的狀態」。其實在足夠複雜的項目中,多數場景仍是須要對全部組件狀態有徹底把控的能力(如:撤銷功能)。學習同樣東西,並不必定是隨處可用,重要的是在最契合的場景,應該下意識的想起它。

相關文章
相關標籤/搜索