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在代码简化,可维护性上都有必定的优点,可是也应该把握好应用场景:“确实不关心组件内部的状态”。其实在足够复杂的项目中,多数场景仍是须要对全部组件状态有彻底把控的能力(如:撤销功能)。学习同样东西,并不必定是随处可用,重要的是在最契合的场景,应该下意识的想起它。