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中引入createRefimport 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">用户名:<inputtype="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">用户名:<inputtype="text"id="username"name="username"value={ this.state.username }onChange={ e => this.handleChange(e) }/></label>// 输入框<label htmlFor="password">用户名:<inputtype="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.valueconst password = this.passwordRef.current.value// 获取的是表单的元素console.log(this.usernameRef.current);console.log(username, password);// 表单数据收集完毕 可发送响应的网络请求}}
