【React 框架】React面向组件编程:非受控组件 / 生命周期 / DOM 的 diffing 算法

学习视频:尚硅谷React技术全家桶全套完整版(零基础入门到精通/男神天禹老师亲授)_哔哩哔哩_bilibili


一、受控组件与非受控组件(收集表单信息)

以下都用一个案例来说明,效果如下:

1、受控组件

主要步骤:

        (1)定义一个表单,创建 ref 容器。

        (2)给相应的结点元素增加 ref 标识。

        (3)通过 onSubmit 事件执行 printData 函数。

        (4)从 this 中获取 username 与 password 结点元素,通过 username.value 获取结点的值。

    <script type="text/babel">
        class Login extends React.Component {
            printData = (event) => {
                event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
                const { username, password } = this;
                alert(`用户名:${username.value},密码:${password.value}`);
            }

            render() {
                return (
                    // 注意:如果没有给 input 增加 name 属性,则不会接收到具体的数据
                    <form action="" onSubmit={this.printData}>
                        用户名:<input type="text" name='username' ref={(c) => { this.username = c }} /> <br />
                        密码:<input type="password" name='password' ref={(c) => { this.password = c }} /> <br />
                        <button>登录</button>
                    </form>
                )
            }
        }
        ReactDOM.render(<Login />, document.querySelector('.box'))
    </script>

2、非受控组件

主要步骤:

        (1)定义表单,在需要获取值的表单上增加 onChange 事件。

        (2)通过事件获取结点元素的值,并将值存储到 state 中去。

        (3)若表单事件需要获取结点值,则从 state 中直接读取。

个人理解:受控组件是获取结点,从结点中获取值。非受控组件是直接从 state 中获取到

    <script type="text/babel">
        class Login extends React.Component {
            // 初始化状态
            state = {
                username: '',
                password: ''
            }
            printData = (event) => {
                event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
                // 从状态中直接读取出所需要的值
                alert(`用户名:${this.state.username},密码:${this.state.password}`);
            }

            usernameData = (event) => {
                // 将事件的值放到状态当中去
                this.setState({ username: event.target.value })
            }

            passwordData = (event) => {
                this.setState({ password: event.target.value })
            }

            render() {
                // 当坐在结点元素的值发生改变时,会触发 onchange 事件。
                return (
                    <form action="" onSubmit={this.printData}>
                        用户名:<input type="text" name='username' onChange={this.usernameData} /> <br />
                        密码:<input type="password" name='password' onChange={this.passwordData} /> <br />
                        <button>登录</button>
                    </form>
                )
            }
        }
        ReactDOM.render(<Login />, document.querySelector('.box'))
    </script>

二、函数柯里化

高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数。

        (1)接收的参数是一个函数 。

        (2)调用的返回值依然是一个函数。

          @ 常见的高阶函数有:Promise、setTimeout、arr.map()等等。

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

		<script type="text/javascript" >
			/* function sum(a,b,c){
				return a+b+c
			} */
			
			function sum(a){
				return(b)=>{
					return (c)=>{
						return a+b+c
					}
				}
			}
			const result = sum(1)(2)(3)
			console.log(result);
		</script>

函数柯里化实现:

    <script type="text/babel">
        class Login extends React.Component {
            // 初始化状态
            state = {
                username: '',
                password: ''
            }
            printData = (event) => {
                event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
                // 从状态中直接读取出所需要的值
                alert(`用户名:${this.state.username},密码:${this.state.password}`);
            }

            saveData = (dataType) => {
                return (event) => {
                    this.setState({ [dataType]: event.target.value })
                }
            }

            render() {
                return (
                    <form action="" onSubmit={this.printData}>
                        用户名:<input type="text" name='username' onChange={this.saveData('username')} /> <br />
                        密码:<input type="password" name='password' onChange={this.saveData('password')} /> <br />
                        <button>登录</button>
                    </form>
                )
            }
        }
        ReactDOM.render(<Login />, document.querySelector('.box'))
    </script>

