首先说明,我们基于表单数据的处理方式来理解受控组件与非受控组件。
非受控组件
概念介绍
非受控组件中,表单数据将交由 DOM 节点来处理,类似原生 JS 中获取 DOM 值的思路(document.getElementById("test")
)一样,只是在 React 是通过 Refs 来实现。
通俗的讲,对非受控组件而言,表单元素的值,是“随用随取”。
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>非受控组件</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app"></div>
<!-- step01: 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- step02: 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- step03: 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
// 1. 创建类式组件
class LoginForm extends React.Component {
// 创建 ref 容器
accountInputRef = React.createRef()
passwordInputmyRef = React.createRef()
handleSubmit = (event) => {
event.preventDefault();
const {accountInputRef, passwordInputmyRef} = this
console.log('@', `账号:${accountInputRef.current.value},密码:${passwordInputmyRef.current.value}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<p>账号:<input type="text" ref={this.accountInputRef} /></p>
<p>密码:<input type="password" ref={this.passwordInputmyRef} /></p>
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<LoginForm />, document.getElementById('app'));
</script>
</body>
</html>
受控组件
什么是受控组件
受控组件中,表单数据是由 React 组件中的 state 来管理的。也即是说受控组件就是受组件 state 的控制。
受控组件中的表单元素,需要绑定监听表单元素值变化的事件(通常是 onChange 事件),当表单元素的值发生变化时,将变化后的值更新到 state,当需要使用表单元素的值的时候,再通过 state 来取。
受控组件还有个特点,就是不使用 Refs 。
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>受控组件</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app"></div>
<!-- step01: 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- step02: 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- step03: 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
// 1. 创建类式组件
class LoginForm extends React.Component {
// 初始化 state
state = { account: '', password: ''}
// 保存账号到 state
saveAccount = (event) => {
this.setState({account: event.target.value})
}
// 保存密码到 state
savePassword = (event) => {
this.setState({password: event.target.value})
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault();
const {account, password} = this.state
console.log('@', `账号:${account},密码:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<p>账号:<input type="text" onChange={this.saveAccount} /></p>
<p>密码:<input type="password" onChange={this.savePassword} /></p>
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<LoginForm />, document.getElementById('app'));
</script>
</body>
</html>
上面的代码比较简单,仅用于示例演示。如果用于实际工作中,就会有问题,比如一个表单可能会有多个表单元素,那我们就需要写很多个 saveXxx 的事件绑定函数,这样写倒是也没错,只是比较麻烦,代码也不好维护。
代码优化
针对上面这个问题,接下来我们通过使用【高阶函数】和【函数的柯里化】对其进行优化来解决上面的问题。
先科普两个概念:
- 什么是高阶函数
如果一个函数符合下面 2 个规范中的任何一个,那该函数就是高阶函数。
- 若 A 函数,接收的参数是一个函数,那 A 就称之为高阶函数。
- 若 A 函数,调用的返回值依然是一个函数,那 A 就称之为高阶函数。
常见的高阶函数:Promise,setTimeout,arr.map()等。
- 什么是函数柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
处理后的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用高阶函数和函数柯里化优化受控组件</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app"></div>
<!-- step01: 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- step02: 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- step03: 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
// 1. 创建类式组件
class LoginForm extends React.Component {
// 初始化 state
state = { account: '', password: ''}
// 保存表单字段的值到 state,运用了高阶函数和函数的柯里化的技术
saveFormData = (fieldName) => {
return (event) => {
this.setState({[fieldName]: event.target.value})
}
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault();
const {account, password} = this.state
console.log('@', `账号:${account},密码:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<p>账号:<input type="text" onChange={this.saveFormData('account')} /></p>
<p>密码:<input type="password" onChange={this.saveFormData('password')} /></p>
<button>登录</button>
</form>
)
}
}
// 渲染组件到页面
ReactDOM.render(<LoginForm />, document.getElementById('app'));
</script>
</body>
</html>