React(三)- 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需求:

  1. 定义一个组件,让指定的文本做一个渐变效果,直到消失,耗时2s。
  2. 点击按钮,则对应组件卸载,从页面中消失。

这里先说两个概念:

  • 挂载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)
}

从这个案例中可以发现什么:

  1. React在渲染组件的时候会调用render()
  2. 在组件挂载完毕后,会调用componentDidMount()
  3. 在组件即将卸载前,会调用componentWillUnmount()

而这些方法都涉及到了一个组件的生命周期,这些函数可以称呼为生命周期回调函数或者生命周期钩子函数(或者把函数去掉)

3.2 生命周期(旧)

从上面的例子当中我们可以做以下理解:

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组建中包含了一系列的钩子函数,会在特定阶段调用。
  3. 我们定义组件时,往往会在钩子函数中做一些特定的工作。

其生命周期图如下:
在这里插入图片描述

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()触发初次渲染,调用顺序:

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount(),一般常用来做一些初始化的动作,比如打开一个定时器、发送网络请求、订阅消息等。

更新阶段:由组件内部this.setState()或者父类组件重新render()触发,调用顺序:

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render()
  4. componentDidUpdate()

卸载组件:由ReactDOM.unmountComponentAtNode()触发,调用顺序:

  1. componentWillUnmount(),一般常用来做一些收尾工作,例如关闭定时器等。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值