不用函数柯里化的实现:

    <script type="text/babel">
        class Login extends React.Component {
            // 初始化状态
            state = {
                username: '',
                password: ''
            }
            printData = (event) => {
                event.preventDefault(); // 阻止默认事件,这里是阻止表单提交
                // 从状态中直接读取出所需要的值
                alert(`用户名:${this.state.username},密码:${this.state.password}`);
            }

            saveData = (dataType, event) => {
                this.setState({ [dataType]: event.target.value })
            }

            render() {
                return (
                    <form action="" onSubmit={this.printData}>
                        用户名:<input type="text" name='username' onChange={(event) => { this.saveData('username', event) }} /> <br />
                        密码:<input type="password" name='password' onChange={(event) => { this.saveData('password', event) }} /> <br />
                        <button>登录</button>
                    </form>
                )
            }
        }
        ReactDOM.render(<Login />, document.querySelector('.box'))
    </script>

三、生命周期

1、旧生命周期

1.1 概述

 【 PS:挂载:mount;卸载:unmount 】

(1)初始化阶段:由ReactDOM.render()触发 —— 初次渲染

        ① constructor()   ——  构造器

        ② componentWillMount()  ——  组件将要挂载

        ③ render()

        ④ componentDidMount()  ——  组件挂载完毕【常用,做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息】                

(2)更新阶段:由组件内部 this.setSate() 或父组件 render 触发

        ① shouldComponentUpdate()  ——  控制组件更新的“阀门”

        ② componentWillUpdate()  ——  组件将要更新

        ③ render() 【必须使用】

        ④ componentDidUpdate()  ——  组件更新完毕

(3)卸载组件:由 ReactDOM.unmountComponentAtNode() 触发

        ① componentWillUnmount()  ——  组件将要卸载【常用,做一些收尾的事,例如:关闭定时器、取消订阅消息】

1.2 案例演示

效果:求和案例,每次点击+1按钮,count+1并显示在页面上。

以下这段代码直接 copy 学习视频中的:

	<script type="text/babel">
		//创建组件
		class Count extends React.Component{

			// 构造器
			constructor(props){
				console.log('Count---constructor');
				super(props)
				//初始化状态
				this.state = {count:0}
			}

			// 加 1 按钮的回调
			add = ()=>{
				// 获取原状态
				const {count} = this.state
				// 更新状态
				this.setState({count:count+1})
			}

			// 卸载组件按钮的回调
			death = ()=>{
				ReactDOM.unmountComponentAtNode(document.getElementById('test'))
			}

			// 强制更新按钮的回调
			force = ()=>{
				this.forceUpdate()
			}

			// 组件将要挂载的钩子
			componentWillMount(){
				console.log('Count---componentWillMount');
			}

			// 组件挂载完毕的钩子
			componentDidMount(){
				console.log('Count---componentDidMount');
			}

			// 组件将要卸载的钩子
			componentWillUnmount(){
				console.log('Count---componentWillUnmount');
			}

			// 控制组件更新的“阀门”
			shouldComponentUpdate(){
				console.log('Count---shouldComponentUpdate');
				return true
			}

			// 组件将要更新的钩子
			componentWillUpdate(){
				console.log('Count---componentWillUpdate');
			}

			// 组件更新完毕的钩子
			componentDidUpdate(){
				console.log('Count---componentDidUpdate');
			}

			render(){
				console.log('Count---render');
				const {count} = this.state
				return(
					<div>
						<h2>当前求和为:{count}</h2>
						<button onClick={this.add}>点我+1</button>
						<button onClick={this.death}>卸载组件</button>
						<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
					</div>
				)
			}
		}
		
		// 父组件A
		class A extends React.Component{
			// 初始化状态
			state = {carName:'奔驰'}

			changeCar = ()=>{
				this.setState({carName:'奥拓'})
			}

			render(){
				return(
					<div>
						<div>我是A组件</div>
						<button onClick={this.changeCar}>换车</button>
						<B carName={this.state.carName}/>
					</div>
				)
			}
		}
		
		// 子组件B
		class B extends React.Component{
			// 组件将要接收新的props的钩子
			componentWillReceiveProps(props){
				console.log('B---componentWillReceiveProps',props);
			}
			render(){
				console.log('B---render');
				return(
					<div>我是B组件,接收到的车是:{this.props.carName}</div>
				)
			}
		}
		
		// 渲染组件
		ReactDOM.render(<Count/>,document.getElementById('test'))
	</script>

