React(三)- React收集表单数据、函数柯里化及生命周期
一. 收集表单数据
React中,包含表单的组件有两大类:
- 受控组件
- 非受控组件
1.1 非受控组件:
页面中所有输入类的DOM,只要现用现取,就是一个非受控组件。非受控组件即不受状态的控制,获取数据就是相当于操作DOM。一般没有value。 案例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>非受控组件</title>
</head>
<body>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<div id="test"></div>
<script type="text/babel">
// 创建登录组件
class Login extends React.Component {
handleSubmit = (event) => {
// 阻止表单提交
event.preventDefault()
const { username, password } = this
alert(`你输入的用户名是${username.value}`)
}
render() {
return (
// onSubmit是React里面的表单提交标签
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
{/*意思是将当前组件的引用赋值给this对象的username属性*/}
{/*页面中所有输入类的DOM,只要现用现取,就是一个非受控组件*/}
用户名:<input ref={c => this.username = c} type="text" name="userName" />
密码:<input ref={c => this.password = c} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
</script>
</body>
</html>
结果如下:同时点击弹框后,页面并不会跳转到百度搜索页。
1.2 受控组件:
受控组件是必须要有value
的,value
用来传入一个参数,结合onchang
来控制这个参数输出。(即随着输入的内容去维护状态值)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>受控组件</title>
</head>
<body>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<div id="test"></div>
<script type="text/babel">
// 创建登录组件
class Login extends React.Component {
// 保存用户名到状态中
saveUserName= (event) => {
// 这里d event.target.value也就是input框中的内容值
// console.log(event.target.value)
this.setState({ username: event.target.value })
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUserName} type="text" name="userName" />
密码:<input type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
</script>
</body>
</html>
效果如下:
实时修改输入框的内容后,可发现其状态中的username
属性也会随之改变:
二. 函数柯里化
2.1 案例优化
上面的受控组件案例中,我们用onChange
去绑定对应的一个函数来实现状态的实时变化,那么如果再加一个属性,就是这样的:
saveUserName = (event) => {
this.setState({ username: event.target.value })
}
savePassword = (event) => {
this.setState({ password: event.target.value })
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUserName} type="text" name="userName" />
密码:<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
)
}
那么试想一下,若一个用户注册的场景,其属性有几十个,那么按照上述的写法,岂不是也要写几十个对应的方法了?这样代码显得非常冗余。
因此这就用到了高阶函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高级函数:
- 若A函数,接收的参数是一个函数,那么A是高阶函数。
- 若A函数,调用的返回值是一个函数,那么A也是高阶函数。
函数柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
上述案例改为:
// 创建登录组件
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveFormData = (dataType) => {
console.log(dataType)
return (event) => {
this.setState({ [dataType]: event.target.value })
}
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="userName" />
密码:<input onChange={this.saveFormData('password')} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
可以看下控制台,每次改变任意一个输入框,其输出的dataType
为两个节点。
同时,还能实时的将结果更新到组件中:
可以知道,即使有十几个属性,我们只需要定义一个函数即可实现所有属性状态的维护。
其实上述案例的优化,也用到了函数柯里化,那么接下来来看下什么是函数柯里化:
2.2 函数柯里化案例
Demo1:
<script type="text/javascript">
function sum(a, b, c) {
return a + b + c
}
const res = sum(1, 2, 3)
console.log(res)
</script>
// 若函数柯里化,则变成:
<script type="text/javascript">
function sum(a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
const res = sum(1)(2)(3)
console.log(res)
</script>
紧接着再来看下上述案例的部分代码:
saveFormData = (dataType) => {
console.log(dataType)
return (event) => {
this.setState({ [dataType]: event.target.value })
}
}
从上面案例结果输出中可以发现,dataType
中包含了多个属性,最后统一进行处理,这也就是用到了函数柯里化的思想,那么这个代码如果不使用函数柯里化那会变成什么样子?来看下:
<script type="text/babel">
// 创建登录组件
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveFormData = (dataTyp, event) => {
this.setState({ [dataType]: event.target.value })
}
render() {
return (
<form onSubmit={this.handleSubmit}>
{/*注意函数不能加括号,因为我们希望其作为回调,而不是调用一个方法,若加了括号,传入的是一个返回值*/}
用户名:<input onChange={event => this.saveFormData('username', event)} type="text" name="userName" />
密码:<input onChange={event => this.saveFormData('password', event)} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
</script>
三. 组件生命周期
3.1 从案例出发引入生命周期的概念
Demo需求:
- 定义一个组件,让指定的文本做一个渐变效果,直到消失,耗时2s。
- 点击按钮,则对应组件卸载,从页面中消失。
这里先说两个概念:
- 挂载
mount
:可以理解为我们将一个组件进行了渲染。 - 卸载安
unmount
:卸载对应组件。
Demo如下:
class MyLife extends React.Component {
state = { opacity: 0.5 }
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 名字固定,在组件挂载页面完毕后会调用这个方法,只会调用一次
componentDidMount() {
setInterval(() => {
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
this.setState({ opacity })
}, 200)
}
// 在初始化渲染和状态更新之后调用
render() {
return (
<div>
<h2 style={{ opacity: this.state.opacity }}>Hello World</h2>
<button onClick={this.death}>卸载组件</button>
</div>
)
}
}
ReactDOM.render(<MyLife />, document.getElementById('test'))
</script>
这样的代码确实能够做到字体渐进的一个效果,但是在点击卸载组件按钮之后,控制台会有所报错:
意思是:不可以在已经卸载的组件中进行状态的更新,因为在上述代码中,组件挂载完毕后,就打开了一个定时器,那么在组件卸载的时候,定时器依旧没有关闭!因此在卸载组件的同时,我们还需要清除对应的定时器。
方式1:通过clearInterval()
方法,删除对应的定时器。
// 知识点复习:对于这种箭头函数,其内部的this是丢失的,那么自动沿用上一级的this,而这里的外层this指的是组件的实例对象,因此通用,可以拿来使用
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
clearInterval(this.timer)
}
componentDidMount() {
this.timer = setInterval(() => {
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
this.setState({ opacity })
}, 200)
}
方式2:通过自带方法componentWillUnmount()
// 组件将要卸载的时候会调用这个方法
componentWillUnmount() {
clearInterval(this.timer)
}
从这个案例中可以发现什么:
- React在渲染组件的时候会调用
render()
- 在组件挂载完毕后,会调用
componentDidMount()
- 在组件即将卸载前,会调用
componentWillUnmount()
而这些方法都涉及到了一个组件的生命周期,这些函数可以称呼为生命周期回调函数或者生命周期钩子函数(或者把函数去掉)。
3.2 生命周期(旧)
从上面的例子当中我们可以做以下理解:
- 组件从创建到死亡它会经历一些特定的阶段。
- React组建中包含了一系列的钩子函数,会在特定阶段调用。
- 我们定义组件时,往往会在钩子函数中做一些特定的工作。
其生命周期图如下:
3.2.1 挂载时的生命周期
案例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3</title>
</head>
<body>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<div id="test"></div>
<script type="text/babel">
class Count extends React.Component {
constructor(props) {
console.log('Count---constructor')
super(props)
}
state = { count: 0 }
// 组件将要挂载的时候调用
componentWillMount() {
console.log("Count---componentWillMount");
}
// 组件挂在完毕的时候调用
componentDidMount() {
console.log("Count---componentDidMount");
}
// 渲染或者发生更改的时候调用
render() {
console.log("Count---render");
const { count } = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点击</button>
</div>
)
}
// 点击按钮的时候回调
add = () => {
const { count } = this.state
this.setState({ count: count + 1 })
}
}
ReactDOM.render(<Count />, document.getElementById('test'))
</script>
</body>
</html>
最后的输出结果:
3.2.2 更新时的生命周期
Demo1案例如下:
class Count extends React.Component {
// 渲染或者发生更改的时候调用
state = { count: 0 }
render() {
console.log("Count---render");
const { count } = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点击</button>
<button onClick={this.death}>卸载</button>
</div>
)
}
componentWillReceiveProps() {
console.log("Count---componentWillReceiveProps");
}
// 控制组件更新的阀门
shouldComponentUpdate() {
console.log("Count---shouldComponentUpdate");
// 记得返回true,若返回false,则修改state状态的时候,会进行阻塞,默认返回true
return true;
}
// 组件将要卸载时调用
componentWillUpdate() {
console.log("Count---componentWillUpdate");
}
// 组件完成卸载时调用
componentDidUpdate() {
console.log("Count---componentDidUpdate");
}
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
add = () => {
const { count } = this.state
this.setState({ count: count + 1 })
}
}
ReactDOM.render(<Count />, document.getElementById('test'))
若阀门打开(即返回true),那么控制台的输出结果如下:
若阀门关闭(即返回false),那么控制台的输出结果如下:
shouldComponentUpdate() {
console.log("Count---shouldComponentUpdate");
return false;
}
Demo2案例:componentWillReceiveProps()
的调用:
class A extends React.Component {
state = { msg: '你好' }
render() {
return (
<div>
<div>A组件</div>
<button onClick={this.change}>替换</button>
<B msg={this.state.msg} />
</div>
)
}
change = () => {
this.setState({ msg: 'HelloWorld' })
}
}
class B extends React.Component {
componentWillReceiveProps(props) {
console.log("B---componentWillReceiveProps", props);
}
shouldComponentUpdate() {
console.log("B---shouldComponentUpdate");
return true;
}
// 组件将要卸载时调用
componentWillUpdate() {
console.log("B---componentWillUpdate");
}
// 组件完成卸载时调用
componentDidUpdate() {
console.log("B---componentDidUpdate");
}
render() {
return (
<div>组件B接收到的消息:{this.props.msg}</div>
)
}
}
ReactDOM.render(<A />, document.getElementById('test'))
结果如下:
点击替换之后:
总结
初始化阶段:由ReactDOM.render()
触发初次渲染,调用顺序:
constructor()
componentWillMount()
render()
componentDidMount()
,一般常用来做一些初始化的动作,比如打开一个定时器、发送网络请求、订阅消息等。
更新阶段:由组件内部this.setState()
或者父类组件重新render()
触发,调用顺序:
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
卸载组件:由ReactDOM.unmountComponentAtNode()
触发,调用顺序:
componentWillUnmount()
,一般常用来做一些收尾工作,例如关闭定时器等。