1、state数据不可变的力量

当我们需要对组件的状态进行更改的时候,会使用this.setState()方法。该方法使用后,组件render方法就会被调用,组件便会重新进行渲染,当需要对原始数据进行修改时, 不要直接对原始的state状态进行修改,而应该先将原始的状态进行拷贝,然后在拷贝的数据上进行数据的修改,将拷贝修改后的数据赋值给需要修改组件的状态,重新调用render函数,重新渲染UI界面。那么为什么需要这么做,而不是直接对组件的state数据进行修改呢?
原因: 在对组件状态进行更改的时候,我们的render函数总会重新调用渲染,render函数的调用,就会导致render函数里面所有的子组件都会重新进行渲染, 这个操作非常消耗react渲染性能, 所以我们在使用的时候应该考虑性能优化方面的问题。如shouldComponentUpdate进行性能优化和组件继承PureComponent组件, 在生命周期和react纯函数组件中会对我们的修改后的props和修改后的state进行浅层比较, 如果我们对原始state进行修改的话, 生命周期函数shouldComponentUpdata会一直返回true,那么对我们的性能一点优化都没有, 那么这种做法就是有问题的, 所以我们不能对我们原始的数据进行相应的修改。

2、兄弟组件通信-事件总线

在react组件化开发中, 父组件向子组件通信通过组件的标签使用props来传值,子组件通过调用父组件传递的方法来与父组件进行通信, 在日常的开发中,兄弟组件之间的通信我们可以利用兄弟组件共同的父组件进行通信,但是这个过程是比较烦琐的。所以,在兄弟组件之间进行通信我们还可以使用事件总线第三方库。

  1. // 事件总线Events具体的使用过程
  2. // 1 安装、增加事件总线库的依赖
  3. # yarn add events;
  4. // 2 引入事件总线
  5. import { EventEmitter } form 'events';
  6. // 3 创建事件总线的实例对象 方便不同的组件进行使用 直接使用关键字new来创建事件总线
  7. const eventBus = new EventEmitter();
  8. // 在实际的业务组件中使用
  9. // 组件A和组件B的关系
  10. export default class App extends Component {
  11. render() {
  12. return (
  13. <div>
  14. <p>我是App组件</p>
  15. // 组件A和组件B时互为兄弟的关系
  16. <ComponentA />
  17. <ComponentB />
  18. </div>
  19. )
  20. }
  21. }
  22. // 在组件A中触发
  23. class ComponentA extends Component {
  24. render() {
  25. return (
  26. <div>
  27. <p>组件A</p>
  28. <button onClick={() => this.btnClick()}>触发事件</button>
  29. </div>
  30. )
  31. }
  32. // 点击事件的定义
  33. btnClick() {
  34. // 触发事件 并传递相应的参数 使用事件总线触发事件 第一个参数时事件名 后面是随事件传递的参数
  35. eventBus.emit('message', params1, params2)
  36. }
  37. }
  38. // 在组件B中监听
  39. class ComponentB extends Component {
  40. // 在生命周期组件挂载的函数中 订阅事件
  41. componentDidMount() {
  42. // 第一个参数是 触发的事件名 第一个参数是事件触发后 第二个参数是事件触发后对应的回调函数
  43. 可以使用箭头函数 也可以将函数单独提取出来 方便在组件卸载的时候 移除事件监听
  44. eventBus.addListener('message', this.handleMessageListener)
  45. }
  46. // 在生命周期组件卸载的函数中 取消订阅的事件
  47. componentWillUnmount() {
  48. // 第一个参数是 移除的事件名 第二个参数是 移除事件的回调函数
  49. // 如果不传入第二个参数的话 就会在组件卸载的时候移除所有监听的事件
  50. // 一般的话 我们会在组件中移除当前组件的回调函数
  51. eventBus.removeListener('message', this.handleMessageListener)
  52. }
  53. // 监听的事件的回调函数
  54. handleMessageListener(arg1, arg2) {
  55. // 可以获取传递过来的参数 可以直接在回调函数中获取传入的参数
  56. console.log(arg1, arg2)
  57. // doSomething 在这里可以使用其他组件传递过来的数据 在组件就可以进行相应的操作。
  58. }
  59. // 渲染函数
  60. render() {
  61. return (
  62. <div>
  63. <p>组件B</p>
  64. </div>
  65. )
  66. }
  67. }

3、受控组件的使用

使用方法: 属性的形式:ref=”字符串/对象/回调函数”

3.1 ref使用的三种方式:

