ReactP9

编程入门 行业动态 更新时间:2024-10-08 06:19:48

ReactP9

ReactP9

ReactP9_不可变数据的力量_事件总线_ref_受控/非受控组件

  • 不可变数据的力量(The Power Of Not Mutating Data)
  • 事件总线
  • 关于ref
  • 受控/非受控组件
    • 受控组件
    • 非受控组件

这边是react学习记录,期间加入了大量自己的理解,用于加强印象,若有错误之处还请多多指出

不可变数据的力量(The Power Of Not Mutating Data)

不可变数据的力量,代表的就是不可变数据设计原则。

React的生命周期中每次调用ComponentShouldUpdate()会获取props/state,利用现有的数据跟将要改变的数据进行比较,更新变化的数据并进行渲染。此举最大限度减少不必要的更新,达到性能优化的目的。因此,使用时不建议直接更改state里面的数据,而是通过setState去改变参数。

用一个简单案例来说清楚:

  • 每次单击“加入新数据”,在底部会加入一个设置好的新数组以及对应的“单价+1”按钮
  • 每次单击“单价+1”,水果名字后面的数字+1
  • 每次单击按钮页面立即更新

错误代码如下:

import React, { PureComponent } from 'react'export default class App extends PureComponent {constructor(props){super(props);this.state = {fruits:[{name:'apple',price:10},{name:'orange',price:20},{name:'watermelon',price:30},]}}render() {return (<div><ul>{this.state.fruits.map((item,index)=>{return (<li>{item.name}{item.price}<button onClick={ e=>{this.priceAddtion(index)}}>单价+1</button></li>)})}</ul><button onClick={ e => {this.insertData()}}>加入新数据</button></div>)}insertData(){const newData = {name:"grapes",price:40};//错误的数据更新方法    ShouldComponentUpdate——SCU优化失效this.state.fruits.push(newData);this.setState({fruits:this.state.fruits})}priceAddtion(index){//错误的数据更新方法this.state.fruits[index].price++;this.setState({fruits:this.state.fruits})}
}

此处insertData()中看似进行了一次setState的操作,但是实际上数据并不会发生任何改变。这牵扯到JS的语言特性,JS语言是一种标记语言,每个参数所保存的内容不是内容本身,而是存放对应内容的内存首地址。代码中fruits:this.state.fruits执行的时候,是把原本fruits数组的首地址赋给了setState去执行数据更新。虽然前面的push方法已经在数组的后面插入了一个新的数据,但是由于数组的首地址并没有发生改变,fruits的首地址是D,而this.state.fruits表明的数组和fruits是同一个数组,因此首地址相同,同为D。那么fruits就等于this.state.fruits,被判定为数据没有发生改变,也就不会执行更新操作了。


这张图比较形象的描绘了变量在内存中的表现。其中名称上方指的是内存当前地址,名称下方是指内存指向的地址。关于内存的基本原理请学习计算机组成。(途中所有的地址是编的,主要强调说明的是内存之间的关系,而非在内存中的表现一定如此)

正确代码如下:

import React, { PureComponent } from 'react'export default class App extends PureComponent {constructor(props){//省略}render() {return (//省略)}insertData(){const newData = {name:"grapes",price:40};//正确的数据更新方法const newState = [...this.state.fruits,newData];this.setState({fruits:newState});}priceAddtion(index){//正确的数据更新方法const newData = [...this.state.fruits];newData[index].price += 1;this.setState({fruits:newData});}
}

该大致思想是创建一个新的数组,这样系统就会分配一个新的空间,该空间有着和原本数组不同的内存地址,通过ES6拆分数组元素的语法,并在末尾追加一个新的元素的方法,构建一个数据结构相同,但是包含了新的元素的数组。最后再用setState方法来更新数据。避免由于内存地址赋值原因导致的更新失败bug。

关于单击+1,总体思路大致相同,不多赘述。

事件总线

在研究事件总线之前,首先需要安装相关插件

终端执行代码:yarn add events


事件总线,可以理解成一个能够被所有组件调用的“全局数据”。举个例子:

首先构造一个简单的,具有兄弟关系组件的一个页面,其中父组件是App,子组件Home和Profile互为兄弟组件关系包含于App之中

import React, { PureComponent } from 'react'class Home extends PureComponent{render(){return(<div>Home</div>)}
}class Profile extends PureComponent{render(){return(<div>profile<button>hello</button></div>)}
}export default class App extends PureComponent {render() {return (<div><Home/><Profile/></div>)}
}

作为一个“全局的数据”,根据使用的逻辑,可以大致分为:

  • 引用
  • 声明
  • 监听
  • 执行
  • 取消监听

接下来针对每一步给出具体代码:

  • 引用
import {EventEmitter} from 'events'
//event bus 
const eventBus = new EventEmitter();

安装了events模块插件之后,可以调用到events包中的一个类EventEmitter,这是一个用于创建事件总线的类

针对EventEmitter创建一个对象实例,取名eventBus(bus含有总线的意思)

  • 声明
emitEvent(){//需要在btn中添加对该函数的调用eventBus.emit("sayhello","hello home",123);}

emit需要输入两个以上的参数:

第一个参数是事件的key,可以理解成需要调用总线事件时需要的“口令”

其余参数则是用于进行共享的数据(会被依次作为参数放入到后面需要执行的函数中

  • 监听
componentDidMount(){//用于事件监听eventBus.addListener("sayhello",this.handleSayHelloListener)//此处函数后不能加()}componentWillUnmount(){//取消事件监听eventBus.removeListener("sayhello",this.handleSayHelloListener);}

针对事件总线调用需要添加监听事件,根据官方指定的监听规则,需要在钩子函数componentDidMount()中添加对总线事件的监听;而在componentWillUnmount()中,也就是组件即将销毁之前,取消对总线事件的监听。

这里有一个需要注意的地方,添加监听事件,引用对应的执行函数时,函数名后面不能添加小括号,不然会被视作是调用该函数,所在的地方会被填充的是函数执行之后return返回的值,而在此处仅仅是为了表明监听触发后需要执行的指定函数,而非调用该函数

  • 执行
handleSayHelloListener(num,message){console.log(num,message);}

监听到了“口令”“sayhello”之后,调用并执行了函数handleSayHelloListener,其中,一个事件总线的监听执行流程就走完了。

总结一下:

  • 应用并创建用于事件总线的应用实例后,
  • 在组件profile中添加满足触发条件就会emit的事件“口令”和执行“口令”对应调用函数所需的参数
  • 需要共享数据的组件添加监听事件,监听时间需要提供事件的“口令”以及引用需要执行的函数
  • 触发事件,发出“口令”和参数,根据“口令”和参数执行指定函数
  • 取消监听事件

关于ref

如果需要对DOM进行操作可以使用ref

当前有三种ref获取DOM的方式

  • 第一种,字符串获取
    使用时通过 this.refs.传入的字符串获取对应元素

  • 第二种,传入对象获取
    通过 React.createRef() 创建对象
    使用时获取创建的对象,其中有一个current属性就是对应的元素

  • 第三种,传入函数获取
    该函数会在DOM被挂载时进行回调,函数会传入一个元素对象,可以自己保存
    使用时,直接使用保存的元素对象即可

import React, { createRef, PureComponent } from 'react'export default class App extends PureComponent {constructor(props){super(props);//第二种获取方式this.secondRef = createRef();//第三种获取方式this.thirdEl = createRef();}render() {return (<div><h1 className="firstDOM" ref="first">Hello baby</h1><h1 className="secondDOM" ref={this.secondRef}>Hello doggy</h1><h1 className="thirdDOM" ref={args => this.thirdEl = args}>Hello piggy</h1><hr/><button onClick={e=>{this.changeText()}}>what?</button></div>)}changeText(){//通过字符串获取DOMthis.refs.first.innerHTML = "Come on baby";console.log(this.refs.first);//通过创建对象获取DOMthis.secondRef.current.innerHTML = "Come on doggy";console.log(this.secondRef.current);//回调函数赋值获取DOMthis.thirdEl.innerHTML = "Come on piggy"console.log(this.thirdEl);}
}

根据官方更新的进程,第一种字符串方式可能在未来被移除,所以这边优先推荐使用第二、第三种方式来获取要操作的DOM

ref类型的值根据节点的类型而有所不同

  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性
  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
  • 不能在函数组件上使用 ref 属性,因为他们没有实例

因此,可以在一个组件内通过ref调用另外一个组件的方法(挺像vue中this.$ref来调用其他组件中方法的

受控/非受控组件

受控组件

React中表单元素交由框架内部的state中处理

个人理解:判断是否是受控组件,主要看表单元素是否把state作为唯一数据源

import React, { PureComponent } from 'react'export default class App extends PureComponent {constructor(props){super(props);this.state = {username:""}}render() {return (<div><form onSubmit={e=>{this.formSubmit(e)}}><label htmlFor="">user:{/*受控组件*/}<input type="text" id="username" onChange={e=>{this.formChange(e)}}value={this.state.username}/></label><input type="submit" value="submit"/></form></div>)}formSubmit(e){e.preventDefault();console.log(this.state.username);}formChange(e){this.setState({username:e.target.value})}
}

在这个用例中,先通过onChange监听获取input中的value,再将value赋给state,state发生改变后,主动再将state的数据重新赋给一次input。这种通过state作为组件唯一数据源并且时刻保持state和value同步的组件,就是受控组件,该案例是受控组件的一种基本使用形式。

该数据交互并非双向数据绑定,而是一种单向数据流

非受控组件

表单数据交由DOM节点来处理

官方不建议使用非受控组件来处理表单数据

一般由ref方式来获取表单数据,例如:

constructor(props){super(props);this.usernameRef = createRef();}
render() {return (<div><form onSubmit={e=>{this.formSubmit(e)}}><label htmlFor="">user:<input type="text" id="username" ref={this.usernameRef}/></label><input type="submit" value="submit"/></form></div>)}
formSubmit(e){e.preventDefault();console.log(this.usernameRef.current.value);
}

此处通过this.[refObject].current.value来获取表单中的数据。

感谢coderwhy(王红元老师)的课程

ASH,AZZ

更多推荐

ReactP9

本文发布于:2024-02-14 13:25:55,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1763687.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!