React全家桶:非受控、受控组件 --高阶函数_函数柯里化--生命周期--DOM的diffing算法

简介: React全家桶:非受控、受控组件 --高阶函数_函数柯里化--生命周期--DOM的diffing算法

写在前面

在最近看了React之后,一直觉得学的懵懵然,虽然很多大佬的手写笔记,写的都很不错,但是我一直没有我想要的那种细无巨细,比如类式组件this指向问题的追根溯源,又比如三大实例属性简写的由来,总之我还是决定做一份事无巨细的笔记。

那就让我们开始吧!


非受控组件

非受控组件(现用现取)使用ref将标签取到类的实例上

1. 绑定的类的实例上的函数会接受一个event的参数。可以使用event.preeventDefault()来阻止表单的默认提交。

<script type="text/babel">
        //创建组件
        class Login extends React.Component{
                handleSubmit = (event)=>{
                        event.preventDefault() //阻止表单提交
                        const {username,password} = this
                        alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
                }
                render(){
                        return(
                                <form onSubmit={this.handleSubmit}>
                                        用户名:<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>
复制代码


受控组件

  • 双向绑定

使用onChange事件函数调用方法然后将even.target传入状态state中

可以根据输入实时更新状态。

<script type="text/babel">
        //创建组件
        class Login extends React.Component{
                //初始化状态
                state = {
                        username:'', //用户名
                        password:'' //密码
                }
                //保存用户名到状态中
                saveUsername = (event)=>{
                        this.setState({username:event.target.value})
                }
                //保存密码到状态中
                savePassword = (event)=>{
                        this.setState({password:event.target.value})
                }
                //表单提交的回调
                handleSubmit = (event)=>{
                        event.preventDefault() //阻止表单提交
                        const {username,password} = this.state
                        alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
                }
                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>
                        )
                }
        }
        //渲染组件
        ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
复制代码

#高阶函数_函数柯里化


解决代码重复度

原因:初始化调用render的时候如果回调函数有()就会立即调用,那么回调的也就是函数的返回值,不在是函数了,但是如果不加()又不能传参。


解决方法

  1. 高阶函数
  • 让函数的返回值也是一个函数

这样回调的函数就是那个返回的函数了。

  • 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
    1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
    2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。常见的高阶函数有:Promise、setTimeout、arr.map()等等
  • 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
        return(b)=>{
                return (c)=>{
                        return a+b+c
                }
        }
}
const result = sum(1)(2)(3)
console.log(result);
复制代码


对象的相关知识

  • 变量做为属性名需要使用[]。

不用柯里化的写法

使用箭头函数调用需要调用的函数

用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
复制代码


引出生命周期

挂载

传送门

zh-hans.reactjs.org/docs/react-…


卸载

  • 卸载组件