2、新生命周期

2.1 概念

(1)初始化阶段:由ReactDOM.render()触发  ——  初次渲染

        ① constructor()

        ② getDerivedStateFromProps  —— 【新增:从 props 中获取衍生的 state】

        ③ render()

        ④ componentDidMount()

(2)更新阶段: 由组件内部 this.setSate() 或父组件重新 render 触发

        ① getDerivedStateFromProps

        ② shouldComponentUpdate()

        ③ render()

        ④ getSnapshotBeforeUpdate  —— 【新增:在更新前获取快照】

        ⑤ componentDidUpdate()

(3)卸载组件:由 ReactDOM.unmountComponentAtNode() 触发

        ① componentWillUnmount()

2.2 getDerivedStateFromProps(了解)

        若 state 的值在任何时候都取决于 props,则可以使用 getDerivedStateFromProps。仅使用于罕见的案例,并且派生状态会导致状态冗余,了解即可。

        官方文档说明:React.Component – React

2.3 getSnapshotBeforeUpdate(了解)

        getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

        返回 snapshot 的值(或 null)。

  componentDidUpdate(prevProps, prevState, snapshot) {  }

3、总结

旧生命周期新生命周期
初始化阶段

① constructor()

componentWillMount()

③ render()

④ componentDidMount()

① constructor()

getDerivedStateFromProps()

③ render()

④ componentDidMount()

更新阶段

① shouldComponentUpdate()

componentWillUpdate()

③ render() 

④ componentDidUpdate()

getDerivedStateFromProps()

② shouldComponentUpdate()

③ render()

④ getSnapshotBeforeUpdate()

卸载组件① componentWillUnmount()① componentWillUnmount()
重要钩子

① render() 【必须使用】

② componentDidMount()  【初始化】

③ componentWillUnmount()  【收尾】

新 / 旧区别

① 预计弃用 3 个:componentWillMount()、componentWillUpdate()、componentWillReceiveProps。如果要使用,需要加前缀 UNSAFE_。

② 新增 2 个:getDerivedStateFromProps()、getSnapshotBeforeUpdate()。

四、DOM 的 diffing 算法

1、概述

        在入门章节曾说过,React 有一个特点是:使用虚拟 DOM + Diffing 算法,尽量减少与真实 DOM 的交互

主要步骤(个人理解):

(1)初始化的时候,虚拟 DOM 获取到了数据 A,并呈现到真实的 DOM 上。

(2)虚拟 DOM 再次接收到 数据 B 时,会先将数据 A 与 数据 B 进行对比,对比的最小力度是标签

        ① 若标签内容没有发生任何改变,则不进行更新,直接沿用之前的。

        ② 若标签内容发生了改变,则将此时的数据(更新后的)呈现到页面中。

 2、关于 key 的经典面试题

2.1 react / vue 中的 key 有什么作用?( key 的内部原理是什么?)

虚拟 DOM 中 key 的作用:

(1)简单的说:key 是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用。

(2)详细的说:当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较,比较规则如下:

        ① 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的key:

                a. 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM。

                b. 若虚拟 DOM 中内容变了,则生成新的真实 DOM 替换之前的 真实 DOM。

        ② 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,则根据数据创建新的真实 DOM,随后渲染到页面上。

2.2 为什么遍历列表时,key 最好不要用 index ?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值