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也是可以的。



目录
相关文章
|
7月前
|
缓存 前端开发 JavaScript
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
🌟蒋星熠Jaxonic,前端探索者。专注React Hooks深度实践,从原理到实战,分享状态管理、性能优化与自定义Hook精髓。助力开发者掌握函数组件的无限可能,共赴技术星辰大海!
React Hooks深度解析与最佳实践:提升函数组件能力的终极指南
|
12月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
396 68
|
12月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
345 67
|
12月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
435 62
|
前端开发
React 中高阶组件的原理是什么?
React 中高阶组件的原理是什么?
281 57
|
移动开发 前端开发 JavaScript
React音频播放列表组件:常见问题、易错点与解决方案
本文介绍了在React中实现音频播放列表时常见的挑战及解决方案。通过基础实现、常见问题分析和最佳实践,帮助开发者避免状态管理、生命周期控制和事件处理中的陷阱。关键点包括使用`useRef`操作音频元素、`useState`同步播放状态、全局状态管理防止多音频同时播放、以及通过`useEffect`清理资源。还提供了代码示例和跨浏览器兼容性处理方法,确保高效实现功能并减少调试时间。
423 30
|
移动开发 前端开发 UED
React 音频音量控制组件 Audio Volume Control
在现代Web应用中,音频播放功能不可或缺。React以其声明式编程和组件化开发模式,非常适合构建复杂的音频音量控制组件。本文介绍了如何使用HTML5 `&lt;audio&gt;`元素与React结合,实现直观的音量控制系统,并解决了常见问题如音量范围不合理、初始音量设置及性能优化等,帮助开发者打造优秀的音频播放器。
451 27
|
编解码 前端开发 开发者
React 图片组件样式自定义:常见问题与解决方案
在 React 开发中,图片组件的样式自定义常因细节问题导致布局错乱、性能损耗或交互异常。本文系统梳理常见问题及解决方案,涵盖基础样式应用、响应式设计、加载状态与性能优化等,结合代码案例帮助开发者高效实现图片组件的样式控制。重点解决图片尺寸不匹配、边框阴影不一致、移动端显示模糊、加载失败处理及懒加载等问题,并总结易错点和最佳实践,助力开发者提升开发效率和用户体验。
530 22
|
移动开发 前端开发 开发者
React 音频播放控制组件 Audio Controls
本文介绍了如何使用React构建音频播放控制组件,涵盖HTML5 `&lt;audio&gt;`标签和React组件化思想的基础知识。针对常见问题如播放状态管理、进度条更新不准确及跨浏览器兼容性,提供了详细的解决方案和代码示例。同时,还总结了易错点及避免方法,如确保音频加载完成再操作、处理音频错误等,帮助开发者实现稳定且功能强大的音频播放器。
499 11
|
移动开发 前端开发 UED
React 音频进度条组件 Audio Progress Bar
在现代Web开发中,音频播放功能不可或缺。使用React构建音频进度条组件,不仅能实现播放控制和拖动跳转,还能确保代码的可维护性和复用性。本文介绍了如何利用HTML5 `&lt;audio&gt;`标签的基础功能、解决获取音频时长和当前时间的问题、动态更新进度条样式,并避免常见错误如忘记移除事件监听器和忽略跨浏览器兼容性。通过这些方法,开发者可以打造高质量的音频播放器,提升用户体验。
566 6

热门文章

最新文章