death = ()=>{
        //卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
复制代码


  • 书写定时器
  1. 不能放在render方法内部

原因:使用定时器更新状态,会导致再次调用redner方法,随后就会再次调用定时器,这样就会死循环。

微信截图_20221112112918.png


  1. 将定时器绑定在类式组件上进行回调

缺点:必须通过点击事件才能回调定时器

目标:组件一旦挂载就自动触发定时器

微信截图_20221112112936.png


3.使用componentDidMount()

在React创建组件实例对象时就会调用render方法和componentDidMount方法

//组件挂完毕
componentDidMount(){
    console.log('componentDidMount');
    this.timer = setInterval(() => {
        //获取原状态
        let {opacity} = this.state
        //减小0.1
        opacity -= 0.1
        if(opacity <= 0) opacity = 1
        //设置新的透明度
        this.setState({opacity})
    }, 200);
}
复制代码


  • 关闭定时器
  1. 在卸载了组件之后,定时器仍在运行。

微信截图_20221112113058.png


解决方式

  1. 在death方法中卸载组件前,提前将定时器关闭。
death = ()=>{
        clearInterval(this.timer)
        //卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
复制代码


2.使用componentWillUnmount()

//组件将要卸载
componentWillUnmount(){
        //清除定时器
        clearInterval(this.timer)
}
复制代码


  • 生命周期概念

传送门

zh-hans.reactjs.org/docs/state-…

生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子


生命周期(旧)_组件挂载流程

理解

1. 组件从创建到死亡它会经历一些特定的阶段。

2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。

3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。


生命周期流程图(旧)

微信截图_20221112113128.png


挂载

  • 执行顺序

constructor->componentWillMount->render->componentDidMount


生命周期(日) setState流程

componentWillUnmount()

在组件将要卸载的时候调用的“钩子”


shouldComponentUpdate()

  • shouldComponentUpdate()是一个“阀门”

1.在使用setState()之后调用shouldComponentUpdate()判断是否进行下一步。

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


  1. 如果不写,默认返回true


setState流程

shouldComponentUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()


生命周期(旧)_ forceUpdate流程

  • 不改变状态的修改,强制更新。(不经过shouldComponentUpdate())


执行流程

forceUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()


生命周期(旧)_父组件render流程

父子组件

//父组件A
class A extends React.Component{、
        render(){
                return(
                        <div>
                                <div>我是A组件</div>
                                <B />
                        </div>
                )
        }
}
//子组件B
class B extends React.Component{
        render(){
                return(
                        <div>我是B组件,接收到的车是:{this.props.carName}</div>
                )
        }
}
复制代码


  • 实现结果

微信截图_20221112113357.png


组件将要接收新的props的钩子

//组件将要接收新的props的钩子
componentWillReceiveProps(props){
        console.log('B---componentWillReceiveProps',props);
}
复制代码


componentWillReceiveProps()

  • 第一次组件接受props并不会调用这个钩子。
  • 父组件状态更新调用了render方法,导致子组件重新接受props。就会调用这个钩子。


执行流程

父组件render-> componentWillReceiveProps() -> shouldComponentUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()


总结 生命周期(旧)

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

1.  constructor()
2.  componentWillMount()
3.  render()
4.  componentDidMount() =====> 常用
复制代码


  • 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

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

1.  shouldComponentUpdate()
        2.  componentWillUpdate()
        3.  render() =====> 必须使用的一个
        4.  componentDidUpdate()
复制代码


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

1.  componentWillUnmount()  =====> 常用
复制代码
  • 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息


第43集 对比新旧生命周期

下载依赖包

  • react-dom.development.js和react.development.js 版本都相同。
  • cdn链接

react.docschina.org/docs/cdn-li…

  • BootCDN

前端常用的js库的地址

传送门

www.bootcdn.cn/


新生命周期

微信截图_20221112113641.png


新版本使用旧钩子

微信截图_20221112113702.png


即将过时的钩子

传送门

react.docschina.org/docs/react-…

componentWillReceiveProps()、componentWillMount()、componentWillUpdate()这3个旧钩子都得添加UNSAFE_。不然会报错。

微信截图_20221112113724.png


微信截图_20221112113744.png


新旧更替

将旧版本的componentWillReceiveProps()、componentWillMount()、componentWillUpdate()更换为了getDrivedStateFormProps()。并且在调用了setState之后也会调用这个钩子。其他的父组件render、挂载时、强制更新forceUpdate()之后都会执行上面的新钩子,而不在执行之前带有Will的旧钩子。

微信截图_20221112113807.png


getDerivedStateFromProps()

添加static

  • 这个钩子得添加到类的原型上。
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
        console.log('getDerivedStateFromProps',props,state);
        return null
}
复制代码


微信截图_20221112113833.png


必须返回state object 或者 null

微信截图_20221112113849.png


  • 返回状态对象会影响状态更新
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
        console.log('getDerivedStateFromProps',props,state);
        return {count:100}
}
复制代码
  • 这样就无法修改这个状态值。


微信截图_20221112113909.png


  • 使用场景

state 的值在任何时候都取决于 props。

//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
        console.log('getDerivedStateFromProps',props,state);
        return null
}
复制代码

传送门

react.docschina.org/docs/react-…


  • 可以接收state。
static getDerivedStateFromProps(props, state)
复制代码

将props、state里面的属性可以进行对比,按需选择使用。


getSnapshotBeforeUpdate()

返回值

  • 必须是返回一个值,或者null

微信截图_20221112113931.png


//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
        console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
复制代码


  • 实现效果

微信截图_20221112113953.png


使用场景

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

getSnapshotBeforeUpdate(prevProps, prevState)
复制代码


getSnapshotBeforeUpdate举例

新闻列表

属性复习

菜鸟教程传送门

  • scrollTop() 方法返回或设置匹配元素的滚动条的垂直位置。

