1、state数据不可变的力量
当我们需要对组件的状态进行更改的时候,会使用this.setState()方法。该方法使用后,组件render方法就会被调用,组件便会重新进行渲染,当需要对原始数据进行修改时, 不要直接对原始的state状态进行修改,而应该先将原始的状态进行拷贝,然后在拷贝的数据上进行数据的修改,将拷贝修改后的数据赋值给需要修改组件的状态,重新调用render函数,重新渲染UI界面。那么为什么需要这么做,而不是直接对组件的state数据进行修改呢?
原因: 在对组件状态进行更改的时候,我们的render函数总会重新调用渲染,render函数的调用,就会导致render函数里面所有的子组件都会重新进行渲染, 这个操作非常消耗react渲染性能, 所以我们在使用的时候应该考虑性能优化方面的问题。如shouldComponentUpdate进行性能优化和组件继承PureComponent组件, 在生命周期和react纯函数组件中会对我们的修改后的props和修改后的state进行浅层比较, 如果我们对原始state进行修改的话, 生命周期函数shouldComponentUpdata会一直返回true,那么对我们的性能一点优化都没有, 那么这种做法就是有问题的, 所以我们不能对我们原始的数据进行相应的修改。
2、兄弟组件通信-事件总线
在react组件化开发中, 父组件向子组件通信通过组件的标签使用props来传值,子组件通过调用父组件传递的方法来与父组件进行通信, 在日常的开发中,兄弟组件之间的通信我们可以利用兄弟组件共同的父组件进行通信,但是这个过程是比较烦琐的。所以,在兄弟组件之间进行通信我们还可以使用事件总线第三方库。
// 事件总线Events具体的使用过程
// 1 安装、增加事件总线库的依赖
# yarn add events;
// 2 引入事件总线
import { EventEmitter } form 'events';
// 3 创建事件总线的实例对象 方便不同的组件进行使用 直接使用关键字new来创建事件总线
const eventBus = new EventEmitter();
// 在实际的业务组件中使用
// 组件A和组件B的关系
export default class App extends Component {
render() {
return (
<div>
<p>我是App组件</p>
// 组件A和组件B时互为兄弟的关系
<ComponentA />
<ComponentB />
</div>
)
}
}
// 在组件A中触发
class ComponentA extends Component {
render() {
return (
<div>
<p>组件A</p>
<button onClick={() => this.btnClick()}>触发事件</button>
</div>
)
}
// 点击事件的定义
btnClick() {
// 触发事件 并传递相应的参数 使用事件总线触发事件 第一个参数时事件名 后面是随事件传递的参数
eventBus.emit('message', params1, params2)
}
}
// 在组件B中监听
class ComponentB extends Component {
// 在生命周期组件挂载的函数中 订阅事件
componentDidMount() {
// 第一个参数是 触发的事件名 第一个参数是事件触发后 第二个参数是事件触发后对应的回调函数
可以使用箭头函数 也可以将函数单独提取出来 方便在组件卸载的时候 移除事件监听
eventBus.addListener('message', this.handleMessageListener)
}
// 在生命周期组件卸载的函数中 取消订阅的事件
componentWillUnmount() {
// 第一个参数是 移除的事件名 第二个参数是 移除事件的回调函数
// 如果不传入第二个参数的话 就会在组件卸载的时候移除所有监听的事件
// 一般的话 我们会在组件中移除当前组件的回调函数
eventBus.removeListener('message', this.handleMessageListener)
}
// 监听的事件的回调函数
handleMessageListener(arg1, arg2) {
// 可以获取传递过来的参数 可以直接在回调函数中获取传入的参数
console.log(arg1, arg2)
// doSomething 在这里可以使用其他组件传递过来的数据 在组件就可以进行相应的操作。
}
// 渲染函数
render() {
return (
<div>
<p>组件B</p>
</div>
)
}
}
3、受控组件的使用
3.1 ref使用的三种方式:
3.1.1 使用ref绑定一个字符串(后期的语法此api将会被废弃,不推荐使用)
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
render() {
<div>
// 这里的ref绑定为字符串的形式
<h2 ref="refTitle">hello react</h2>
<button onClick={ () => this.btnClick() }>测试按钮</button>
</div>
}
// 点击事件
btnClick() {
// 获取ref绑定的数据
this.refs.refTitle.innerHTML = '最新修改的数据'
}
}
3.1.2 ref绑定为createRef()函数创建对象的形式(官方推荐使用)
// 从react中引入createRef
import React, { PureComponent, createRef } from 'react'
export default class App extends PureComponent {
constructor() {
super()
// 创建ref的对象 将其绑在dom结构上
this.refTitle = createRef();
}
render() {
<div>
// 这里的ref绑定为对象的形式
<h2 ref={ this.refTitle }>hello react</h2>
<button onClick={() => this.btnClick()}>测试按钮</button>
</div>
}
btnClick() {
// 使用ref获取dom解构 注意:这里的dom结构需要使用current的语法来进行获取
this.refTitle.current.innerHTML = '最新修改的数据';
}
}
3.1.3 将ref绑定为回调函数的形式
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor() {
super()
// 创建ref的对象 赋值为空
this.refTitle = null;
}
render() {
<div>
// 这里的ref绑定为回调函数的形式
<h2 ref={ arg => this.refTitle = arg }>hello react</h2>
<button onClick={ () => this.btnClick() }>测试按钮</button>
</div>
}
btnClick() {
// 可以直接获取dom的结构
this.refTitle.innerHTML = "最新修改的数据";
}
}
3.2 ref可以绑定在我们自定义的组件上
当ref绑定在自定义的组件上面的时候,我们就可以直接获取到组件的实例对象,然后在其他的地方(如父组件中)查看自定义组件的实例,还可以使用组件实例对应的属性和方法。当然ref只能绑定在类组件上,因为只有类组件才有实例化对象,函数式组件是没有实例化对象的。所以ref不能绑定在函数式组件上面。
# 父组件将ref绑定在子组件的上面 父组件可以直接访问子组件的实例对象、和访问子组件的属性和方法
import React, { PureComponent, createRef } from 'react'
// 子组件
class Son extends PureComponent {
constructor() {
super()
this.state = {
counter: 100
}
}
render() {
return (
<div style={{ backgroundColor: 'pink' }}>
<h2>当前计数:{ this.state.counter }</h2>
<button onClick={ () => this.btnClick() }>点击增加</button>
</div>
)
}
// 更新组件内部状态的方法
btnClick() {
this.setState({
counter: this.state.counter + 50
})
}
}
// 父组件
export default class App extends PureComponent {
constructor() {
super()
this.sonRef = createRef();
}
render() {
return (
<div>
<h2>app组件</h2>
<button onClick={ () => this.handleClick()}>按钮</button>
<hr/>
<Son ref={this.sonRef}/>
</div>
)
}
handleClick() {
console.log(this.sonRef.current);
// 直接调用子组件的函数 注意是以.current的形式进行访问
this.sonRef.current.btnClick()
}
}
4、受控组件的使用
受控组件的基本使用: 以input输入框为例,监听input输入框的值的变化,获取输入框最新的值,然后将输入框最新的值保存在组件的状态state中,每监听一次修改一次状态,提交的时候表单数据就会保存在组件自身的状态state中,我们可以直接从state中取数据即可。
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor() {
super()
this.state = {
username: ''
}
}
render() {
return (
<div>
<form onSubmit={ e => this.handleSubmit(e) }>
{/* label的for属性与input的id属性进行绑定 当我们在点击label标签的时候 input框会自动进行聚焦 */}
<label htmlFor="username">
用户名:<input
type="text"
id="username"
value={ this.state.username }
onChange={ e => this.handleChange(e) }/>
</label>
<input type="submit"/>
</form>
</div>
)
}
// 收集表单数据
handleChange(event) {
console.log(event.target.value);
// 将获取的数据绑定到组件自身的state属性中
this.setState({
username: event.target.value
})
}
// 表单数据提交
handleSubmit(e) {
e.preventDefault()
// 收集到的表单数据
console.log(this.state);
}
}
当有多个表单项的时候,处理函数会有多个相同的处理方法,我们需要封装一个公用的函数来处理每一个输入框的值。
constructor() {
super()
this.state = {
username: '',
password: ''
}
}
# 渲染函数
render() {
return (
<div>
<form onSubmit={ e => this.handleSubmit(e) }>
// 输入框
<label htmlFor="username">
用户名:<input
type="text"
id="username"
name="username"
value={ this.state.username }
onChange={ e => this.handleChange(e) }/>
</label>
// 输入框
<label htmlFor="password">
用户名:<input
type="text"
id="password"
name="password"
value={ this.state.password }
onChange={ e => this.handleChange(e) }/>
</label>
<input type="submit"/>
</form>
</div>
)
}
// 方法1 统一处理表单数据的函数 使用传递过来的name属性 因为name属性对每一个表单元素都做了标记
handleChange(event) {
// event.target.value就是获取的最新的值
this.setState = {
// 前面的值 是动态的 根据填写的表单的数据不同而不同
// 这种写法 是es6里面的 计算属性名
[event.target.name]: event.target.value
}
}
// 方法2 在处理事件的时候 向表单里面传递不同的参数 以此来做一个标记 也是可取的
// 渲染方法
onChange={ e => this.handleChange(e, 'username')}
onchange={ e => this.handleChange(e, 'password')}
// 事件处理
handleChange(event, type) {
// 这里的type是根据我们回调函数传递过来的数据决定的
this.setState({
type: event.target.value
})
}
5、非受控组件的使用(官方不推荐使用)
在受控组件中,表单元素的数据是保存在组件自身的状态中,是一种单项数据流的方式。在非受控组件中,我们就可以使用ref的方式来获取dom元素,并通过dom元素来获取表单数据项的值。但是,在react中,官方并不推荐这么使用。
import React, { PureComponent, createRef } from 'react'
export default class App extends PureComponent {
constructor() {
super()
// 绑定表单域的值
this.usernameRef = createRef();
this.passwordRef = createRef();
}
render() {
return (
<div>
<h2>表单数据的提交</h2>
<form onSubmit={ e => this.handleSubmit(e) }>
<label htmlFor="username">
用户名:
<input type="text" id="username" ref={ this.usernameRef }/>
</label>
<br/>
<label htmlFor="password">
密码:
<input type="password" id="password" ref={ this.passwordRef }/>
</label>
<br/>
{/* 表单的提交 */}
<input type="submit"/>
</form>
</div>
)
}
handleSubmit(event) {
// 阻止表单提交的默认事件
event.preventDefault()
// 获取表单输入的数据
const username = this.usernameRef.current.value
const password = this.passwordRef.current.value
// 获取的是表单的元素
console.log(this.usernameRef.current);
console.log(username, password);
// 表单数据收集完毕 可发送响应的网络请求
}
}