3.1.1 使用ref绑定一个字符串(后期的语法此api将会被废弃,不推荐使用)

  1. import React, { PureComponent } from 'react'
  2. export default class App extends PureComponent {
  3. render() {
  4. <div>
  5. // 这里的ref绑定为字符串的形式
  6. <h2 ref="refTitle">hello react</h2>
  7. <button onClick={ () => this.btnClick() }>测试按钮</button>
  8. </div>
  9. }
  10. // 点击事件
  11. btnClick() {
  12. // 获取ref绑定的数据
  13. this.refs.refTitle.innerHTML = '最新修改的数据'
  14. }
  15. }

3.1.2 ref绑定为createRef()函数创建对象的形式(官方推荐使用)

  1. // 从react中引入createRef
  2. import React, { PureComponent, createRef } from 'react'
  3. export default class App extends PureComponent {
  4. constructor() {
  5. super()
  6. // 创建ref的对象 将其绑在dom结构上
  7. this.refTitle = createRef();
  8. }
  9. render() {
  10. <div>
  11. // 这里的ref绑定为对象的形式
  12. <h2 ref={ this.refTitle }>hello react</h2>
  13. <button onClick={() => this.btnClick()}>测试按钮</button>
  14. </div>
  15. }
  16. btnClick() {
  17. // 使用ref获取dom解构 注意:这里的dom结构需要使用current的语法来进行获取
  18. this.refTitle.current.innerHTML = '最新修改的数据';
  19. }
  20. }

3.1.3 将ref绑定为回调函数的形式

  1. import React, { PureComponent } from 'react'
  2. export default class App extends PureComponent {
  3. constructor() {
  4. super()
  5. // 创建ref的对象 赋值为空
  6. this.refTitle = null;
  7. }
  8. render() {
  9. <div>
  10. // 这里的ref绑定为回调函数的形式
  11. <h2 ref={ arg => this.refTitle = arg }>hello react</h2>
  12. <button onClick={ () => this.btnClick() }>测试按钮</button>
  13. </div>
  14. }
  15. btnClick() {
  16. // 可以直接获取dom的结构
  17. this.refTitle.innerHTML = "最新修改的数据";
  18. }
  19. }

3.2 ref可以绑定在我们自定义的组件上

当ref绑定在自定义的组件上面的时候,我们就可以直接获取到组件的实例对象,然后在其他的地方(如父组件中)查看自定义组件的实例,还可以使用组件实例对应的属性和方法。当然ref只能绑定在类组件上,因为只有类组件才有实例化对象,函数式组件是没有实例化对象的。所以ref不能绑定在函数式组件上面。

  1. # 父组件将ref绑定在子组件的上面 父组件可以直接访问子组件的实例对象、和访问子组件的属性和方法
  2. import React, { PureComponent, createRef } from 'react'
  3. // 子组件
  4. class Son extends PureComponent {
  5. constructor() {
  6. super()
  7. this.state = {
  8. counter: 100
  9. }
  10. }
  11. render() {
  12. return (
  13. <div style={{ backgroundColor: 'pink' }}>
  14. <h2>当前计数:{ this.state.counter }</h2>
  15. <button onClick={ () => this.btnClick() }>点击增加</button>
  16. </div>
  17. )
  18. }
  19. // 更新组件内部状态的方法
  20. btnClick() {
  21. this.setState({
  22. counter: this.state.counter + 50
  23. })
  24. }
  25. }
  26. // 父组件
  27. export default class App extends PureComponent {
  28. constructor() {
  29. super()
  30. this.sonRef = createRef();
  31. }
  32. render() {
  33. return (
  34. <div>
  35. <h2>app组件</h2>
  36. <button onClick={ () => this.handleClick()}>按钮</button>
  37. <hr/>
  38. <Son ref={this.sonRef}/>
  39. </div>
  40. )
  41. }
  42. handleClick() {
  43. console.log(this.sonRef.current);
  44. // 直接调用子组件的函数 注意是以.current的形式进行访问
  45. this.sonRef.current.btnClick()
  46. }
  47. }

4、受控组件的使用