www.runoob.com/jquery/css-…

  • scrollHeight 属性是一个只读属性,它返回该元素的像素高度,高度包含内边距(padding),不包含外边距(margin)、边框(border),是一个整数,单位是像素 px。

www.runoob.com/jsref/prop-…


使用定时器动态更新状态

  • 使用定时器更新状态
state = {newsArr:[]}
componentDidMount(){
        setInterval(() => {
                //获取原状态
                const {newsArr} = this.state
                //模拟一条新闻
                const news = '新闻'+ (newsArr.length+1)
                //更新状态
                this.setState({newsArr:[news,...newsArr]})
        }, 1000);
}
复制代码


  • 使用getSnapshotBeforeUpdate()记录之前的scrollHeight
getSnapshotBeforeUpdate(){
        return this.refs.list.scrollHeight
}
复制代码


  • 使用componentDidUpdate记录更新后的scrollHeight。

将scrollTop 不断累加前后直接的scrollHeight差值。就可以保持滑动框不变。

componentDidUpdate(preProps,preState,height){
        this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
复制代码


生命周期总结

  1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1.  constructor()
 2. getDerivedStateFromProps 
 3. render()
 4. componentDidMount() =====> 常用
复制代码


  • 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  1. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1.  getDerivedStateFromProps
 2. shouldComponentUpdate()
 3. render()
 4. getSnapshotBeforeUpdate
 5. componentDidUpdate()
复制代码


  1. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.  componentWillUnmount()  =====> 常用
复制代码


  • 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息


重要的勾子

1. render:初始化渲染或更新渲染调用

2. componentDidMount:开启监听, 发送ajax请求

3. componentWillUnmount:做一些收尾工作, 如: 清理定时器


即将废弃的勾子

1. componentWillMount

2. componentWillReceiveProps

3. componentWillUpdate


DOM的diffing算法

复用的最小单位还是标签,如果标签中嵌套了标签,那么如果使用index就有可能会导致部分的标签被服用。但是id可以完全避免这个情况。因为一个标签一个id。

  • 官网传送门

zh-hans.reactjs.org/docs/reconc…

微信截图_20221112114043.png


key的作用?

经典面试题: 1). react/vue中的key有什么作用?(key的内部原理是什么?) 2). 为什么遍历列表时,key最好不要用index?

  1. 虚拟DOM中key的作用:
    1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
    2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
         (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
         (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
 b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
         根据数据创建新的真实DOM,随后渲染到到页面
复制代码


  1. 用index作为key可能会引发的问题:
  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
  2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
  1. 微信截图_20221112114120.png


  1. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
  1. 开发中如何选择key?:
    1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
    2.如果确定只是简单的展示数据,用index也是可以的。



目录
相关文章
|
2月前
|
JavaScript 前端开发 算法
React技术栈-虚拟DOM和DOM diff算法
这篇文章介绍了React技术栈中的虚拟DOM和DOM diff算法,并通过一个实际案例展示了如何使用React组件和状态管理来实现动态更新UI。
41 2
|
3月前
|
JavaScript 前端开发 算法
react中虚拟dom和diff算法
在React中,虚拟DOM(Virtual DOM)和Diff算法是两个核心概念,它们共同工作以提高应用的性能和效率。
41 4
|
2月前
|
JavaScript 前端开发
react学习(3)创建虚拟dom的两种方式
react学习(3)创建虚拟dom的两种方式
169 67
|
1月前
|
JavaScript 前端开发 算法
React 虚拟 DOM 深度解析
【10月更文挑战第5天】本文深入解析了 React 虚拟 DOM 的工作原理,包括其基础概念、优点与缺点,以及 Diff 算法的关键点。同时,分享了常见问题及解决方法,并介绍了作者在代码/项目上的成就和经验,如大型电商平台的前端重构和开源贡献。
57 3
|
2月前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
28 2
|
2月前
|
JavaScript 前端开发
react字符串转为dom标签,类似于Vue中的v-html
本文介绍了在React中将字符串转换为DOM标签的方法,类似于Vue中的`v-html`指令,通过使用`dangerouslySetInnerHTML`属性实现。
92 0
react字符串转为dom标签,类似于Vue中的v-html
|
1月前
|
前端开发 JavaScript 开发者
深入理解React Hooks:提升前端开发效率的关键
【10月更文挑战第5天】深入理解React Hooks:提升前端开发效率的关键
|
11天前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
38 9
|
1月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
1月前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具