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
「高內聚低耦合」是模塊設計中很重要的原則。對於一些純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,定位問題的範圍也能夠鎖定在組件內部,這對提升項目的可維護性是很是有幫助的。函數
上文提到的React-Draggable功能實現相對複雜,依據controlled和uncontrolled分紅了兩個組件,更多的時候,每每是一個組件承載了兩種調用方式。(Mixed Component) 例如Ant.Design存在有許多例子:學習
current
與defaultCurrent
checked
與defaultChecked
value
與defaultValue
把兩種模式集中在一個組件中,如何更好的組織代碼呢?以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便可
)
}
}
複製代碼
在通常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在代碼簡化,可維護性上都有必定的優點,可是也應該把握好應用場景:「確實不關心組件內部的狀態」。其實在足夠複雜的項目中,多數場景仍是須要對全部組件狀態有徹底把控的能力(如:撤銷功能)。學習同樣東西,並不必定是隨處可用,重要的是在最契合的場景,應該下意識的想起它。