受控组件的基本使用: 以input输入框为例,监听input输入框的值的变化,获取输入框最新的值,然后将输入框最新的值保存在组件的状态state中,每监听一次修改一次状态,提交的时候表单数据就会保存在组件自身的状态state中,我们可以直接从state中取数据即可。

  1. import React, { PureComponent } from 'react'
  2. export default class App extends PureComponent {
  3. constructor() {
  4. super()
  5. this.state = {
  6. username: ''
  7. }
  8. }
  9. render() {
  10. return (
  11. <div>
  12. <form onSubmit={ e => this.handleSubmit(e) }>
  13. {/* label的for属性与input的id属性进行绑定 当我们在点击label标签的时候 input框会自动进行聚焦 */}
  14. <label htmlFor="username">
  15. 用户名:<input
  16. type="text"
  17. id="username"
  18. value={ this.state.username }
  19. onChange={ e => this.handleChange(e) }/>
  20. </label>
  21. <input type="submit"/>
  22. </form>
  23. </div>
  24. )
  25. }
  26. // 收集表单数据
  27. handleChange(event) {
  28. console.log(event.target.value);
  29. // 将获取的数据绑定到组件自身的state属性中
  30. this.setState({
  31. username: event.target.value
  32. })
  33. }
  34. // 表单数据提交
  35. handleSubmit(e) {
  36. e.preventDefault()
  37. // 收集到的表单数据
  38. console.log(this.state);
  39. }
  40. }

当有多个表单项的时候,处理函数会有多个相同的处理方法,我们需要封装一个公用的函数来处理每一个输入框的值。

  1. constructor() {
  2. super()
  3. this.state = {
  4. username: '',
  5. password: ''
  6. }
  7. }
  8. # 渲染函数
  9. render() {
  10. return (
  11. <div>
  12. <form onSubmit={ e => this.handleSubmit(e) }>
  13. // 输入框
  14. <label htmlFor="username">
  15. 用户名:<input
  16. type="text"
  17. id="username"
  18. name="username"
  19. value={ this.state.username }
  20. onChange={ e => this.handleChange(e) }/>
  21. </label>
  22. // 输入框
  23. <label htmlFor="password">
  24. 用户名:<input
  25. type="text"
  26. id="password"
  27. name="password"
  28. value={ this.state.password }
  29. onChange={ e => this.handleChange(e) }/>
  30. </label>
  31. <input type="submit"/>
  32. </form>
  33. </div>
  34. )
  35. }
  36. // 方法1 统一处理表单数据的函数 使用传递过来的name属性 因为name属性对每一个表单元素都做了标记
  37. handleChange(event) {
  38. // event.target.value就是获取的最新的值
  39. this.setState = {
  40. // 前面的值 是动态的 根据填写的表单的数据不同而不同
  41. // 这种写法 是es6里面的 计算属性名
  42. [event.target.name]: event.target.value
  43. }
  44. }
  45. // 方法2 在处理事件的时候 向表单里面传递不同的参数 以此来做一个标记 也是可取的
  46. // 渲染方法
  47. onChange={ e => this.handleChange(e, 'username')}
  48. onchange={ e => this.handleChange(e, 'password')}
  49. // 事件处理
  50. handleChange(event, type) {
  51. // 这里的type是根据我们回调函数传递过来的数据决定的
  52. this.setState({
  53. type: event.target.value
  54. })
  55. }

5、非受控组件的使用(官方不推荐使用)

在受控组件中,表单元素的数据是保存在组件自身的状态中,是一种单项数据流的方式。在非受控组件中,我们就可以使用ref的方式来获取dom元素,并通过dom元素来获取表单数据项的值。但是,在react中,官方并不推荐这么使用。

  1. import React, { PureComponent, createRef } from 'react'
  2. export default class App extends PureComponent {
  3. constructor() {
  4. super()
  5. // 绑定表单域的值
  6. this.usernameRef = createRef();
  7. this.passwordRef = createRef();
  8. }
  9. render() {
  10. return (
  11. <div>
  12. <h2>表单数据的提交</h2>
  13. <form onSubmit={ e => this.handleSubmit(e) }>
  14. <label htmlFor="username">
  15. 用户名:
  16. <input type="text" id="username" ref={ this.usernameRef }/>
  17. </label>
  18. <br/>
  19. <label htmlFor="password">
  20. 密码:
  21. <input type="password" id="password" ref={ this.passwordRef }/>
  22. </label>
  23. <br/>
  24. {/* 表单的提交 */}
  25. <input type="submit"/>
  26. </form>
  27. </div>
  28. )
  29. }
  30. handleSubmit(event) {
  31. // 阻止表单提交的默认事件
  32. event.preventDefault()
  33. // 获取表单输入的数据
  34. const username = this.usernameRef.current.value
  35. const password = this.passwordRef.current.value
  36. // 获取的是表单的元素
  37. console.log(this.usernameRef.current);
  38. console.log(username, password);
  39. // 表单数据收集完毕 可发送响应的网络请求
  40. }
  41. }